Compare commits
638 Commits
api/v0.26.
...
main
Author | SHA1 | Date |
---|---|---|
|
c77a9799fb | |
|
fd132e2846 | |
|
124fd9fe8f | |
|
3999c651b1 | |
|
b8e667d558 | |
|
d8366598f0 | |
|
a4fbfa885b | |
|
009d764d7f | |
|
42af3383fc | |
|
17e585c90d | |
|
45e34fd5b3 | |
|
48e677ab26 | |
|
b4ad41bef5 | |
|
b09cceb8b9 | |
|
4f207d5108 | |
|
e5ae4097be | |
|
48b16c2e51 | |
|
806d41081a | |
|
4addc4e215 | |
|
82f08530f8 | |
|
f7387ccbef | |
|
3d434f0fe7 | |
|
6c5ffee6c0 | |
|
07058a1f60 | |
|
498ec11b09 | |
|
91c2f594b6 | |
|
d86ccc497f | |
|
493ce833b0 | |
|
d48392519c | |
|
081f333a3a | |
|
369d9eb107 | |
|
68b9bc7d0d | |
|
8aa8679f5b | |
|
41a0e4d324 | |
|
e9ed331572 | |
|
640aa7a79b | |
|
80cb694b10 | |
|
b1fc97f6c6 | |
|
a77cd6263f | |
|
93534085af | |
|
ba238df57b | |
|
3d295cc9c1 | |
|
68d9744428 | |
|
6ec657048d | |
|
59b50fae29 | |
|
6ca182eade | |
|
3efde8d870 | |
|
148bd07a1f | |
|
9feaa2cdf6 | |
|
19ebeeb56f | |
|
ef9608d41d | |
|
a67596e3cf | |
|
d5746c5a0a | |
|
2d6cf83f55 | |
|
227559c60a | |
|
8414fa2aa0 | |
|
bb3e96f68c | |
|
9a8e210df8 | |
|
3ae89361df | |
|
e4b925aa9c | |
|
1066a57871 | |
|
6dafa5ce9a | |
|
904289de75 | |
|
c8284d90f0 | |
|
4654113ca4 | |
|
e721c2016a | |
|
674490569e | |
|
bd119da4c8 | |
|
aa24c35eb4 | |
|
5e98055e29 | |
|
f6eed853e3 | |
|
03b9190c45 | |
|
8d5084bbea | |
|
02b165fcda | |
|
79136ad34a | |
|
d5c29a3133 | |
|
f03a53d73e | |
|
4bdd80f0d5 | |
|
9f17c8b21d | |
|
4c9cc99c76 | |
|
9e6023dbf5 | |
|
c243beeb50 | |
|
75e708baff | |
|
28f2164eea | |
|
6b7d26bdf1 | |
|
735f515d03 | |
|
9c7037771c | |
|
a7b5389aaa | |
|
d3550173d6 | |
|
fabeff2876 | |
|
d2763e6d74 | |
|
65f2a43c9b | |
|
d02dec2e6c | |
|
6cd33ab88c | |
|
6d59fcebc8 | |
|
6655e25469 | |
|
e3cd0be322 | |
|
9a32e0c10a | |
|
90821a7925 | |
|
3c846ebaa8 | |
|
67e9cf40d9 | |
|
b1d5d38b37 | |
|
d9ba54b8e0 | |
|
aaeaf52925 | |
|
891d82618e | |
|
7c9408fcda | |
|
0b463c27c8 | |
|
12dfef1342 | |
|
db0998a44c | |
|
8fa6c76df5 | |
|
b205745e5c | |
|
ddd4d61874 | |
|
b25e69b1a9 | |
|
870da77897 | |
|
c2d6a59235 | |
|
d9db01d360 | |
|
b88af04335 | |
|
5b945f7424 | |
|
e1a9080d46 | |
|
54b9f5106c | |
|
8160146a76 | |
|
7294a62885 | |
|
6c91870e91 | |
|
1e0220c016 | |
|
c5ace72c36 | |
|
ea43c5ecb6 | |
|
fcc5b78845 | |
|
ce2617afa0 | |
|
5729d1c094 | |
|
45c61bfe1b | |
|
36b5271988 | |
|
23687d65a9 | |
|
7500b46874 | |
|
1259a519c7 | |
|
5d74f31d3e | |
|
644ef7c9f7 | |
|
ae49037eab | |
|
ebeead0e14 | |
|
189ba5894b | |
|
711504944c | |
|
8599ef13b7 | |
|
131bcd76bc | |
|
ee6f36262f | |
|
e8f1f12349 | |
|
632a84bf48 | |
|
d534149ddc | |
|
3ff8476fbb | |
|
443ef35e64 | |
|
de958604e8 | |
|
87ab708624 | |
|
1d01d43a2a | |
|
1f6b3577fc | |
|
2ccad39527 | |
|
4da0650005 | |
|
34557726e9 | |
|
438705a1b8 | |
|
474e4c39d4 | |
|
d2b33d1165 | |
|
a7cebe3622 | |
|
1a7d0fe5f0 | |
|
e16d086808 | |
|
a9af390d2d | |
|
5755a87be2 | |
|
87bff0e6fd | |
|
b24fb414c3 | |
|
7203b194c5 | |
|
646646bd6e | |
|
d56ac02a95 | |
|
e189b0c271 | |
|
a716f8b292 | |
|
e5343d4f7f | |
|
b93308f488 | |
|
14e5969687 | |
|
649188c828 | |
|
c1c5cc14a8 | |
|
168d33c430 | |
|
1aa234649c | |
|
26cdb3659c | |
|
9a59f96178 | |
|
66256a4f84 | |
|
e11935b48f | |
|
e34881d370 | |
|
0f17d9d9f0 | |
|
2940e1bb8f | |
|
176bf9528a | |
|
db92b7b92b | |
|
f5af4aafe1 | |
|
2902dfa072 | |
|
d5e9ad4e79 | |
|
1aa87f8d79 | |
|
e207b2f81c | |
|
2387dd94a5 | |
|
dc1a6154b8 | |
|
c6a6747474 | |
|
3361a1378c | |
|
beba7d90d2 | |
|
3419b2416b | |
|
af1b13de06 | |
|
edb686882a | |
|
9a9ed7b5f9 | |
|
56bfc37cab | |
|
47c19b4f88 | |
|
0ad8d3904b | |
|
7c22c990ad | |
|
4aba4c1d79 | |
|
50ab2644f2 | |
|
c79407da83 | |
|
d8e7395c9a | |
|
27dc1dca10 | |
|
e6a1f2388d | |
|
b8d2ffb8bf | |
|
6d051b2abc | |
|
fe5bb8d350 | |
|
0fb81f08d9 | |
|
425e3daf37 | |
|
ab64fb477d | |
|
121edb604e | |
|
90b9e0abd1 | |
|
164b992277 | |
|
8e6783fd96 | |
|
0863e022a5 | |
|
b22453a781 | |
|
f78b82579f | |
|
008c5c0634 | |
|
ede9104c40 | |
|
30f4c3dbf3 | |
|
242604b5e3 | |
|
fe8e3a2f18 | |
|
8694e4949d | |
|
25730b122d | |
|
52030916a3 | |
|
7a581f31d9 | |
|
cc5c84431d | |
|
fd99f8c989 | |
|
142eb5ca22 | |
|
2826dde7ea | |
|
4875677707 | |
|
cf8e9917c9 | |
|
88206032dc | |
|
f453888f1f | |
|
3e83225a98 | |
|
32e891a564 | |
|
102e2b3a19 | |
|
317d322803 | |
|
9d4afa9e4b | |
|
12afc54ab1 | |
|
08bd89dc1f | |
|
2b714c5193 | |
|
74c14e48ef | |
|
02f66f4198 | |
|
cb9f657c05 | |
|
651751aaac | |
|
f48e791162 | |
|
df1a9ab6e2 | |
|
6a238db493 | |
|
2cc10fc14a | |
|
799d505696 | |
|
c00665d1af | |
|
0c5cc851cc | |
|
e6507779c7 | |
|
6dc5b34312 | |
|
b49999cfc0 | |
|
02fc3223c8 | |
|
e392237e4a | |
|
f86008ff0c | |
|
587a7501fd | |
|
1653230f3d | |
|
aecec57a9c | |
|
1c7b8896b8 | |
|
0c7b287048 | |
|
8fb20fe3ec | |
|
7bc2cbe3c0 | |
|
44912bd43f | |
|
a523f66f22 | |
|
a33bb0eedb | |
|
e093c3ecd3 | |
|
e028cd962f | |
|
ec6d6871f6 | |
|
785b5f7ca4 | |
|
8743ead5dc | |
|
e722b5f800 | |
|
2948f7ad76 | |
|
7c2a8f6951 | |
|
79788ab52e | |
|
96898c99e3 | |
|
bb485c031a | |
|
dfe6927676 | |
|
899272f208 | |
|
af6c529e4c | |
|
1c829502bf | |
|
5fb7af7441 | |
|
ffcb4d1f31 | |
|
d0a24940d6 | |
|
fd4a40d941 | |
|
6f7010771b | |
|
f5a2940cd7 | |
|
edc5cee43d | |
|
d810aa6a46 | |
|
5789c00945 | |
|
83865d39c9 | |
|
5b3b1e5fba | |
|
e0d0885e32 | |
|
862e56c5e6 | |
|
45b79b2320 | |
|
f5daf358a8 | |
|
9dbcffa35e | |
|
9271c9b6d6 | |
|
682c598e30 | |
|
9728b403b2 | |
|
6fc3712761 | |
|
a43a3e9cc9 | |
|
73f6943de7 | |
|
bf3cf4be0a | |
|
1c4db83b08 | |
|
1698305f77 | |
|
4397249c7f | |
|
0b25ac93c6 | |
|
b424607308 | |
|
f1b8d339ca | |
|
415ef43135 | |
|
65b8b1d5c3 | |
|
cd988f19be | |
|
6e093957e1 | |
|
853a7608ce | |
|
ef6226b5d1 | |
|
4325802e06 | |
|
0f2432c520 | |
|
e50916952a | |
|
2b0b732be5 | |
|
59bb5767ce | |
|
45e5f426b2 | |
|
580ac1a41c | |
|
beca50a1d7 | |
|
021a40ea10 | |
|
c90491e3d0 | |
|
c04ed6521f | |
|
783e2ea9eb | |
|
ca4aa24ee1 | |
|
abefa7a2e8 | |
|
690341e3f4 | |
|
316278ad48 | |
|
8e10e572a5 | |
|
db060d62c5 | |
|
056311120e | |
|
2645767c05 | |
|
1439a5cc64 | |
|
1345d4b1f2 | |
|
540b4e080f | |
|
ca55b11d6f | |
|
d5e199b983 | |
|
00553644cd | |
|
6b6b8363a9 | |
|
17a65b2fee | |
|
b054af2399 | |
|
2fdd3f99c0 | |
|
1648a248b6 | |
|
53c503e456 | |
|
c6a4045119 | |
|
90f5bdf7de | |
|
cc42323be5 | |
|
021193eac1 | |
|
700b4d4755 | |
|
25600e8a34 | |
|
6d8b491fc8 | |
|
13413b2670 | |
|
c9d0a39f34 | |
|
05021967b0 | |
|
f88fe3d441 | |
|
24f60b550e | |
|
06a0e025a1 | |
|
bac0f22481 | |
|
057be9488b | |
|
a739af5509 | |
|
ede60037a8 | |
|
387e82c9ac | |
|
e3f74faefb | |
|
e953ae18dc | |
|
5adfb0779b | |
|
0c8056356e | |
|
afc58b6806 | |
|
8bbeec5192 | |
|
f4c3b91b96 | |
|
9baade5ba7 | |
|
85702e7795 | |
|
ef4ab7bf19 | |
|
57208bd390 | |
|
078cc33a9f | |
|
eab57fb9c3 | |
|
3d34d8fcd5 | |
|
7ae8cc7f81 | |
|
97ac75d00e | |
|
2963c1688f | |
|
26451fab3b | |
|
8437e1520d | |
|
b7f461350b | |
|
0b56f09e88 | |
|
02dadfd16d | |
|
db8a2577c7 | |
|
73466b6a5a | |
|
390a972e57 | |
|
5120491073 | |
|
81f33c0259 | |
|
57f8d1ae02 | |
|
7c9ef11254 | |
|
660ad9f3fd | |
|
ed658e6bb1 | |
|
8f451b85cd | |
|
51520ff578 | |
|
d896b4f6ea | |
|
d2f8784180 | |
|
f9bbcf1cae | |
|
644ca35fec | |
|
1dd0e63924 | |
|
1e0fad1feb | |
|
95a7b39de0 | |
|
6f07aad390 | |
|
c120f9134a | |
|
f7c5f690a7 | |
|
192b9e006b | |
|
68ad71b1b3 | |
|
e1273742fd | |
|
491c2cb59d | |
|
a6ac532850 | |
|
46f68d6917 | |
|
6827808a1a | |
|
b66e992faa | |
|
e6570a929a | |
|
e27a9e2482 | |
|
d52002a772 | |
|
4d09a593a3 | |
|
48f2f49d5f | |
|
a7b2de01d2 | |
|
c6e99d3379 | |
|
09aa2aca7a | |
|
eabd20bef8 | |
|
205065a16b | |
|
cea79ca764 | |
|
386d678853 | |
|
23b904bf1d | |
|
5242551eae | |
|
853583395e | |
|
b15b42d547 | |
|
d76e4739b9 | |
|
d0a8fbbb66 | |
|
412c7ea576 | |
|
d29800da24 | |
|
12a05ad6f9 | |
|
901f1b39c5 | |
|
6b1d254e2f | |
|
5e2d4c818c | |
|
8f9bbae903 | |
|
284e2a0ef4 | |
|
886c57a606 | |
|
3dc39ea81c | |
|
51ce64d41e | |
|
9b45bef17a | |
|
1bd7ebf866 | |
|
710afdcca5 | |
|
b00a28c796 | |
|
218cff3953 | |
|
dd780036c9 | |
|
792e43003e | |
|
ad9e52bba6 | |
|
35a621595f | |
|
09da177952 | |
|
bd68550e12 | |
|
a80f1a7f30 | |
|
6456d51cbf | |
|
6524f6e7a1 | |
|
a161319271 | |
|
5f88b30add | |
|
e922335ba0 | |
|
e9b5e2c2b3 | |
|
99590a1e08 | |
|
6d1e442451 | |
|
aca28ae6e4 | |
|
f64fbb947d | |
|
745bfb7043 | |
|
4a04c7c564 | |
|
4e7371c0d6 | |
|
4c397057ee | |
|
a32227f30a | |
|
741227f3e0 | |
|
769ce5e6d3 | |
|
cd568c41b5 | |
|
1d5f7abac4 | |
|
e567c77e4e | |
|
afcc5f58f2 | |
|
fb06ebc63d | |
|
48289ec1ac | |
|
f33fc78bb3 | |
|
3e98646d7c | |
|
7c0529b228 | |
|
faf265e79b | |
|
a85f25d3df | |
|
1d934cc5ee | |
|
9fae8c424c | |
|
a5326a8593 | |
|
3e7cf9b826 | |
|
c656221973 | |
|
8a8ac50087 | |
|
ac427afbfb | |
|
0578f12ea9 | |
|
76a9b2381d | |
|
99009ca9e1 | |
|
bfb2a978ad | |
|
548b612b10 | |
|
45b7c6839f | |
|
43193209cb | |
|
7a0f3eb249 | |
|
ca4a5f3acb | |
|
5c24abe4a5 | |
|
cc59b4b0c5 | |
|
dec38eb800 | |
|
2df56b63ba | |
|
71c1da1f5a | |
|
973f8fb116 | |
|
9174311a21 | |
|
0bac43111e | |
|
2f82afbf55 | |
|
4ac647a5b7 | |
|
7cc9c9c5f4 | |
|
c65a2dfeea | |
|
7e85148a00 | |
|
aeb528929f | |
|
eafea0dfd4 | |
|
18f215617b | |
|
a3fee6d598 | |
|
b193e2d4d9 | |
|
8f84ba5fae | |
|
59501e89e5 | |
|
d359ea7ac7 | |
|
0ef6f98a1e | |
|
da9c442ac2 | |
|
6e4174e7aa | |
|
bd6d19fa4c | |
|
f946d7f231 | |
|
cf455f2e34 | |
|
0fb73e6996 | |
|
b9d51e6dbd | |
|
487160c5e2 | |
|
8453cc2284 | |
|
3537367401 | |
|
dfe26820d9 | |
|
2ef5c36eb9 | |
|
8760b2140d | |
|
946bb84409 | |
|
c62e9f289c | |
|
7889652c39 | |
|
e556517da4 | |
|
08598bb835 | |
|
3cd042cef7 | |
|
79fe985110 | |
|
87e8161dbf | |
|
4994c9fbc1 | |
|
67efdaff6f | |
|
5d1264edb5 | |
|
f31d3136cb | |
|
405ad76290 | |
|
53d2778ed2 | |
|
48e5acf24c | |
|
0f0f0e67a8 | |
|
1f3bbaeb34 | |
|
88b127debb | |
|
502a4b1d86 | |
|
43b2fa5e48 | |
|
fb7ca174e9 | |
|
497cf1b332 | |
|
e2d3f8b251 | |
|
a2c0d5e38f | |
|
06cbd0684a | |
|
b270f945c4 | |
|
d5fe633a97 | |
|
2f67f33029 | |
|
a04d1cd7ac | |
|
3d6578a417 | |
|
3abab5bb1e | |
|
2bd67d3f2b | |
|
5b98a20ac0 | |
|
63a3bd6b0c | |
|
d19c53f628 | |
|
3dbcc711d1 | |
|
978d40fa32 | |
|
4be070b871 | |
|
6766f3b451 | |
|
576cd7e331 | |
|
9fcf51dae3 | |
|
d3eb70b366 | |
|
c60315edc8 | |
|
ca012e2b71 | |
|
864d031e5f | |
|
2922cce102 | |
|
9d242c54f0 | |
|
922b4f608b | |
|
d94f317e65 | |
|
ed0c8a22b0 | |
|
a980b894e0 | |
|
f48e3bd8fd | |
|
5048e38508 | |
|
2fe47bbfdf | |
|
b396dce1c6 | |
|
a18b51b479 | |
|
7a05f4856c | |
|
38d76cc407 | |
|
5453088b02 | |
|
5475da4475 | |
|
ebabb47510 | |
|
e846e29458 | |
|
6d639f3bcd | |
|
cdbbca0fb0 | |
|
6a0e0dedeb | |
|
ccf956a0f3 | |
|
7fd2c7cbb3 | |
|
5fefee34b7 | |
|
43b99c65b6 | |
|
3e0c3e04bb | |
|
7bd4886a79 | |
|
ebbf83aa10 | |
|
c0f9593aad | |
|
2ce27a70fb | |
|
92603f3295 | |
|
1cacafa5fc | |
|
1b8d307a6d | |
|
564b0b5c7e | |
|
93055b0c79 | |
|
cad8850c11 | |
|
b59fffa14e | |
|
0191d6b4cc | |
|
4e1d1216dc | |
|
842c6d78c4 | |
|
d293039e5d | |
|
b00625bffc | |
|
32b8e78f6c | |
|
7c6a117523 | |
|
26bd330f18 | |
|
0296f7acf0 | |
|
a7dba5331b | |
|
0084acaa37 |
|
@ -1 +1 @@
|
|||
build/libgit2/
|
||||
build/
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
labels: ["dependencies"]
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
groups:
|
||||
go-deps:
|
||||
patterns:
|
||||
- "*"
|
||||
allow:
|
||||
- dependency-type: "direct"
|
||||
ignore:
|
||||
# Kubernetes deps are updated by fluxcd/pkg
|
||||
- dependency-name: "k8s.io/*"
|
||||
- dependency-name: "sigs.k8s.io/*"
|
||||
- dependency-name: "github.com/go-logr/*"
|
||||
# Flux APIs are updated at release time
|
||||
- dependency-name: "github.com/fluxcd/image-automation-controller/api"
|
||||
- dependency-name: "github.com/fluxcd/image-reflector-controller/api"
|
||||
- dependency-name: "github.com/fluxcd/source-controller/api"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
labels: ["area/ci", "dependencies"]
|
||||
groups:
|
||||
ci:
|
||||
patterns:
|
||||
- "*"
|
||||
schedule:
|
||||
interval: "monthly"
|
|
@ -0,0 +1,14 @@
|
|||
# Configuration file to declaratively configure labels
|
||||
# Ref: https://github.com/EndBug/label-sync#Config-files
|
||||
|
||||
- name: area/git
|
||||
description: Git related issues and pull requests
|
||||
color: '#863faf'
|
||||
- name: area/kyaml
|
||||
description: YAML patching related issues and pull requests
|
||||
color: '#86dbf2'
|
||||
|
||||
# TODO: enable this when we have a release/v1.0.x branch
|
||||
#- name: backport:release/v1.0.x
|
||||
# description: To be backported to release/v1.0.x
|
||||
# color: '#ffd700'
|
|
@ -0,0 +1,31 @@
|
|||
name: backport
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [closed, labeled]
|
||||
|
||||
jobs:
|
||||
pull-request:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
if: github.event.pull_request.state == 'closed' && github.event.pull_request.merged && (github.event_name != 'labeled' || startsWith('backport:', github.event.label.name))
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Create backport PRs
|
||||
uses: korthout/backport-action@436145e922f9561fc5ea157ff406f21af2d6b363 # v3.2.0
|
||||
# xref: https://github.com/korthout/backport-action#inputs
|
||||
with:
|
||||
# Use token to allow workflows to be triggered for the created PR
|
||||
github_token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
# Match labels with a pattern `backport:<target-branch>`
|
||||
label_pattern: '^backport:([^ ]+)$'
|
||||
# A bit shorter pull-request title than the default
|
||||
pull_title: '[${target_branch}] ${pull_title}'
|
||||
# Simpler PR description than default
|
||||
pull_description: |-
|
||||
Automated backport to `${target_branch}`, triggered by a label in #${pull_number}.
|
|
@ -2,8 +2,7 @@ name: build
|
|||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
branches: [ 'main', 'release/**' ]
|
||||
|
||||
permissions:
|
||||
contents: read # for actions/checkout to fetch code
|
||||
|
@ -13,66 +12,32 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: 1.19.x
|
||||
- name: Restore go cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
# the ff is mounted into the container as ~/go/pkg/mod
|
||||
path: /home/runner/work/_temp/_github_home/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
go-version: 1.24.x
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Run tests
|
||||
run: make test
|
||||
- name: Verify
|
||||
run: make verify
|
||||
|
||||
kind-linux-arm64:
|
||||
# Hosted on Equinix
|
||||
# Docs: https://github.com/fluxcd/flux2/tree/main/.github/runners
|
||||
runs-on: [self-hosted, Linux, ARM64, equinix]
|
||||
runs-on:
|
||||
group: "ARM64"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: 1.19.x
|
||||
- name: Run tests
|
||||
run: make test
|
||||
env:
|
||||
# Temporarily disabling -race for arm64 as our GitHub action
|
||||
# runners don't seem to like it.
|
||||
#
|
||||
# We should reenable go test -race for arm64 runners once the
|
||||
# current issue is resolved.
|
||||
GO_TEST_ARGS: ""
|
||||
SKIP_COSIGN_VERIFICATION: true
|
||||
- name: Verify
|
||||
run: make verify
|
||||
|
||||
# Runs 'make test' on macos-11 to assure development environment for
|
||||
# contributors using MacOS.
|
||||
darwin-amd64:
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19.x
|
||||
- name: Restore Go cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /home/runner/work/_temp/_github_home/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
go-version: 1.24.x
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Run tests
|
||||
run: make test
|
||||
env:
|
||||
|
|
|
@ -1,30 +1,23 @@
|
|||
name: CIFuzz
|
||||
name: fuzz
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
branches: [ 'main', 'release/**' ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
contents: read # for actions/checkout to fetch code
|
||||
|
||||
jobs:
|
||||
Fuzzing:
|
||||
smoketest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19.x
|
||||
- name: Restore Go cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /home/runner/work/_temp/_github_home/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
- name: Smoke test Fuzzers
|
||||
run: make fuzz-smoketest
|
||||
env:
|
||||
SKIP_COSIGN_VERIFICATION: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: 1.24.x
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Smoke test Fuzzers
|
||||
run: make fuzz-smoketest
|
||||
|
|
|
@ -14,18 +14,15 @@ jobs:
|
|||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
with:
|
||||
platforms: all
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
- name: Setup Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
buildkitd-flags: "--debug"
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
- name: Build multi-arch container image
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
|
||||
with:
|
||||
push: false
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
|
|
|
@ -7,22 +7,29 @@ on:
|
|||
inputs:
|
||||
tag:
|
||||
description: 'image tag prefix'
|
||||
default: 'rc'
|
||||
default: 'preview'
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: write # needed to write releases
|
||||
id-token: write # needed for keyless signing
|
||||
packages: write # needed for ghcr access
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CONTROLLER: ${{ github.event.repository.name }}
|
||||
|
||||
jobs:
|
||||
build-push:
|
||||
release:
|
||||
outputs:
|
||||
hashes: ${{ steps.slsa.outputs.hashes }}
|
||||
image_url: ${{ steps.slsa.outputs.image_url }}
|
||||
image_digest: ${{ steps.slsa.outputs.image_digest }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # for creating the GitHub release.
|
||||
id-token: write # for creating OIDC tokens for signing.
|
||||
packages: write # for pushing and signing container images.
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Kustomize
|
||||
uses: fluxcd/pkg/actions/kustomize@main
|
||||
- name: Prepare
|
||||
|
@ -32,27 +39,27 @@ jobs:
|
|||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
VERSION=${GITHUB_REF/refs\/tags\//}
|
||||
fi
|
||||
echo ::set-output name=BUILD_DATE::$(date -u +'%Y-%m-%dT%H:%M:%SZ')
|
||||
echo ::set-output name=VERSION::${VERSION}
|
||||
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
|
||||
echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
- name: Setup Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: fluxcdbot
|
||||
password: ${{ secrets.GHCR_TOKEN }}
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
username: fluxcdbot
|
||||
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
||||
- name: Generate images meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||
with:
|
||||
images: |
|
||||
fluxcd/${{ env.CONTROLLER }}
|
||||
|
@ -60,8 +67,11 @@ jobs:
|
|||
tags: |
|
||||
type=raw,value=${{ steps.prep.outputs.VERSION }}
|
||||
- name: Publish images
|
||||
uses: docker/build-push-action@v3
|
||||
id: build-push
|
||||
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
|
||||
with:
|
||||
sbom: true
|
||||
provenance: true
|
||||
push: true
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
|
@ -69,32 +79,82 @@ jobs:
|
|||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
- name: Check images
|
||||
run: |
|
||||
docker buildx imagetools inspect docker.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
|
||||
docker buildx imagetools inspect ghcr.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
|
||||
docker pull docker.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
|
||||
docker pull ghcr.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
|
||||
- uses: sigstore/cosign-installer@main
|
||||
- uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
|
||||
- name: Sign images
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: 1
|
||||
run: |
|
||||
cosign sign fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
|
||||
cosign sign ghcr.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
|
||||
cosign sign --yes fluxcd/${{ env.CONTROLLER }}@${{ steps.build-push.outputs.digest }}
|
||||
cosign sign --yes ghcr.io/fluxcd/${{ env.CONTROLLER }}@${{ steps.build-push.outputs.digest }}
|
||||
- name: Generate release artifacts
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
run: |
|
||||
mkdir -p config/release
|
||||
kustomize build ./config/crd > ./config/release/${{ env.CONTROLLER }}.crds.yaml
|
||||
kustomize build ./config/manager > ./config/release/${{ env.CONTROLLER }}.deployment.yaml
|
||||
echo '[CHANGELOG](https://github.com/fluxcd/${{ env.CONTROLLER }}/blob/main/CHANGELOG.md)' > ./config/release/notes.md
|
||||
- uses: anchore/sbom-action/download-syft@v0
|
||||
- uses: anchore/sbom-action/download-syft@e11c554f704a0b820cbf8c51673f6945e0731532 # v0.20.0
|
||||
- name: Create release and SBOM
|
||||
id: run-goreleaser
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: goreleaser/goreleaser-action@v3
|
||||
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
|
||||
with:
|
||||
version: latest
|
||||
args: release --release-notes=config/release/notes.md --rm-dist --skip-validate
|
||||
args: release --clean --skip=validate
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Generate SLSA metadata
|
||||
id: slsa
|
||||
env:
|
||||
ARTIFACTS: "${{ steps.run-goreleaser.outputs.artifacts }}"
|
||||
run: |
|
||||
hashes=$(echo -E $ARTIFACTS | jq --raw-output '.[] | {name, "digest": (.extra.Digest // .extra.Checksum)} | select(.digest) | {digest} + {name} | join(" ") | sub("^sha256:";"")' | base64 -w0)
|
||||
echo "hashes=$hashes" >> $GITHUB_OUTPUT
|
||||
|
||||
image_url=fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.version }}
|
||||
echo "image_url=$image_url" >> $GITHUB_OUTPUT
|
||||
|
||||
image_digest=${{ steps.build-push.outputs.digest }}
|
||||
echo "image_digest=$image_digest" >> $GITHUB_OUTPUT
|
||||
|
||||
release-provenance:
|
||||
needs: [release]
|
||||
permissions:
|
||||
actions: read # for detecting the Github Actions environment.
|
||||
id-token: write # for creating OIDC tokens for signing.
|
||||
contents: write # for uploading attestations to GitHub releases.
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
|
||||
with:
|
||||
provenance-name: "provenance.intoto.jsonl"
|
||||
base64-subjects: "${{ needs.release.outputs.hashes }}"
|
||||
upload-assets: true
|
||||
|
||||
dockerhub-provenance:
|
||||
needs: [release]
|
||||
permissions:
|
||||
actions: read # for detecting the Github Actions environment.
|
||||
id-token: write # for creating OIDC tokens for signing.
|
||||
packages: write # for uploading attestations.
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||
with:
|
||||
image: ${{ needs.release.outputs.image_url }}
|
||||
digest: ${{ needs.release.outputs.image_digest }}
|
||||
registry-username: fluxcdbot
|
||||
secrets:
|
||||
registry-password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
|
||||
|
||||
ghcr-provenance:
|
||||
needs: [release]
|
||||
permissions:
|
||||
actions: read # for detecting the Github Actions environment.
|
||||
id-token: write # for creating OIDC tokens for signing.
|
||||
packages: write # for uploading attestations.
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||
with:
|
||||
image: ghcr.io/${{ needs.release.outputs.image_url }}
|
||||
digest: ${{ needs.release.outputs.image_digest }}
|
||||
registry-username: fluxcdbot
|
||||
secrets:
|
||||
registry-password: ${{ secrets.GHCR_TOKEN }}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
name: Scan
|
||||
name: scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches: [ 'main', 'release/**' ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
branches: [ 'main', 'release/**' ]
|
||||
schedule:
|
||||
- cron: '18 10 * * 3'
|
||||
|
||||
|
@ -17,9 +17,10 @@ jobs:
|
|||
name: FOSSA
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Run FOSSA scan and upload build data
|
||||
uses: fossa-contrib/fossa-action@v1
|
||||
uses: fossa-contrib/fossa-action@3d2ef181b1820d6dcd1972f86a767d18167fa19b # v3.0.1
|
||||
with:
|
||||
# FOSSA Push-Only API Token
|
||||
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
|
||||
|
@ -29,17 +30,23 @@ jobs:
|
|||
name: CodeQL
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: 1.19.x
|
||||
go-version: 1.24.x
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
|
||||
with:
|
||||
languages: go
|
||||
# xref: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# xref: https://codeql.github.com/codeql-query-help/go/
|
||||
queries: security-and-quality
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
name: sync-labels
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- .github/labels.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
labels:
|
||||
name: Run sync
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: EndBug/label-sync@52074158190acb45f3077f9099fea818aa43f97a # v2.3.3
|
||||
with:
|
||||
# Configuration file
|
||||
config-file: |
|
||||
https://raw.githubusercontent.com/fluxcd/community/main/.github/standard-labels.yaml
|
||||
.github/labels.yaml
|
||||
# Strictly declarative
|
||||
delete-other-labels: true
|
|
@ -2,7 +2,7 @@ notes
|
|||
|
||||
# Thes are downloaded in the Makefile
|
||||
cache/*
|
||||
controllers/testdata/crds/*
|
||||
internal/controller/testdata/crds/*
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
|
@ -29,5 +29,8 @@ testbin
|
|||
*.swo
|
||||
*~
|
||||
|
||||
# Exclude all libgit2 related files
|
||||
# Exclude all build related files
|
||||
build/
|
||||
|
||||
# CRDs for fuzzing tests.
|
||||
internal/controller/testdata/crd
|
||||
|
|
|
@ -4,9 +4,26 @@ builds:
|
|||
- skip: true
|
||||
|
||||
release:
|
||||
prerelease: "true"
|
||||
extra_files:
|
||||
- glob: config/release/*.yaml
|
||||
prerelease: "true"
|
||||
header: |
|
||||
## Changelog
|
||||
|
||||
[{{.Tag}} changelog](https://github.com/fluxcd/{{.ProjectName}}/blob/{{.Tag}}/CHANGELOG.md)
|
||||
footer: |
|
||||
## Container images
|
||||
|
||||
- `docker.io/fluxcd/{{.ProjectName}}:{{.Tag}}`
|
||||
- `ghcr.io/fluxcd/{{.ProjectName}}:{{.Tag}}`
|
||||
|
||||
Supported architectures: `linux/amd64`, `linux/arm64` and `linux/arm/v7`.
|
||||
|
||||
The container images are built on GitHub hosted runners and are signed with cosign and GitHub OIDC.
|
||||
To verify the images and their provenance (SLSA level 3), please see the [security documentation](https://fluxcd.io/flux/security/).
|
||||
|
||||
changelog:
|
||||
disable: true
|
||||
|
||||
checksum:
|
||||
extra_files:
|
||||
|
@ -32,6 +49,7 @@ signs:
|
|||
certificate: "${artifact}.pem"
|
||||
args:
|
||||
- sign-blob
|
||||
- "--yes"
|
||||
- "--output-certificate=${certificate}"
|
||||
- "--output-signature=${signature}"
|
||||
- "${artifact}"
|
||||
|
|
1291
ATTRIBUTIONS.md
1291
ATTRIBUTIONS.md
File diff suppressed because it is too large
Load Diff
758
CHANGELOG.md
758
CHANGELOG.md
|
@ -1,5 +1,763 @@
|
|||
# Changelog
|
||||
|
||||
## 0.41.1
|
||||
|
||||
**Release date:** 2025-06-13
|
||||
|
||||
This patch release comes with a fix for the `knownhosts: key mismatch` error.
|
||||
|
||||
Fixes:
|
||||
- Fix `knownhosts key mismatch` regression bug
|
||||
[#923](https://github.com/fluxcd/image-automation-controller/pull/923)
|
||||
- Upgrade dependencies (includes `github.com/go-git/go-git/v5` `v5.16.2`)
|
||||
[#919](https://github.com/fluxcd/image-automation-controller/pull/919)
|
||||
|
||||
## 0.41.0
|
||||
|
||||
**Release date:** 2025-05-28
|
||||
|
||||
This prerelease comes with support for updating image digests, for
|
||||
configuring mTLS with HTTPS Git repositories, and with caching Git
|
||||
provider access tokens.
|
||||
|
||||
ImagePolicy API now supports reflecting image digests in-cluster. By
|
||||
integrating with this feature the ImageUpdateAutomation API can now
|
||||
update image digests in YAML manifests. See this
|
||||
[guide](https://fluxcd.io/flux/guides/image-update/#digest-pinning)
|
||||
for more details.
|
||||
|
||||
The controller now caches Git provider access tokens by default.
|
||||
This behavior can be disabled or fine-tuned by adjusting the
|
||||
token cache controller flags (see
|
||||
[docs](https://fluxcd.io/flux/components/image/options/#image-automation-flags)).
|
||||
The token cache also exposes metrics that are documented
|
||||
[here](https://fluxcd.io/flux/monitoring/metrics/#controller-metrics).
|
||||
|
||||
For configuring mTLS with HTTPS Git repositories see the GitRepository
|
||||
[docs](https://fluxcd.io/flux/components/source/gitrepositories/#secret-reference).
|
||||
|
||||
In addition, the Kubernetes dependencies have been updated to v1.33
|
||||
and various other controller dependencies have been updated to their latest
|
||||
version. The controller is now built with Go 1.24.
|
||||
|
||||
Fixes:
|
||||
- Fix tag parsing logic misinterpreting host:port registry url
|
||||
[#893](https://github.com/fluxcd/image-automation-controller/pull/893)
|
||||
|
||||
Improvements:
|
||||
- Update digest of latest image
|
||||
[#902](https://github.com/fluxcd/image-automation-controller/pull/902)
|
||||
- Introduce token cache for Git provider access tokens
|
||||
[#861](https://github.com/fluxcd/image-automation-controller/pull/861)
|
||||
[#895](https://github.com/fluxcd/image-automation-controller/pull/895)
|
||||
[#897](https://github.com/fluxcd/image-automation-controller/pull/897)
|
||||
- Add support for mutual TLS to Git HTTP/S operations
|
||||
[#886](https://github.com/fluxcd/image-automation-controller/pull/886)
|
||||
- Support for all recognized Kustomize config file names
|
||||
[#864](https://github.com/fluxcd/image-automation-controller/pull/864)
|
||||
- Various dependency updates
|
||||
[#916](https://github.com/fluxcd/image-automation-controller/pull/916)
|
||||
[#908](https://github.com/fluxcd/image-automation-controller/pull/908)
|
||||
[#900](https://github.com/fluxcd/image-automation-controller/pull/900)
|
||||
[#896](https://github.com/fluxcd/image-automation-controller/pull/896)
|
||||
[#898](https://github.com/fluxcd/image-automation-controller/pull/898)
|
||||
[#891](https://github.com/fluxcd/image-automation-controller/pull/891)
|
||||
[#890](https://github.com/fluxcd/image-automation-controller/pull/890)
|
||||
[#881](https://github.com/fluxcd/image-automation-controller/pull/881)
|
||||
[#880](https://github.com/fluxcd/image-automation-controller/pull/880)
|
||||
[#878](https://github.com/fluxcd/image-automation-controller/pull/878)
|
||||
[#856](https://github.com/fluxcd/image-automation-controller/pull/856)
|
||||
[#854](https://github.com/fluxcd/image-automation-controller/pull/854)
|
||||
[#852](https://github.com/fluxcd/image-automation-controller/pull/852)
|
||||
[#850](https://github.com/fluxcd/image-automation-controller/pull/850)
|
||||
|
||||
## 0.40.0
|
||||
|
||||
**Release date:** 2025-02-14
|
||||
|
||||
This prerelease comes with support for GitHub App authentication for GitHub
|
||||
repositories using the `.spec.provider` field by setting it to `github` on the
|
||||
`GitRepository` object associated with an `ImageUpdateAutomation` object.
|
||||
|
||||
In addition, the Kubernetes dependencies have been updated to v1.32.1
|
||||
and various other controller dependencies have been updated to their latest
|
||||
version.
|
||||
|
||||
Fixes:
|
||||
|
||||
Improvements:
|
||||
- Additional values for commit message template
|
||||
[#772](https://github.com/fluxcd/image-automation-controller/pull/772)
|
||||
- [RFC-007] Implement GitHub app authentication for git repositories in IAC
|
||||
[#780](https://github.com/fluxcd/image-automation-controller/pull/780)
|
||||
- Various dependency updates
|
||||
[#765](https://github.com/fluxcd/image-automation-controller/pull/765)
|
||||
[#766](https://github.com/fluxcd/image-automation-controller/pull/766)
|
||||
[#768](https://github.com/fluxcd/image-automation-controller/pull/768)
|
||||
[#769](https://github.com/fluxcd/image-automation-controller/pull/769)
|
||||
[#770](https://github.com/fluxcd/image-automation-controller/pull/770)
|
||||
[#774](https://github.com/fluxcd/image-automation-controller/pull/774)
|
||||
[#776](https://github.com/fluxcd/image-automation-controller/pull/776)
|
||||
[#777](https://github.com/fluxcd/image-automation-controller/pull/777)
|
||||
[#778](https://github.com/fluxcd/image-automation-controller/pull/778)
|
||||
[#782](https://github.com/fluxcd/image-automation-controller/pull/782)
|
||||
[#784](https://github.com/fluxcd/image-automation-controller/pull/784)
|
||||
[#787](https://github.com/fluxcd/image-automation-controller/pull/787)
|
||||
[#789](https://github.com/fluxcd/image-automation-controller/pull/789)
|
||||
[#790](https://github.com/fluxcd/image-automation-controller/pull/790)
|
||||
[#791](https://github.com/fluxcd/image-automation-controller/pull/791)
|
||||
[#794](https://github.com/fluxcd/image-automation-controller/pull/794)
|
||||
[#795](https://github.com/fluxcd/image-automation-controller/pull/795)
|
||||
[#798](https://github.com/fluxcd/image-automation-controller/pull/798)
|
||||
[#799](https://github.com/fluxcd/image-automation-controller/pull/799)
|
||||
[#801](https://github.com/fluxcd/image-automation-controller/pull/801)
|
||||
[#802](https://github.com/fluxcd/image-automation-controller/pull/802)
|
||||
[#804](https://github.com/fluxcd/image-automation-controller/pull/804)
|
||||
[#805](https://github.com/fluxcd/image-automation-controller/pull/805)
|
||||
[#806](https://github.com/fluxcd/image-automation-controller/pull/806)
|
||||
[#808](https://github.com/fluxcd/image-automation-controller/pull/808)
|
||||
[#815](https://github.com/fluxcd/image-automation-controller/pull/815)
|
||||
[#819](https://github.com/fluxcd/image-automation-controller/pull/819)
|
||||
[#821](https://github.com/fluxcd/image-automation-controller/pull/821)
|
||||
[#824](https://github.com/fluxcd/image-automation-controller/pull/824)
|
||||
[#826](https://github.com/fluxcd/image-automation-controller/pull/826)
|
||||
[#828](https://github.com/fluxcd/image-automation-controller/pull/828)
|
||||
[#831](https://github.com/fluxcd/image-automation-controller/pull/831)
|
||||
[#832](https://github.com/fluxcd/image-automation-controller/pull/832)
|
||||
[#835](https://github.com/fluxcd/image-automation-controller/pull/835)
|
||||
[#839](https://github.com/fluxcd/image-automation-controller/pull/839)
|
||||
[#840](https://github.com/fluxcd/image-automation-controller/pull/840)
|
||||
[#842](https://github.com/fluxcd/image-automation-controller/pull/842)
|
||||
[#843](https://github.com/fluxcd/image-automation-controller/pull/843)
|
||||
[#845](https://github.com/fluxcd/image-automation-controller/pull/845)
|
||||
|
||||
## 0.39.0
|
||||
|
||||
**Release date:** 2024-09-26
|
||||
|
||||
This prerelease comes with support for OIDC authentication for Azure DevOps
|
||||
Repositories using the `.spec.provider` field by setting it to `azure` on the
|
||||
`GitRepository` object associated with `ImageUpdateAutomation` object.
|
||||
|
||||
In addition, the Kubernetes dependencies have been updated to v1.31.1
|
||||
and various other controller dependencies have been updated to their latest
|
||||
version. The controller is now built with Go 1.23.
|
||||
|
||||
Fixes:
|
||||
- Fix incorrect use of format strings with the conditions package.
|
||||
[#711](https://github.com/fluxcd/image-automation-controller/pull/711)
|
||||
- Fix RBAC role generation for IAC
|
||||
[#745](https://github.com/fluxcd/image-automation-controller/pull/745)
|
||||
|
||||
Improvements:
|
||||
- [RFC-0007] Enable Azure OIDC for Azure DevOps Repository
|
||||
[#747](https://github.com/fluxcd/image-automation-controller/pull/747)
|
||||
- Build with Go 1.23
|
||||
[#736](https://github.com/fluxcd/image-automation-controller/pull/736)
|
||||
- Run ARM64 tests on GitHub runners
|
||||
[#696](https://github.com/fluxcd/image-automation-controller/pull/696)
|
||||
- Various dependency updates
|
||||
[#680](https://github.com/fluxcd/image-automation-controller/pull/680)
|
||||
[#683](https://github.com/fluxcd/image-automation-controller/pull/683)
|
||||
[#685](https://github.com/fluxcd/image-automation-controller/pull/685)
|
||||
[#690](https://github.com/fluxcd/image-automation-controller/pull/690)
|
||||
[#691](https://github.com/fluxcd/image-automation-controller/pull/691)
|
||||
[#693](https://github.com/fluxcd/image-automation-controller/pull/693)
|
||||
[#694](https://github.com/fluxcd/image-automation-controller/pull/694)
|
||||
[#695](https://github.com/fluxcd/image-automation-controller/pull/695)
|
||||
[#698](https://github.com/fluxcd/image-automation-controller/pull/698)
|
||||
[#697](https://github.com/fluxcd/image-automation-controller/pull/697)
|
||||
[#700](https://github.com/fluxcd/image-automation-controller/pull/700)
|
||||
[#701](https://github.com/fluxcd/image-automation-controller/pull/701)
|
||||
[#702](https://github.com/fluxcd/image-automation-controller/pull/702)
|
||||
[#703](https://github.com/fluxcd/image-automation-controller/pull/703)
|
||||
[#704](https://github.com/fluxcd/image-automation-controller/pull/704)
|
||||
[#706](https://github.com/fluxcd/image-automation-controller/pull/706)
|
||||
[#707](https://github.com/fluxcd/image-automation-controller/pull/707)
|
||||
[#708](https://github.com/fluxcd/image-automation-controller/pull/708)
|
||||
[#709](https://github.com/fluxcd/image-automation-controller/pull/709)
|
||||
[#712](https://github.com/fluxcd/image-automation-controller/pull/712)
|
||||
[#710](https://github.com/fluxcd/image-automation-controller/pull/710)
|
||||
[#714](https://github.com/fluxcd/image-automation-controller/pull/714)
|
||||
[#716](https://github.com/fluxcd/image-automation-controller/pull/716)
|
||||
[#718](https://github.com/fluxcd/image-automation-controller/pull/718)
|
||||
[#719](https://github.com/fluxcd/image-automation-controller/pull/719)
|
||||
[#720](https://github.com/fluxcd/image-automation-controller/pull/720)
|
||||
[#724](https://github.com/fluxcd/image-automation-controller/pull/724)
|
||||
[#722](https://github.com/fluxcd/image-automation-controller/pull/722)
|
||||
[#727](https://github.com/fluxcd/image-automation-controller/pull/727)
|
||||
[#726](https://github.com/fluxcd/image-automation-controller/pull/726)
|
||||
[#728](https://github.com/fluxcd/image-automation-controller/pull/728)
|
||||
[#729](https://github.com/fluxcd/image-automation-controller/pull/729)
|
||||
[#730](https://github.com/fluxcd/image-automation-controller/pull/730)
|
||||
[#731](https://github.com/fluxcd/image-automation-controller/pull/731)
|
||||
[#732](https://github.com/fluxcd/image-automation-controller/pull/732)
|
||||
[#734](https://github.com/fluxcd/image-automation-controller/pull/734)
|
||||
[#733](https://github.com/fluxcd/image-automation-controller/pull/733)
|
||||
[#737](https://github.com/fluxcd/image-automation-controller/pull/737)
|
||||
[#738](https://github.com/fluxcd/image-automation-controller/pull/738)
|
||||
[#739](https://github.com/fluxcd/image-automation-controller/pull/739)
|
||||
[#740](https://github.com/fluxcd/image-automation-controller/pull/740)
|
||||
[#741](https://github.com/fluxcd/image-automation-controller/pull/741)
|
||||
[#742](https://github.com/fluxcd/image-automation-controller/pull/742)
|
||||
[#743](https://github.com/fluxcd/image-automation-controller/pull/743)
|
||||
[#748](https://github.com/fluxcd/image-automation-controller/pull/748)
|
||||
[#750](https://github.com/fluxcd/image-automation-controller/pull/750)
|
||||
[#752](https://github.com/fluxcd/image-automation-controller/pull/752)
|
||||
[#755](https://github.com/fluxcd/image-automation-controller/pull/755)
|
||||
[#757](https://github.com/fluxcd/image-automation-controller/pull/757)
|
||||
[#759](https://github.com/fluxcd/image-automation-controller/pull/759)
|
||||
|
||||
## 0.38.0
|
||||
|
||||
**Release date:** 2024-05-06
|
||||
|
||||
This prerelease graduates the `ImageUpdateAutomation` API to v1beta2.
|
||||
|
||||
### `image.toolkit.fluxcd.io/v1beta2`
|
||||
|
||||
After upgrading the controller to v0.38.0, please update the
|
||||
`ImageUpdateAutomation` **Custom Resources** in Git by replacing
|
||||
`image.toolkit.fluxcd.io/v1beta1` with `image.toolkit.fluxcd.io/v1beta2` in all
|
||||
YAML manifests. Bumping the API version in manifests can be done gradually. It
|
||||
is advised not to delay this procedure as the `v1beta1` version will be removed
|
||||
after 6 months.
|
||||
|
||||
### Highlights
|
||||
|
||||
#### New API specification format
|
||||
|
||||
[The specification for the `v1beta2`
|
||||
API](https://github.com/fluxcd/image-automation-controller/tree/v0.38.0/docs/spec/v1beta2)
|
||||
has been written in a new format with the aim to be more valuable to a user.
|
||||
Featuring separate sections with examples, and information on how to write and
|
||||
work with them.
|
||||
|
||||
#### New template data `Changed` and deprecation of `Updated`
|
||||
|
||||
A new Git commit message template data named `Changed` is introduced to replace
|
||||
`Updated` template data. `Changed` is designed to accommodate for all the types
|
||||
of updates made by ImaegUpdateAutomation, unlike only full image reference
|
||||
updates captured by `Updated`. The message template can now be used to render
|
||||
the old and new values for better presentation of the updates. For example:
|
||||
|
||||
```
|
||||
Automation: default/test-update-auto
|
||||
|
||||
- File: foo-deployment.yaml
|
||||
- Object: Deployment/default/foo
|
||||
Changes:
|
||||
- 2.2.2 -> 5.0.3
|
||||
|
||||
- File: podinfo-deployment.yaml
|
||||
- Object: Deployment/default/infopod
|
||||
Changes:
|
||||
- v1.0 -> 5.0.3
|
||||
- Object: Deployment/default/podinfo
|
||||
Changes:
|
||||
- ghcr.io/stefanprodan/podinfo:4.0.6 -> ghcr.io/stefanprodan/podinfo:5.0.3
|
||||
- bar -> ghcr.io/stefanprodan/podinfo
|
||||
- 4.0.6 -> 5.0.3
|
||||
```
|
||||
|
||||
`Updated` template data is deprecated, but is still available in the message
|
||||
template, for existing users. It is recommended to migrate to `Changed` template
|
||||
data. See the new API specification docs for details about the new template
|
||||
data.
|
||||
|
||||
#### ImagePolicy selector support
|
||||
|
||||
`ImageUpdateAutomation` now supports selecting `ImagePolicies` using label
|
||||
selectors in the new field `.spec.policySelector`. For example:
|
||||
|
||||
```yaml
|
||||
apiVersion: image.toolkit.fluxcd.io/v1beta2
|
||||
kind: ImageUpdateAutomation
|
||||
metadata:
|
||||
name: update-app
|
||||
spec:
|
||||
policySelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/component: foo
|
||||
app.kubernetes.io/instance: bar
|
||||
...
|
||||
```
|
||||
|
||||
See the new API specification docs for details and more examples.
|
||||
|
||||
#### short-circuit reconciliations
|
||||
|
||||
For same push branch and checkout branch, the controller now checks with the
|
||||
remote Git repository if there's any new commit and the image policies for any
|
||||
new latest image before performing full reconciliation, otherwise the
|
||||
reconciliation returns early. This helps avoid cloning the Git repository every
|
||||
reconciliation even when there is no new change.
|
||||
|
||||
#### Enhancements in events and notifications
|
||||
|
||||
The events and notifications have been improved to provide better information.
|
||||
Notifications are sent for: initial successful reconciliation, update
|
||||
pushes, failures and successful failure recovery.
|
||||
|
||||
|
||||
In addition, the controller is now built with Go 1.22, the Kubernetes
|
||||
dependencies have been updated to v1.30.0, and various other dependencies have
|
||||
been updated to their latest version.
|
||||
|
||||
### Full changelog
|
||||
|
||||
Improvements:
|
||||
- Deprecate v1beta1 API
|
||||
[#677](https://github.com/fluxcd/image-automation-controller/pull/677)
|
||||
- Update source-controller API to v1.3.0
|
||||
[#676](https://github.com/fluxcd/image-automation-controller/pull/676)
|
||||
- Update dependencies to Kubernetes 1.30
|
||||
[#670](https://github.com/fluxcd/image-automation-controller/pull/670)
|
||||
- ImageUpdateAutomation v1beta2 API with refactored controller
|
||||
[#647](https://github.com/fluxcd/image-automation-controller/pull/647)
|
||||
- Update dependencies to Kustomize v5.4.0
|
||||
[#662](https://github.com/fluxcd/image-automation-controller/pull/662)
|
||||
- Update dependencies to Go 1.22 and Kubernetes 1.29.3
|
||||
[#661](https://github.com/fluxcd/image-automation-controller/pull/661)
|
||||
- Add tests for getExtFromSchema
|
||||
[#658](https://github.com/fluxcd/image-automation-controller/pull/658)
|
||||
- Introduce ResultV2 for update results
|
||||
[#642](https://github.com/fluxcd/image-automation-controller/pull/642)
|
||||
- updating controller-gen to v0.14.0
|
||||
[#649](https://github.com/fluxcd/image-automation-controller/pull/649)
|
||||
- Add predicates for GitRepo and ImagePolicy watches
|
||||
[#639](https://github.com/fluxcd/image-automation-controller/pull/639)
|
||||
- adding tests for update accept function
|
||||
[#636](https://github.com/fluxcd/image-automation-controller/pull/636)
|
||||
- Various dependency updates
|
||||
[#637](https://github.com/fluxcd/image-automation-controller/pull/637)
|
||||
[#652](https://github.com/fluxcd/image-automation-controller/pull/652)
|
||||
[#660](https://github.com/fluxcd/image-automation-controller/pull/660)
|
||||
[#668](https://github.com/fluxcd/image-automation-controller/pull/668)
|
||||
[#665](https://github.com/fluxcd/image-automation-controller/pull/665)
|
||||
[#666](https://github.com/fluxcd/image-automation-controller/pull/666)
|
||||
[#673](https://github.com/fluxcd/image-automation-controller/pull/673)
|
||||
|
||||
## 0.37.1
|
||||
|
||||
**Release date:** 2024-02-01
|
||||
|
||||
This prerelease comes with an update to the Kubernetes dependencies to
|
||||
v1.28.6 and various other dependencies have been updated to their latest version
|
||||
to patch upstream CVEs.
|
||||
|
||||
In addition, the controller is now built with Go 1.21.
|
||||
|
||||
Improvements:
|
||||
- ci: Enable dependabot gomod updates
|
||||
[#627](https://github.com/fluxcd/image-automation-controller/pull/627)
|
||||
- Update Go to 1.21
|
||||
[#625](https://github.com/fluxcd/image-automation-controller/pull/625)
|
||||
- Various dependency updates
|
||||
[#635](https://github.com/fluxcd/image-automation-controller/pull/635)
|
||||
[#632](https://github.com/fluxcd/image-automation-controller/pull/632)
|
||||
[#630](https://github.com/fluxcd/image-automation-controller/pull/630)
|
||||
[#631](https://github.com/fluxcd/image-automation-controller/pull/631)
|
||||
[#629](https://github.com/fluxcd/image-automation-controller/pull/629)
|
||||
[#626](https://github.com/fluxcd/image-automation-controller/pull/626)
|
||||
[#623](https://github.com/fluxcd/image-automation-controller/pull/623)
|
||||
[#622](https://github.com/fluxcd/image-automation-controller/pull/622)
|
||||
[#618](https://github.com/fluxcd/image-automation-controller/pull/618)
|
||||
[#615](https://github.com/fluxcd/image-automation-controller/pull/615)
|
||||
|
||||
## 0.37.0
|
||||
|
||||
**Release date:** 2023-12-11
|
||||
|
||||
This prerelease comes with updates to the controller's YAML parser and fixes
|
||||
all the issues with YAML multi-line strings being flattened.
|
||||
|
||||
In addition, the controller dependencies have been updated to Kubernetes 1.28
|
||||
and the container base image has been updated to Alpine 3.19.
|
||||
|
||||
Improvements:
|
||||
- Remove dependence on `kustomize/setters2`
|
||||
[#604](https://github.com/fluxcd/image-automation-controller/pull/604)
|
||||
- Update dependencies to Kubernetes v1.28
|
||||
[#605](https://github.com/fluxcd/image-automation-controller/pull/605)
|
||||
- Update Git dependencies
|
||||
[#607](https://github.com/fluxcd/image-automation-controller/pull/607)
|
||||
- build: update Alpine to 3.19
|
||||
[#610](https://github.com/fluxcd/image-automation-controller/pull/610)
|
||||
- Update Kustomize to v5.3.0
|
||||
[#611](https://github.com/fluxcd/image-automation-controller/pull/611)
|
||||
- Update dependencies
|
||||
[#613](https://github.com/fluxcd/image-automation-controller/pull/613)
|
||||
|
||||
## 0.36.1
|
||||
|
||||
**Release date:** 2023-09-18
|
||||
|
||||
This prerelease fixes the push branch reported in the logs and status under
|
||||
certain circumstances.
|
||||
|
||||
It also upgrades the version of github.com/cyphar/filepath-securejoin that fixes
|
||||
[GHSA-6xv5-86q9-7xr8](https://github.com/advisories/GHSA-6xv5-86q9-7xr8).
|
||||
Even though the Flux controllers are not affected by this vulnerability
|
||||
since they don't run on Windows nodes, this bump will keep security scanners silent.
|
||||
|
||||
Fixes:
|
||||
- Fix bad link in docs
|
||||
[#578](https://github.com/fluxcd/image-automation-controller/pull/578)
|
||||
- bump github.com/cyphar/filepath-securejoin
|
||||
[#584](https://github.com/fluxcd/image-automation-controller/pull/584)
|
||||
- Fix push branch reporting when is equal to checkout branch
|
||||
[#581](https://github.com/fluxcd/image-automation-controller/pull/581)
|
||||
- Upgrade github.com/fluxcd/pkg/{git,git/gogit}
|
||||
[#586](https://github.com/fluxcd/image-automation-controller/pull/586)
|
||||
|
||||
## 0.36.0
|
||||
|
||||
**Release date:** 2023-08-23
|
||||
|
||||
This prerelease introduces a new field `.spec.git.push.refspec` to the
|
||||
ImageUpdateAutomation API. This field can be used to make the controller push
|
||||
the commits it makes using a specific refspec. It also improves compatibility
|
||||
with Gerrit. For more details, check the [documentation](https://github.com/fluxcd/image-automation-controller/blob/v0.36.0/docs/spec/v1beta1/imageupdateautomations.md#gerrit).
|
||||
|
||||
Additionally, a new field `.spec.git.push.options` has been added to the
|
||||
ImageUpdateAutomation API for specifying the [push options](https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt)
|
||||
to be sent to the Git server when performing a push operation.
|
||||
|
||||
From this release on, the controller also stops exporting an object's metrics
|
||||
as soon as the object has been deleted.
|
||||
|
||||
Lastly, it adds support for using the referred GitRepository's
|
||||
`.spec.proxySecretRef` field for all remote Git operations related to the
|
||||
object.
|
||||
|
||||
Fixes:
|
||||
- Fix links in API docs
|
||||
[#573](https://github.com/fluxcd/image-automation-controller/pull/573)
|
||||
|
||||
Improvements:
|
||||
- git: add push.refspec to push using a refspec
|
||||
[#514](https://github.com/fluxcd/image-automation-controller/pull/514)
|
||||
- Handle delete before adding finalizer
|
||||
[#564](https://github.com/fluxcd/image-automation-controller/pull/564)
|
||||
- add support for specified proxy in source GitRepository
|
||||
[#565](https://github.com/fluxcd/image-automation-controller/pull/565)
|
||||
- Delete stale metrics on object delete
|
||||
[#570](https://github.com/fluxcd/image-automation-controller/pull/570)
|
||||
- Update dependencies
|
||||
[#571](https://github.com/fluxcd/image-automation-controller/pull/571)
|
||||
- add support for specifying push options
|
||||
[#577](https://github.com/fluxcd/image-automation-controller/pull/577)
|
||||
|
||||
## 0.35.0
|
||||
|
||||
**Release date:** 2023-07-04
|
||||
|
||||
This prerelease comes with support for Kubernetes v1.27.3 and updates to the
|
||||
controller's dependencies.
|
||||
It has better error reporting for situations where the Git repository deploy key
|
||||
does not have write acceess. Additionally, it improves support for
|
||||
Git >= v2.41.0.
|
||||
Furthermore, a bug related to GPG commit signing that could cause panics has
|
||||
been fixed.
|
||||
|
||||
Starting with this version, the build, release and provenance portions of the
|
||||
Flux project supply chain [provisionally meet SLSA Build Level 3](https://fluxcd.io/flux/security/slsa-assessment/).
|
||||
|
||||
Fixes:
|
||||
- Check if GPG private key is nil before decrypting
|
||||
[#534](https://github.com/fluxcd/image-automation-controller/pull/534)
|
||||
|
||||
Improvements:
|
||||
- Align `go.mod` version with Kubernetes (Go 1.20)
|
||||
[#545](https://github.com/fluxcd/image-automation-controller/pull/545)
|
||||
- Update Git packages
|
||||
[#549](https://github.com/fluxcd/image-automation-controller/pull/549)
|
||||
- Update go-git to unreleased v5.8.0
|
||||
[#553](https://github.com/fluxcd/image-automation-controller/pull/553)
|
||||
- Update Go dependencies
|
||||
[#554](https://github.com/fluxcd/image-automation-controller/pull/554)
|
||||
|
||||
## 0.34.1
|
||||
|
||||
**Release date:** 2023-06-01
|
||||
|
||||
This prerelease fixes a regression introduced in `v0.34.0` where
|
||||
support for Git servers that exclusively use v2 of the wire protocol like Azure
|
||||
Devops and AWS CodeCommit was broken.
|
||||
Furthermore, the reconciler now errors out if it fails to get the signing entity
|
||||
to be used for Git commit signing.
|
||||
|
||||
Fixes:
|
||||
- Return signing entity parsing error
|
||||
[#527](https://github.com/fluxcd/image-automation-controller/pull/527)
|
||||
- Set controller package name
|
||||
[#529](https://github.com/fluxcd/image-automation-controller/pull/529)
|
||||
- Bump `fluxcd/pkg/git/gogit` to v0.12.0
|
||||
[#530](https://github.com/fluxcd/image-automation-controller/pull/530)
|
||||
|
||||
## 0.34.0
|
||||
|
||||
**Release date:** 2023-05-29
|
||||
|
||||
This prerelease comes with support for Kubernetes v1.27 and updates to the
|
||||
controller's dependencies.
|
||||
|
||||
Improvements:
|
||||
- Update controller-runtime, Kubernetes and kyaml dependencies
|
||||
[#518](https://github.com/fluxcd/image-automation-controller/pull/518)
|
||||
- Drop go-git fork in favor of go-git v5.7.0
|
||||
[#519](https://github.com/fluxcd/image-automation-controller/pull/519)
|
||||
- Update workflows and enable dependabot
|
||||
[#520](https://github.com/fluxcd/image-automation-controller/pull/520)
|
||||
- build(deps): bump github/codeql-action from 2.3.3 to 2.3.4
|
||||
[#521](https://github.com/fluxcd/image-automation-controller/pull/521)
|
||||
- Update source-controller to v1.0.0-rc.4
|
||||
[#523](https://github.com/fluxcd/image-automation-controller/pull/523)
|
||||
|
||||
## 0.33.1
|
||||
|
||||
**Release date:** 2023-05-12
|
||||
|
||||
This prerelease comes with updates to the controller dependencies
|
||||
to patch CVE-2023-1732.
|
||||
|
||||
In addition, the controller base image has been updated to Alpine 3.18.
|
||||
|
||||
Improvements:
|
||||
- Update Alpine to 3.18
|
||||
[#513](https://github.com/fluxcd/image-automation-controller/pull/513)
|
||||
- Update dependencies
|
||||
[#516](https://github.com/fluxcd/image-automation-controller/pull/516)
|
||||
- build(deps): bump github.com/cloudflare/circl from 1.3.2 to 1.3.3
|
||||
[#515](https://github.com/fluxcd/image-automation-controller/pull/515)
|
||||
|
||||
## 0.33.0
|
||||
|
||||
**Release date:** 2023-05-09
|
||||
|
||||
This prerelease comes with support for signing commits using password protected OpenPGP keys.
|
||||
|
||||
In addition, the controller dependencies have been updated to their latest
|
||||
versions.
|
||||
|
||||
Improvements:
|
||||
- Add support for commit signing PGP key passphrases
|
||||
[#510](https://github.com/fluxcd/image-automation-controller/pull/510)
|
||||
- Update dependencies
|
||||
[#511](https://github.com/fluxcd/image-automation-controller/pull/511)
|
||||
|
||||
## 0.32.0
|
||||
|
||||
**Release date:** 2023-03-31
|
||||
|
||||
This prerelease updates the dependencies to their latest versions.
|
||||
|
||||
In addition, the controller now supports horizontal scaling using sharding based on a label selector.
|
||||
|
||||
### Highlights
|
||||
|
||||
#### API Changes
|
||||
|
||||
This prerelease is only compatible with `GitRepository` API version `v1`, first shipped with
|
||||
[source-controller](https://github.com/fluxcd/source-controller) v1.0.0-rc.1.
|
||||
|
||||
#### Sharding
|
||||
|
||||
Starting with this release, the controller can be configured with `--watch-label-selector`, after
|
||||
which only objects with this label will be reconciled by the controller.
|
||||
|
||||
This allows for horizontal scaling, where image-automation-controller can be deployed multiple times
|
||||
with a unique label selector which is used as the sharding key.
|
||||
|
||||
### Full Changelog
|
||||
|
||||
Improvements:
|
||||
- move `controllers` to `internal/controllers`
|
||||
[#500](https://github.com/fluxcd/image-automation-controller/pull/500)
|
||||
- Add reconciler sharding capability based on label selector
|
||||
[#504](https://github.com/fluxcd/image-automation-controller/pull/504)
|
||||
- Update dependencies and GitRepository API to v1
|
||||
[#505](https://github.com/fluxcd/image-automation-controller/pull/505)
|
||||
- bump google.golang.org/protobuf from 1.29.0 to 1.29.1
|
||||
[#506](https://github.com/fluxcd/image-automation-controller/pull/506)
|
||||
|
||||
## 0.31.0
|
||||
|
||||
**Release date:** 2023-03-08
|
||||
|
||||
This release updates to Go version the controller is build with to 1.20, and
|
||||
updates the dependencies to their latest versions.
|
||||
|
||||
In addition, `klog` is now configured to log using the same logger as the rest
|
||||
of the controller (providing a consistent log format).
|
||||
|
||||
Improvements:
|
||||
- Update Go to 1.20
|
||||
[#492](https://github.com/fluxcd/image-automation-controller/pull/492)
|
||||
- Update dependencies
|
||||
[#494](https://github.com/fluxcd/image-automation-controller/pull/494)
|
||||
[#496](https://github.com/fluxcd/image-automation-controller/pull/496)
|
||||
- Use `logger.SetLogger` to also configure `klog`
|
||||
[#495](https://github.com/fluxcd/image-automation-controller/pull/495)
|
||||
|
||||
## 0.30.0
|
||||
|
||||
**Release date:** 2023-02-16
|
||||
|
||||
This prerelease comes with support for the new `ImagePolicy` v1beta2 API. See
|
||||
[image-reflector-controller
|
||||
changelog](https://github.com/fluxcd/image-reflector-controller/blob/v0.25.0/CHANGELOG.md#0250)
|
||||
for instructions about updating the `ImagePolicies`.
|
||||
|
||||
:warning: Also note that the autologin flags in image-reflector-controller have
|
||||
been deprecated and the `ImageRepositories` that use the autologin feature have
|
||||
to be updated with `.spec.provider` field to continue working. Refer the
|
||||
[docs](https://fluxcd.io/flux/components/image/imagerepositories/#provider) for
|
||||
details and examples.
|
||||
|
||||
In addition, the controller dependencies have been updated to their latest
|
||||
versions.
|
||||
|
||||
Improvements:
|
||||
- Set rate limiter option in test reconcilers
|
||||
[#475](https://github.com/fluxcd/image-automation-controller/pull/475)
|
||||
- Update dependencies
|
||||
[#486](https://github.com/fluxcd/image-automation-controller/pull/486)
|
||||
- Update image-reflector API to v1beta2
|
||||
[#485](https://github.com/fluxcd/image-automation-controller/pull/485)
|
||||
|
||||
## 0.29.0
|
||||
|
||||
**Release date:** 2023-02-01
|
||||
|
||||
This prerelease disables caching of Secrets and ConfigMaps to improve memory
|
||||
usage. To opt-out from this behavior, start the controller with:
|
||||
`--feature-gates=CacheSecretsAndConfigMaps=true`.
|
||||
|
||||
In addition, the controller dependencies have been updated to Kubernetes
|
||||
v1.26.1 and controller-runtime v0.14.2. The controller base image has been
|
||||
updated to Alpine 3.17.
|
||||
|
||||
Improvements:
|
||||
- build: Enable SBOM and SLSA Provenance
|
||||
[#478](https://github.com/fluxcd/image-automation-controller/pull/478)
|
||||
- Disable caching of Secrets and ConfigMaps
|
||||
[#479](https://github.com/fluxcd/image-automation-controller/pull/479)
|
||||
- Update dependencies
|
||||
[#480](https://github.com/fluxcd/image-automation-controller/pull/480)
|
||||
|
||||
## 0.28.0
|
||||
|
||||
**Release date:** 2022-12-21
|
||||
|
||||
This prerelease removes all code references to `libgit2` and `git2go`, from
|
||||
this release onwards the controller will use `go-git` as the only git implementation.
|
||||
For more information, refer to version 0.27.0's changelog, which started `libgit2`'s
|
||||
deprecation process.
|
||||
|
||||
The feature gate `ForceGoGitImplementation` was removed, users passing it as their
|
||||
controller's startup args will need to remove it before upgrading.
|
||||
|
||||
Two new feature gates were introduced and are enabled by default:
|
||||
- `GitShallowClone`: enables the use of shallow clones when pulling source
|
||||
from Git repositories.
|
||||
- `GitAllBranchReferences`: enables users to toggle the download of all branch
|
||||
head references when push branches are configured.
|
||||
|
||||
To opt-out from the feature gates above, start the controller with:
|
||||
`--feature-gates=GitShallowClone=false,GitAllBranchReferences=false`.
|
||||
|
||||
Fixes:
|
||||
- Block the creation of empty commits
|
||||
[#470](https://github.com/fluxcd/image-automation-controller/pull/470)
|
||||
|
||||
Improvements:
|
||||
- Add GitShallowClone feature
|
||||
[#463](https://github.com/fluxcd/image-automation-controller/pull/463)
|
||||
- Add feature gate GitAllBranchReferences
|
||||
[#469](https://github.com/fluxcd/image-automation-controller/pull/469)
|
||||
- Remove libgit2 and git2go from codebase
|
||||
[#468](https://github.com/fluxcd/image-automation-controller/pull/468)
|
||||
- build: Link libgit2 via LIB_FUZZING_ENGINE
|
||||
[#465](https://github.com/fluxcd/image-automation-controller/pull/465)
|
||||
- build: Add postbuild script for fuzzing
|
||||
[#464](https://github.com/fluxcd/image-automation-controller/pull/464)
|
||||
- build: Fix cifuzz and improve fuzz tests' reliability
|
||||
[#462](https://github.com/fluxcd/image-automation-controller/pull/462)
|
||||
- Update dependencies
|
||||
[#471](https://github.com/fluxcd/image-automation-controller/pull/471)
|
||||
|
||||
## 0.27.0
|
||||
|
||||
**Release date:** 2022-11-21
|
||||
|
||||
This prerelease comes with a major refactoring of the controller's Git operations.
|
||||
The controller can now observe the field `spec.gitImplementation` instead of always
|
||||
ignoring it and using `libgit2`. The `go-git` implementation now supports all Git
|
||||
servers, including Azure DevOps, which previously was only supported by `libgit2`.
|
||||
|
||||
By default, the field `spec.gitImplementation` is ignored and the reconciliations
|
||||
will use `go-git`. To opt-out from this behaviour, and get the controller to
|
||||
honour the field `spec.gitImplementation`, start the controller with:
|
||||
`--feature-gates=ForceGoGitImplementation=false`.
|
||||
|
||||
This version initiates the soft deprecation of the `libgit2` implementation.
|
||||
The motivation for removing support for `libgit2` being:
|
||||
- Reliability: over the past months we managed to substantially reduce the
|
||||
issues users experienced, but there are still crashes happening when the controller
|
||||
runs over longer periods of time, or when under intense GC pressure.
|
||||
- Performance: due to the inherit nature of `libgit2` implementation, which
|
||||
is a C library called via CGO through `git2go`, it will never perform as well as
|
||||
a pure Go implementations. At scale, memory pressure insues which then triggers
|
||||
the reliability issues above.
|
||||
- Lack of Shallow Clone Support.
|
||||
- Maintainability: supporting two Git implementations is a big task, even more
|
||||
so when one of them is in a complete different tech stack. Given its nature, to
|
||||
support `libgit2`, we have to maintain an additional repository. Statically built
|
||||
`libgit2` libraries need to be cross-compiled for all our supported platforms.
|
||||
And a lot of "unnecessary" code has to be in place to make building, testing and
|
||||
fuzzing work seamlessly.
|
||||
|
||||
Users having any issues with `go-git` should report it to the Flux team,
|
||||
so any issues can be resolved before support for `libgit2` is completely
|
||||
removed from the codebase.
|
||||
|
||||
Starting from this version `ImageUpdateAutomation` objects with a `spec.PushBranch`
|
||||
specified will have the push branch refreshed automatically via force push.
|
||||
To opt-out from this behaviour, start the controller with:
|
||||
`--feature-gates=GitForcePushBranch=false`.
|
||||
|
||||
Improvements:
|
||||
- Refactor Git operations and introduce go-git support for Azure DevOps and AWS CodeCommit
|
||||
[#451](https://github.com/fluxcd/image-automation-controller/pull/451)
|
||||
- Add new ForceGoGitImplementation FeatureGate
|
||||
[#452](https://github.com/fluxcd/image-automation-controller/pull/452)
|
||||
- Add support for force push
|
||||
[#453](https://github.com/fluxcd/image-automation-controller/pull/453)
|
||||
- Use Flux Event API v1beta1
|
||||
[#455](https://github.com/fluxcd/image-automation-controller/pull/455)
|
||||
- Remove deprecated alpha APIs
|
||||
[#456](https://github.com/fluxcd/image-automation-controller/pull/456)
|
||||
- Remove nsswitch.conf creation
|
||||
[#458](https://github.com/fluxcd/image-automation-controller/pull/458)
|
||||
- Update Dependencies
|
||||
[#459](https://github.com/fluxcd/image-automation-controller/pull/459)
|
||||
[#460](https://github.com/fluxcd/image-automation-controller/pull/460)
|
||||
|
||||
## 0.26.1
|
||||
|
||||
**Release date:** 2022-10-21
|
||||
|
||||
This prerelease comes with dependency updates, including the fix for the upstream
|
||||
vulnerability CVE-2022-32149.
|
||||
|
||||
In addition, the controller dependencies have been updated to Kubernetes v1.25.3.
|
||||
|
||||
Improvements:
|
||||
- Update dependencies
|
||||
[#448](https://github.com/fluxcd/image-automation-controller/pull/448)
|
||||
|
||||
## 0.26.0
|
||||
|
||||
**Release date:** 2022-09-29
|
||||
|
|
|
@ -13,80 +13,7 @@ There are a number of dependencies required to be able to run the controller and
|
|||
- [Install Docker](https://docs.docker.com/engine/install/)
|
||||
- (Optional) [Install Kubebuilder](https://book.kubebuilder.io/quick-start.html#installation)
|
||||
|
||||
The dependency [libgit2](https://libgit2.org/) also needs to be installed to be able
|
||||
to run `image-automation-controller` or its test-suite locally (not in a container).
|
||||
|
||||
In case this dependency is not present on your system (at the expected
|
||||
version), the first invocation of a `make` target that requires the
|
||||
dependency will attempt to compile it locally to `hack/libgit2`. For this build
|
||||
to succeed ensure the following dependencies are present on your system:
|
||||
- [CMake](https://cmake.org/download/)
|
||||
- [OpenSSL 1.1](https://www.openssl.org/source/)
|
||||
- [LibSSH2](https://www.libssh2.org/)
|
||||
- [pkg-config](https://freedesktop.org/wiki/Software/pkg-config/)
|
||||
|
||||
|
||||
Triggering a manual build of the dependency is possible as well by running
|
||||
`make libgit2`. To enforce the build, for example if your system dependencies
|
||||
match but are not linked in a compatible way, append `LIBGIT2_FORCE=1` to the
|
||||
`make` command.
|
||||
|
||||
Follow the instructions below to install these dependencies to your system.
|
||||
|
||||
### macOS
|
||||
|
||||
```console
|
||||
$ # Ensure libgit2 dependencies are available
|
||||
$ brew install cmake openssl@1.1 libssh2 pkg-config
|
||||
$ LIBGIT2_FORCE=1 make libgit2
|
||||
```
|
||||
|
||||
#### openssl and pkg-config
|
||||
|
||||
You may see this message when trying to run a build:
|
||||
|
||||
```
|
||||
# pkg-config --cflags libgit2
|
||||
Package openssl was not found in the pkg-config search path.
|
||||
Perhaps you should add the directory containing `openssl.pc'
|
||||
to the PKG_CONFIG_PATH environment variable
|
||||
Package 'openssl', required by 'libgit2', not found
|
||||
pkg-config: exit status 1
|
||||
```
|
||||
|
||||
On some macOS systems `brew` will not link the pkg-config file for
|
||||
openssl into expected directory, because it clashes with a
|
||||
system-provided openssl. When installing it, or if you do
|
||||
|
||||
```console
|
||||
brew link openssl@1.1
|
||||
Warning: Refusing to link macOS provided/shadowed software: openssl@1.1
|
||||
If you need to have openssl@1.1 first in your PATH, run:
|
||||
echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.profile
|
||||
|
||||
For compilers to find openssl@1.1 you may need to set:
|
||||
export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
|
||||
export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"
|
||||
|
||||
For pkg-config to find openssl@1.1 you may need to set:
|
||||
export PKG_CONFIG_PATH="/usr/local/opt/openssl@1.1/lib/pkgconfig"
|
||||
```
|
||||
|
||||
.. it tells you (in the last part of the message) how to add `openssl`
|
||||
to your `PKG_CONFIG_PATH` so it can be found.
|
||||
|
||||
### Linux
|
||||
|
||||
```console
|
||||
$ # Ensure libgit2 dependencies are available
|
||||
$ pacman -S cmake openssl libssh2
|
||||
$ LIBGIT2_FORCE=1 make libgit2
|
||||
```
|
||||
|
||||
**Note:** Example shown is for Arch Linux, but likewise procedure can be
|
||||
followed using any other package manager, e.g. `apt`.
|
||||
|
||||
In addition to the above, the following dependencies are also used by some of the `make` targets:
|
||||
The following dependencies are also used by some of the `make` targets:
|
||||
|
||||
- `controller-gen` (v0.7.0)
|
||||
- `gen-crd-api-reference-docs` (v0.3.0)
|
||||
|
@ -97,7 +24,7 @@ If any of the above dependencies are not present on your system, the first invoc
|
|||
## How to run the test suite
|
||||
|
||||
Prerequisites:
|
||||
* Go >= 1.18
|
||||
* Go >= 1.24
|
||||
|
||||
You can run the test suite by simply doing
|
||||
|
||||
|
@ -169,18 +96,11 @@ Create a `.vscode/launch.json` file:
|
|||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"envFile": "${workspaceFolder}/build/.env",
|
||||
"program": "${workspaceFolder}/main.go"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Create the environment file containing details on how to load
|
||||
`libgit2` dependencies:
|
||||
```bash
|
||||
make env
|
||||
```
|
||||
|
||||
Start debugging by either clicking `Run` > `Start Debugging` or using
|
||||
the relevant shortcut.
|
||||
|
|
53
Dockerfile
53
Dockerfile
|
@ -1,15 +1,10 @@
|
|||
ARG BASE_VARIANT=alpine
|
||||
ARG GO_VERSION=1.19
|
||||
ARG XX_VERSION=1.1.2
|
||||
|
||||
ARG LIBGIT2_IMG=ghcr.io/fluxcd/golang-with-libgit2-only
|
||||
ARG LIBGIT2_TAG=v0.3.0
|
||||
|
||||
FROM ${LIBGIT2_IMG}:${LIBGIT2_TAG} AS libgit2-libs
|
||||
ARG GO_VERSION=1.24
|
||||
ARG XX_VERSION=1.6.1
|
||||
|
||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-${BASE_VARIANT} as gostable
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-${BASE_VARIANT} AS gostable
|
||||
|
||||
FROM gostable AS go-linux
|
||||
|
||||
|
@ -17,12 +12,18 @@ FROM gostable AS go-linux
|
|||
# These will be used at current arch to yield execute the cross compilations.
|
||||
FROM go-${TARGETOS} AS build-base
|
||||
|
||||
RUN apk add clang lld pkgconfig
|
||||
RUN apk add clang lld
|
||||
|
||||
COPY --from=xx / /
|
||||
|
||||
# build-go-mod can still be cached at build platform architecture.
|
||||
FROM build-base as build-go-mod
|
||||
# build can still be cached at build platform architecture.
|
||||
FROM build-base AS build
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# Some dependencies have to installed for the target platform:
|
||||
# https://github.com/tonistiigi/xx#go--cgo
|
||||
RUN xx-apk add musl-dev gcc
|
||||
|
||||
# Configure workspace
|
||||
WORKDIR /workspace
|
||||
|
@ -37,33 +38,20 @@ COPY go.sum go.sum
|
|||
# Cache modules
|
||||
RUN go mod download
|
||||
|
||||
# Build stage install per target platform
|
||||
# dependency and effectively cross compile the application.
|
||||
FROM build-go-mod as build
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
COPY --from=libgit2-libs /usr/local/ /usr/local/
|
||||
|
||||
# Some dependencies have to installed
|
||||
# for the target platform: https://github.com/tonistiigi/xx#go--cgo
|
||||
RUN xx-apk add musl-dev gcc clang lld
|
||||
|
||||
WORKDIR /workspace
|
||||
|
||||
# Copy source code
|
||||
COPY main.go main.go
|
||||
COPY controllers/ controllers/
|
||||
COPY pkg/ pkg/
|
||||
COPY internal/ internal/
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
ARG TARGETARCH
|
||||
|
||||
# Reasons why CGO is in use:
|
||||
# - The SHA1 implementation (sha1cd) used by go-git depends on CGO for
|
||||
# performance reasons. See: https://github.com/pjbgf/sha1cd/issues/15
|
||||
ENV CGO_ENABLED=1
|
||||
|
||||
RUN export LIBRARY_PATH="/usr/local/$(xx-info triple)" && \
|
||||
export PKG_CONFIG_PATH="/usr/local/$(xx-info triple)/lib/pkgconfig" && \
|
||||
export CGO_LDFLAGS="$(pkg-config --static --libs --cflags libgit2) -static -fuse-ld=lld" && \
|
||||
RUN export CGO_LDFLAGS="-static -fuse-ld=lld" && \
|
||||
xx-go build \
|
||||
-ldflags "-s -w" \
|
||||
-tags 'netgo,osusergo,static_build' \
|
||||
|
@ -72,19 +60,14 @@ RUN export LIBRARY_PATH="/usr/local/$(xx-info triple)" && \
|
|||
# Ensure that the binary was cross-compiled correctly to the target platform.
|
||||
RUN xx-verify --static /image-automation-controller
|
||||
|
||||
FROM alpine:3.16
|
||||
FROM alpine:3.21
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
RUN apk --no-cache add ca-certificates \
|
||||
&& update-ca-certificates
|
||||
|
||||
# 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
|
||||
|
||||
# Copy over binary from build
|
||||
COPY --from=build /image-automation-controller /usr/local/bin/
|
||||
COPY ATTRIBUTIONS.md /
|
||||
|
||||
USER 65534:65534
|
||||
ENTRYPOINT [ "image-automation-controller" ]
|
||||
|
|
|
@ -9,7 +9,8 @@ from the main Flux v2 git repository, as listed in
|
|||
|
||||
In alphabetical order:
|
||||
|
||||
Paulo Gomes, Weaveworks <paulo.gomes@weave.works> (github: @pjbgf, slack: pjbgf)
|
||||
Dipti Pai, Microsoft <diptipai@microsoft.com> (github: @dipti-pai, slack: Dipti Pai)
|
||||
Paulo Gomes, SUSE <pjbgf@linux.com> (github: @pjbgf, slack: pjbgf)
|
||||
|
||||
Retired maintainers:
|
||||
|
||||
|
|
84
Makefile
84
Makefile
|
@ -6,10 +6,6 @@ TAG ?= latest
|
|||
# Produce CRDs that work back to Kubernetes 1.16
|
||||
CRD_OPTIONS ?= crd:crdVersions=v1
|
||||
|
||||
# Base image used to build the Go binary
|
||||
LIBGIT2_IMG ?= ghcr.io/fluxcd/golang-with-libgit2-only
|
||||
LIBGIT2_TAG ?= v0.3.0
|
||||
|
||||
# Allows for defining additional Docker buildx arguments,
|
||||
# e.g. '--push'.
|
||||
BUILD_ARGS ?=
|
||||
|
@ -37,26 +33,10 @@ REFLECTOR_VER ?= $(shell go list -m github.com/fluxcd/image-reflector-controller
|
|||
REPOSITORY_ROOT := $(shell git rev-parse --show-toplevel)
|
||||
BUILD_DIR := $(REPOSITORY_ROOT)/build
|
||||
|
||||
# Other dependency versions
|
||||
ENVTEST_BIN_VERSION ?= 1.19.2
|
||||
|
||||
# FUZZ_TIME defines the max amount of time, in Go Duration,
|
||||
# each fuzzer should run for.
|
||||
FUZZ_TIME ?= 1m
|
||||
|
||||
# Caches libgit2 versions per tag, "forcing" rebuild only when needed.
|
||||
LIBGIT2_PATH := $(BUILD_DIR)/libgit2/$(LIBGIT2_TAG)
|
||||
LIBGIT2_LIB_PATH := $(LIBGIT2_PATH)/lib
|
||||
LIBGIT2_LIB64_PATH := $(LIBGIT2_PATH)/lib64
|
||||
LIBGIT2 := $(LIBGIT2_LIB_PATH)/libgit2.a
|
||||
|
||||
export CGO_ENABLED=1
|
||||
export PKG_CONFIG_PATH=$(LIBGIT2_LIB_PATH)/pkgconfig
|
||||
export LIBRARY_PATH=$(LIBGIT2_LIB_PATH)
|
||||
export CGO_CFLAGS=-I$(LIBGIT2_PATH)/include -I$(LIBGIT2_PATH)/include/openssl
|
||||
export CGO_LDFLAGS=$(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) pkg-config --libs --static --cflags libgit2 2>/dev/null)
|
||||
|
||||
# The pkg-config command will yield warning messages until libgit2 is downloaded.
|
||||
ifeq ($(shell uname -s),Darwin)
|
||||
GO_STATIC_FLAGS=-ldflags "-s -w" -tags 'netgo,osusergo,static_build'
|
||||
endif
|
||||
|
@ -66,8 +46,8 @@ ifeq ($(shell uname -s),Linux)
|
|||
endif
|
||||
|
||||
# API (doc) generation utilities
|
||||
CONTROLLER_GEN_VERSION ?= v0.7.0
|
||||
GEN_API_REF_DOCS_VERSION ?= v0.3.0
|
||||
CONTROLLER_GEN_VERSION ?= v0.16.1
|
||||
GEN_API_REF_DOCS_VERSION ?= e327d0730470cbd61b06300f81c5fcf91c23c113
|
||||
|
||||
# If gobin not set, create one on ./build and add to path.
|
||||
ifeq (,$(shell go env GOBIN))
|
||||
|
@ -89,7 +69,7 @@ ifeq ($(shell uname -s),Darwin)
|
|||
ENVTEST_ARCH=amd64
|
||||
endif
|
||||
|
||||
TEST_CRDS := controllers/testdata/crds
|
||||
TEST_CRDS := internal/controller/testdata/crds
|
||||
|
||||
# Log level for `make run`
|
||||
LOG_LEVEL ?= info
|
||||
|
@ -129,17 +109,17 @@ ifeq ($(shell uname -s),Darwin)
|
|||
endif
|
||||
|
||||
KUBEBUILDER_ASSETS?="$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_KUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) -p path)"
|
||||
test: $(LIBGIT2) tidy test-api test_deps generate fmt vet manifests api-docs install-envtest ## Run tests
|
||||
test: tidy test-api test_deps generate fmt vet manifests api-docs install-envtest ## Run tests
|
||||
KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) \
|
||||
go test $(GO_STATIC_FLAGS) $(GO_TEST_ARGS) ./... -coverprofile cover.out
|
||||
|
||||
test-api: ## Run api tests
|
||||
cd api; go test $(GO_TEST_ARGS) ./... -coverprofile cover.out
|
||||
|
||||
manager: $(LIBGIT2) generate fmt vet ## Build manager binary
|
||||
manager: generate fmt vet ## Build manager binary
|
||||
go build -o $(BUILD_DIR)/bin/manager ./main.go
|
||||
|
||||
run: $(LIBGIT2) generate fmt vet manifests # Run against the configured Kubernetes cluster in ~/.kube/config
|
||||
run: generate fmt vet manifests # Run against the configured Kubernetes cluster in ~/.kube/config
|
||||
go run $(GO_STATIC_FLAGS) ./main.go --log-level=${LOG_LEVEL} --log-encoding=console
|
||||
|
||||
install: manifests ## Install CRDs into a cluster
|
||||
|
@ -159,20 +139,21 @@ dev-deploy: manifests
|
|||
rm -rf config/dev
|
||||
|
||||
manifests: controller-gen ## Generate manifests e.g. CRD, RBAC etc.
|
||||
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role paths="./..." output:crd:artifacts:config="config/crd/bases"
|
||||
cd api; $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role paths="./..." output:crd:artifacts:config="../config/crd/bases"
|
||||
|
||||
api-docs: gen-crd-api-reference-docs ## Generate API reference documentation
|
||||
$(GEN_CRD_API_REFERENCE_DOCS) -api-dir=./api/v1beta1 -config=./hack/api-docs/config.json -template-dir=./hack/api-docs/template -out-file=./docs/api/image-automation.md
|
||||
$(GEN_CRD_API_REFERENCE_DOCS) -api-dir=./api/v1beta2 -config=./hack/api-docs/config.json -template-dir=./hack/api-docs/template -out-file=./docs/api/v1beta2/image-automation.md
|
||||
|
||||
tidy: ## Run go mod tidy
|
||||
cd api; rm -f go.sum; go mod tidy -compat=1.18
|
||||
rm -f go.sum; go mod tidy -compat=1.18
|
||||
cd api; rm -f go.sum; go mod tidy -compat=1.24
|
||||
rm -f go.sum; go mod tidy -compat=1.24
|
||||
|
||||
fmt: ## Run go fmt against code
|
||||
go fmt ./...
|
||||
cd api; go fmt ./...
|
||||
|
||||
vet: $(LIBGIT2) ## Run go vet against code
|
||||
vet: ## Run go vet against code
|
||||
go vet ./...
|
||||
cd api; go vet ./...
|
||||
|
||||
|
@ -182,8 +163,6 @@ generate: controller-gen ## Generate code
|
|||
|
||||
docker-build: ## Build the Docker image
|
||||
docker buildx build \
|
||||
--build-arg LIBGIT2_IMG=$(LIBGIT2_IMG) \
|
||||
--build-arg LIBGIT2_TAG=$(LIBGIT2_TAG) \
|
||||
--platform=$(BUILD_PLATFORMS) \
|
||||
-t $(IMG):$(TAG) \
|
||||
$(BUILD_ARGS) .
|
||||
|
@ -198,21 +177,13 @@ docker-deploy: ## Set the Docker image in-cluster
|
|||
CONTROLLER_GEN = $(GOBIN)/controller-gen
|
||||
.PHONY: controller-gen
|
||||
controller-gen: ## Download controller-gen locally if necessary.
|
||||
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.8.0)
|
||||
|
||||
libgit2: $(LIBGIT2) ## Detect or download libgit2 library
|
||||
|
||||
COSIGN = $(GOBIN)/cosign
|
||||
$(LIBGIT2):
|
||||
$(call go-install-tool,$(COSIGN),github.com/sigstore/cosign/cmd/cosign@latest)
|
||||
|
||||
IMG=$(LIBGIT2_IMG) TAG=$(LIBGIT2_TAG) PATH=$(PATH):$(GOBIN) ./hack/install-libraries.sh
|
||||
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_GEN_VERSION))
|
||||
|
||||
# Find or download gen-crd-api-reference-docs
|
||||
GEN_CRD_API_REFERENCE_DOCS = $(GOBIN)/gen-crd-api-reference-docs
|
||||
.PHONY: gen-crd-api-reference-docs
|
||||
gen-crd-api-reference-docs:
|
||||
$(call go-install-tool,$(GEN_CRD_API_REFERENCE_DOCS),github.com/ahmetb/gen-crd-api-reference-docs@v0.3.0)
|
||||
$(call go-install-tool,$(GEN_CRD_API_REFERENCE_DOCS),github.com/ahmetb/gen-crd-api-reference-docs@$(GEN_API_REF_DOCS_VERSION))
|
||||
|
||||
ENVTEST_ASSETS_DIR=$(BUILD_DIR)/testbin
|
||||
ENVTEST_KUBERNETES_VERSION?=latest
|
||||
|
@ -227,7 +198,7 @@ setup-envtest: ## Download envtest-setup locally if necessary.
|
|||
$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest)
|
||||
|
||||
# Build fuzzers used by oss-fuzz.
|
||||
fuzz-build: $(LIBGIT2)
|
||||
fuzz-build:
|
||||
rm -rf $(shell pwd)/build/fuzz/
|
||||
mkdir -p $(shell pwd)/build/fuzz/out/
|
||||
|
||||
|
@ -268,20 +239,7 @@ endef
|
|||
update-attributions:
|
||||
./hack/update-attributions.sh
|
||||
|
||||
verify: update-attributions fmt
|
||||
ifneq ($(shell grep -o 'LIBGIT2_IMG ?= \w.*' Makefile | cut -d ' ' -f 3):$(shell grep -o 'LIBGIT2_TAG ?= \w.*' Makefile | cut -d ' ' -f 3), \
|
||||
$(shell grep -o "LIBGIT2_IMG=\w.*" Dockerfile | cut -d'=' -f2):$(shell grep -o "LIBGIT2_TAG=\w.*" Dockerfile | cut -d'=' -f2))
|
||||
@{ \
|
||||
echo "LIBGIT2_IMG and LIBGIT2_TAG must match in both Makefile and Dockerfile"; \
|
||||
exit 1; \
|
||||
}
|
||||
endif
|
||||
ifneq ($(shell grep -o 'LIBGIT2_TAG ?= \w.*' Makefile | cut -d ' ' -f 3), $(shell grep -o "LIBGIT2_TAG=.*" tests/fuzz/oss_fuzz_build.sh | sed 's;LIBGIT2_TAG="$${LIBGIT2_TAG:-;;g' | sed 's;}";;g'))
|
||||
@{ \
|
||||
echo "LIBGIT2_TAG must match in both Makefile and tests/fuzz/oss_fuzz_build.sh"; \
|
||||
exit 1; \
|
||||
}
|
||||
endif
|
||||
verify:
|
||||
ifneq (, $(shell git status --porcelain --untracked-files=no))
|
||||
@{ \
|
||||
echo "working directory is dirty:"; \
|
||||
|
@ -293,15 +251,3 @@ endif
|
|||
.PHONY: help
|
||||
help: ## Display this help menu
|
||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
||||
|
||||
# Creates an env file that can be used to load all image-automation-controller's
|
||||
# dependencies this is handy when you want to run adhoc debug sessions on tests or
|
||||
# start the controller in a new debug session.
|
||||
env: $(LIBGIT2)
|
||||
echo 'GO_ENABLED="1"' > $(BUILD_DIR)/.env
|
||||
echo 'PKG_CONFIG_PATH="$(PKG_CONFIG_PATH)"' >> $(BUILD_DIR)/.env
|
||||
echo 'LIBRARY_PATH="$(LIBRARY_PATH)"' >> $(BUILD_DIR)/.env
|
||||
echo 'CGO_CFLAGS="$(CGO_CFLAGS)"' >> $(BUILD_DIR)/.env
|
||||
echo 'CGO_LDFLAGS="$(CGO_LDFLAGS)"' >> $(BUILD_DIR)/.env
|
||||
echo 'KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS)' >> $(BUILD_DIR)/.env
|
||||
echo 'GIT_CONFIG_GLOBAL=/dev/null' >> $(BUILD_DIR)/.env
|
||||
|
|
9
PROJECT
9
PROJECT
|
@ -1,13 +1,10 @@
|
|||
domain: toolkit.fluxcd.io
|
||||
repo: github.com/fluxcd/image-automation-controller
|
||||
resources:
|
||||
- group: image
|
||||
kind: ImageUpdateAutomation
|
||||
version: v1alpha1
|
||||
- group: image
|
||||
kind: ImageUpdateAutomation
|
||||
version: v1alpha2
|
||||
- group: image
|
||||
kind: ImageUpdateAutomation
|
||||
version: v1beta1
|
||||
- group: image
|
||||
kind: ImageUpdateAutomation
|
||||
version: v1beta2
|
||||
version: "2"
|
||||
|
|
10
README.md
10
README.md
|
@ -21,11 +21,5 @@ guide](https://fluxcd.io/flux/guides/image-update/).
|
|||
|
||||
## How to work on it
|
||||
|
||||
The shared library `libgit2` needs to be installed to test or build
|
||||
locally. The version required corresponds to the version of git2go
|
||||
(which are Go bindings for libgit2), according to [this
|
||||
table](https://github.com/libgit2/git2go#which-go-version-to-use).
|
||||
|
||||
See
|
||||
https://github.com/fluxcd/source-controller/blob/main/DEVELOPMENT.md#installing-required-dependencies
|
||||
for instructions on how to install `libgit2`.
|
||||
For additional information on dependecies and how to contribute
|
||||
please refer to [DEVELOPMENT.md](DEVELOPMENT.md).
|
||||
|
|
37
api/go.mod
37
api/go.mod
|
@ -1,31 +1,32 @@
|
|||
module github.com/fluxcd/image-automation-controller/api
|
||||
|
||||
go 1.18
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/fluxcd/pkg/apis/meta v0.17.0
|
||||
github.com/fluxcd/source-controller/api v0.31.0
|
||||
k8s.io/apimachinery v0.25.3
|
||||
sigs.k8s.io/controller-runtime v0.13.0
|
||||
github.com/fluxcd/pkg/apis/meta v1.12.0
|
||||
github.com/fluxcd/source-controller/api v1.6.1
|
||||
k8s.io/apimachinery v0.33.0
|
||||
sigs.k8s.io/controller-runtime v0.21.0
|
||||
)
|
||||
|
||||
// Fix CVE-2022-32149
|
||||
replace golang.org/x/text => golang.org/x/text v0.4.0
|
||||
|
||||
require (
|
||||
github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/fluxcd/pkg/apis/acl v0.7.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/klog/v2 v2.70.1 // indirect
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
|
127
api/go.sum
127
api/go.sum
|
@ -1,106 +1,115 @@
|
|||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fluxcd/pkg/apis/acl v0.1.0 h1:EoAl377hDQYL3WqanWCdifauXqXbMyFuK82NnX6pH4Q=
|
||||
github.com/fluxcd/pkg/apis/acl v0.1.0/go.mod h1:zfEZzz169Oap034EsDhmCAGgnWlcWmIObZjYMusoXS8=
|
||||
github.com/fluxcd/pkg/apis/meta v0.17.0 h1:Y2dfo1syHZDb9Mexjr2SWdcj1FnxnRXm015hEnhl6wU=
|
||||
github.com/fluxcd/pkg/apis/meta v0.17.0/go.mod h1:GrOVzWXiu22XjLNgLLe2EBYhQPqZetes5SIADb4bmHE=
|
||||
github.com/fluxcd/source-controller/api v0.31.0 h1:4PZQt2XILTUZ/2JOVGzAIpNDXjx8n10skAhuBHa9tVw=
|
||||
github.com/fluxcd/source-controller/api v0.31.0/go.mod h1:XOf8hJB7jFcAKiOb8HVZcegkBeNSb4g0nxqnNjeVufg=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/fluxcd/pkg/apis/acl v0.7.0 h1:dMhZJH+g6ZRPjs4zVOAN9vHBd1DcavFgcIFkg5ooOE0=
|
||||
github.com/fluxcd/pkg/apis/acl v0.7.0/go.mod h1:uv7pXXR/gydiX4MUwlQa7vS8JONEDztynnjTvY3JxKQ=
|
||||
github.com/fluxcd/pkg/apis/meta v1.12.0 h1:XW15TKZieC2b7MN8VS85stqZJOx+/b8jATQ/xTUhVYg=
|
||||
github.com/fluxcd/pkg/apis/meta v1.12.0/go.mod h1:+son1Va60x2eiDcTwd7lcctbI6C+K3gM7R+ULmEq1SI=
|
||||
github.com/fluxcd/source-controller/api v1.6.1 h1:ZPTA9lNzBYHmwHfFX978qb8xVkdnQZHF1ggo6BoFm4w=
|
||||
github.com/fluxcd/source-controller/api v1.6.1/go.mod h1:ZJcAi0nemsnBxjVgmJl0WQzNvB0rMETxQMTdoFosmMw=
|
||||
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
|
||||
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
|
||||
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||
github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
|
||||
github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
k8s.io/api v0.25.0 h1:H+Q4ma2U/ww0iGB78ijZx6DRByPz6/733jIuFpX70e0=
|
||||
k8s.io/apimachinery v0.25.3 h1:7o9ium4uyUOM76t6aunP0nZuex7gDf8VGwkR5RcJnQc=
|
||||
k8s.io/apimachinery v0.25.3/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo=
|
||||
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
|
||||
k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ=
|
||||
k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4=
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
sigs.k8s.io/controller-runtime v0.13.0 h1:iqa5RNciy7ADWnIc8QxCbOX5FEKVR3uxVxKHRMc2WIQ=
|
||||
sigs.k8s.io/controller-runtime v0.13.0/go.mod h1:Zbz+el8Yg31jubvAEyglRZGdLAjplZl+PgtYNI6WNTI=
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU=
|
||||
k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM=
|
||||
k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ=
|
||||
k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
|
||||
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8=
|
||||
sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
||||
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
|
|
|
@ -1,210 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 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 v1alpha1
|
||||
|
||||
import (
|
||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
)
|
||||
|
||||
const ImageUpdateAutomationKind = "ImageUpdateAutomation"
|
||||
|
||||
// ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation
|
||||
type ImageUpdateAutomationSpec struct {
|
||||
// Checkout gives the parameters for cloning the git repository,
|
||||
// ready to make changes.
|
||||
// +required
|
||||
Checkout GitCheckoutSpec `json:"checkout"`
|
||||
|
||||
// Interval gives an lower bound for how often the automation
|
||||
// run should be attempted.
|
||||
// +required
|
||||
Interval metav1.Duration `json:"interval"`
|
||||
|
||||
// Update gives the specification for how to update the files in
|
||||
// the repository. This can be left empty, to use the default
|
||||
// value.
|
||||
// +kubebuilder:default={"strategy":"Setters"}
|
||||
Update *UpdateStrategy `json:"update,omitempty"`
|
||||
|
||||
// Commit specifies how to commit to the git repository.
|
||||
// +required
|
||||
Commit CommitSpec `json:"commit"`
|
||||
|
||||
// Push specifies how and where to push commits made by the
|
||||
// automation. If missing, commits are pushed (back) to
|
||||
// `.spec.checkout.branch`.
|
||||
// +optional
|
||||
Push *PushSpec `json:"push,omitempty"`
|
||||
|
||||
// Suspend tells the controller to not run this automation, until
|
||||
// it is unset (or set to false). Defaults to false.
|
||||
// +optional
|
||||
Suspend bool `json:"suspend,omitempty"`
|
||||
}
|
||||
|
||||
type GitCheckoutSpec struct {
|
||||
// GitRepositoryRef refers to the resource giving access details
|
||||
// to a git repository to update files in.
|
||||
// +required
|
||||
GitRepositoryRef meta.LocalObjectReference `json:"gitRepositoryRef"`
|
||||
// Branch gives the branch to clone from the git repository. If
|
||||
// `.spec.push` is not supplied, commits will also be pushed to
|
||||
// this branch.
|
||||
// +required
|
||||
Branch string `json:"branch"`
|
||||
}
|
||||
|
||||
// UpdateStrategyName is the type for names that go in
|
||||
// .update.strategy. NB the value in the const immediately below.
|
||||
// +kubebuilder:validation:Enum=Setters
|
||||
type UpdateStrategyName string
|
||||
|
||||
const (
|
||||
// UpdateStrategySetters is the name of the update strategy that
|
||||
// uses kyaml setters. NB the value in the enum annotation for the
|
||||
// type, above.
|
||||
UpdateStrategySetters UpdateStrategyName = "Setters"
|
||||
)
|
||||
|
||||
// UpdateStrategy is a union of the various strategies for updating
|
||||
// the Git repository. Parameters for each strategy (if any) can be
|
||||
// inlined here.
|
||||
type UpdateStrategy struct {
|
||||
// Strategy names the strategy to be used.
|
||||
// +required
|
||||
// +kubebuilder:default=Setters
|
||||
Strategy UpdateStrategyName `json:"strategy"`
|
||||
|
||||
// Path to the directory containing the manifests to be updated.
|
||||
// Defaults to 'None', which translates to the root path
|
||||
// of the GitRepositoryRef.
|
||||
// +optional
|
||||
Path string `json:"path,omitempty"`
|
||||
}
|
||||
|
||||
// CommitSpec specifies how to commit changes to the git repository
|
||||
type CommitSpec struct {
|
||||
// AuthorName gives the name to provide when making a commit
|
||||
// +required
|
||||
AuthorName string `json:"authorName"`
|
||||
// AuthorEmail gives the email to provide when making a commit
|
||||
// +required
|
||||
AuthorEmail string `json:"authorEmail"`
|
||||
// SigningKey provides the option to sign commits with a GPG key
|
||||
// +optional
|
||||
SigningKey *SigningKey `json:"signingKey,omitempty"`
|
||||
// MessageTemplate provides a template for the commit message,
|
||||
// into which will be interpolated the details of the change made.
|
||||
// +optional
|
||||
MessageTemplate string `json:"messageTemplate,omitempty"`
|
||||
}
|
||||
|
||||
// PushSpec specifies how and where to push commits.
|
||||
type PushSpec struct {
|
||||
// Branch specifies that commits should be pushed to the branch
|
||||
// named. The branch is created using `.spec.checkout.branch` as the
|
||||
// starting point, if it doesn't already exist.
|
||||
// +required
|
||||
Branch string `json:"branch"`
|
||||
}
|
||||
|
||||
// ImageUpdateAutomationStatus defines the observed state of ImageUpdateAutomation
|
||||
type ImageUpdateAutomationStatus struct {
|
||||
// LastAutomationRunTime records the last time the controller ran
|
||||
// this automation through to completion (even if no updates were
|
||||
// made).
|
||||
// +optional
|
||||
LastAutomationRunTime *metav1.Time `json:"lastAutomationRunTime,omitempty"`
|
||||
// LastPushCommit records the SHA1 of the last commit made by the
|
||||
// controller, for this automation object
|
||||
// +optional
|
||||
LastPushCommit string `json:"lastPushCommit,omitempty"`
|
||||
// LastPushTime records the time of the last pushed change.
|
||||
// +optional
|
||||
LastPushTime *metav1.Time `json:"lastPushTime,omitempty"`
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
// +optional
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
meta.ReconcileRequestStatus `json:",inline"`
|
||||
}
|
||||
|
||||
// SigningKey references a Kubernetes secret that contains a GPG keypair
|
||||
type SigningKey struct {
|
||||
// SecretRef holds the name to a secret that contains a 'git.asc' key
|
||||
// corresponding to the ASCII Armored file containing the GPG signing
|
||||
// keypair as the value. It must be in the same namespace as the
|
||||
// ImageUpdateAutomation.
|
||||
// +required
|
||||
SecretRef meta.LocalObjectReference `json:"secretRef,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
// GitNotAvailableReason is used for ConditionReady when the
|
||||
// automation run cannot proceed because the git repository is
|
||||
// missing or cannot be cloned.
|
||||
GitNotAvailableReason = "GitRepositoryNotAvailable"
|
||||
// NoStrategyReason is used for ConditionReady when the automation
|
||||
// run cannot proceed because there is no update strategy given in
|
||||
// the spec.
|
||||
NoStrategyReason = "MissingUpdateStrategy"
|
||||
)
|
||||
|
||||
// SetImageUpdateAutomationReadiness sets the ready condition with the given status, reason and message.
|
||||
func SetImageUpdateAutomationReadiness(auto *ImageUpdateAutomation, status metav1.ConditionStatus, reason, message string) {
|
||||
auto.Status.ObservedGeneration = auto.ObjectMeta.Generation
|
||||
newCondition := metav1.Condition{
|
||||
Type: meta.ReadyCondition,
|
||||
Status: status,
|
||||
Reason: reason,
|
||||
Message: message,
|
||||
}
|
||||
apimeta.SetStatusCondition(auto.GetStatusConditions(), newCondition)
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:printcolumn:name="Last run",type=string,JSONPath=`.status.lastAutomationRunTime`
|
||||
|
||||
// ImageUpdateAutomation is the Schema for the imageupdateautomations API
|
||||
type ImageUpdateAutomation struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec ImageUpdateAutomationSpec `json:"spec,omitempty"`
|
||||
Status ImageUpdateAutomationStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
func (auto *ImageUpdateAutomation) GetStatusConditions() *[]metav1.Condition {
|
||||
return &auto.Status.Conditions
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// ImageUpdateAutomationList contains a list of ImageUpdateAutomation
|
||||
type ImageUpdateAutomationList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []ImageUpdateAutomation `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&ImageUpdateAutomation{}, &ImageUpdateAutomationList{})
|
||||
}
|
|
@ -1,227 +0,0 @@
|
|||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2020 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.
|
||||
*/
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CommitSpec) DeepCopyInto(out *CommitSpec) {
|
||||
*out = *in
|
||||
if in.SigningKey != nil {
|
||||
in, out := &in.SigningKey, &out.SigningKey
|
||||
*out = new(SigningKey)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommitSpec.
|
||||
func (in *CommitSpec) DeepCopy() *CommitSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CommitSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GitCheckoutSpec) DeepCopyInto(out *GitCheckoutSpec) {
|
||||
*out = *in
|
||||
out.GitRepositoryRef = in.GitRepositoryRef
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitCheckoutSpec.
|
||||
func (in *GitCheckoutSpec) DeepCopy() *GitCheckoutSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GitCheckoutSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageUpdateAutomation) DeepCopyInto(out *ImageUpdateAutomation) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageUpdateAutomation.
|
||||
func (in *ImageUpdateAutomation) DeepCopy() *ImageUpdateAutomation {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImageUpdateAutomation)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ImageUpdateAutomation) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageUpdateAutomationList) DeepCopyInto(out *ImageUpdateAutomationList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]ImageUpdateAutomation, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageUpdateAutomationList.
|
||||
func (in *ImageUpdateAutomationList) DeepCopy() *ImageUpdateAutomationList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImageUpdateAutomationList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ImageUpdateAutomationList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageUpdateAutomationSpec) DeepCopyInto(out *ImageUpdateAutomationSpec) {
|
||||
*out = *in
|
||||
out.Checkout = in.Checkout
|
||||
out.Interval = in.Interval
|
||||
if in.Update != nil {
|
||||
in, out := &in.Update, &out.Update
|
||||
*out = new(UpdateStrategy)
|
||||
**out = **in
|
||||
}
|
||||
in.Commit.DeepCopyInto(&out.Commit)
|
||||
if in.Push != nil {
|
||||
in, out := &in.Push, &out.Push
|
||||
*out = new(PushSpec)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageUpdateAutomationSpec.
|
||||
func (in *ImageUpdateAutomationSpec) DeepCopy() *ImageUpdateAutomationSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImageUpdateAutomationSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageUpdateAutomationStatus) DeepCopyInto(out *ImageUpdateAutomationStatus) {
|
||||
*out = *in
|
||||
if in.LastAutomationRunTime != nil {
|
||||
in, out := &in.LastAutomationRunTime, &out.LastAutomationRunTime
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
if in.LastPushTime != nil {
|
||||
in, out := &in.LastPushTime, &out.LastPushTime
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]v1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
out.ReconcileRequestStatus = in.ReconcileRequestStatus
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageUpdateAutomationStatus.
|
||||
func (in *ImageUpdateAutomationStatus) DeepCopy() *ImageUpdateAutomationStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImageUpdateAutomationStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSpec) DeepCopyInto(out *PushSpec) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSpec.
|
||||
func (in *PushSpec) DeepCopy() *PushSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PushSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SigningKey) DeepCopyInto(out *SigningKey) {
|
||||
*out = *in
|
||||
out.SecretRef = in.SecretRef
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SigningKey.
|
||||
func (in *SigningKey) DeepCopy() *SigningKey {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SigningKey)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *UpdateStrategy) DeepCopyInto(out *UpdateStrategy) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpdateStrategy.
|
||||
func (in *UpdateStrategy) DeepCopy() *UpdateStrategy {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(UpdateStrategy)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 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 v1alpha2
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/scheme"
|
||||
)
|
||||
|
||||
var (
|
||||
// GroupVersion is group version used to register these objects
|
||||
GroupVersion = schema.GroupVersion{Group: "image.toolkit.fluxcd.io", Version: "v1alpha2"}
|
||||
|
||||
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
|
||||
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
|
||||
|
||||
// AddToScheme adds the types in this group-version to the given scheme.
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 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 v1alpha2
|
||||
|
||||
// SourceReference contains enough information to let you locate the
|
||||
// typed, referenced source object.
|
||||
type SourceReference struct {
|
||||
// API version of the referent
|
||||
// +optional
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
|
||||
// Kind of the referent
|
||||
// +kubebuilder:validation:Enum=GitRepository
|
||||
// +kubebuilder:default=GitRepository
|
||||
// +required
|
||||
Kind string `json:"kind"`
|
||||
|
||||
// Name of the referent
|
||||
// +required
|
||||
Name string `json:"name"`
|
||||
}
|
|
@ -18,7 +18,7 @@ package v1beta1
|
|||
|
||||
import (
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
)
|
||||
|
||||
type GitSpec struct {
|
||||
|
@ -85,6 +85,20 @@ type PushSpec struct {
|
|||
// Branch specifies that commits should be pushed to the branch
|
||||
// named. The branch is created using `.spec.checkout.branch` as the
|
||||
// starting point, if it doesn't already exist.
|
||||
// +required
|
||||
Branch string `json:"branch"`
|
||||
// +optional
|
||||
Branch string `json:"branch,omitempty"`
|
||||
|
||||
// Refspec specifies the Git Refspec to use for a push operation.
|
||||
// If both Branch and Refspec are provided, then the commit is pushed
|
||||
// to the branch and also using the specified refspec.
|
||||
// For more details about Git Refspecs, see:
|
||||
// https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
|
||||
// +optional
|
||||
Refspec string `json:"refspec,omitempty"`
|
||||
|
||||
// Options specifies the push options that are sent to the Git
|
||||
// server when performing a push operation. For details, see:
|
||||
// https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt
|
||||
// +optional
|
||||
Options map[string]string `json:"options,omitempty"`
|
||||
}
|
||||
|
|
|
@ -133,9 +133,9 @@ func SetImageUpdateAutomationReadiness(auto *ImageUpdateAutomation, status metav
|
|||
apimeta.SetStatusCondition(auto.GetStatusConditions(), newCondition)
|
||||
}
|
||||
|
||||
//+kubebuilder:storageversion
|
||||
//+kubebuilder:object:root=true
|
||||
//+kubebuilder:subresource:status
|
||||
//+kubebuilder:deprecatedversion:warning="v1beta1 ImageUpdateAutomation is deprecated, upgrade to v1beta2"
|
||||
//+kubebuilder:printcolumn:name="Last run",type=string,JSONPath=`.status.lastAutomationRunTime`
|
||||
|
||||
// ImageUpdateAutomation is the Schema for the imageupdateautomations API
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2020 The Flux authors
|
||||
Copyright 2024 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.
|
||||
|
@ -105,7 +104,7 @@ func (in *GitSpec) DeepCopyInto(out *GitSpec) {
|
|||
if in.Push != nil {
|
||||
in, out := &in.Push, &out.Push
|
||||
*out = new(PushSpec)
|
||||
**out = **in
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,6 +238,13 @@ func (in *ImageUpdateAutomationStatus) DeepCopy() *ImageUpdateAutomationStatus {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSpec) DeepCopyInto(out *PushSpec) {
|
||||
*out = *in
|
||||
if in.Options != nil {
|
||||
in, out := &in.Options, &out.Options
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSpec.
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
Copyright 2024 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 v1beta2
|
||||
|
||||
const (
|
||||
// InvalidUpdateStrategyReason represents an invalid image update strategy
|
||||
// configuration.
|
||||
InvalidUpdateStrategyReason string = "InvalidUpdateStrategy"
|
||||
|
||||
// InvalidSourceConfigReason represents an invalid source configuration.
|
||||
InvalidSourceConfigReason string = "InvalidSourceConfiguration"
|
||||
|
||||
// SourceManagerFailedReason represents a failure in the SourceManager which
|
||||
// manages the source.
|
||||
SourceManagerFailedReason string = "SourceManagerFailed"
|
||||
|
||||
// GitOperationFailedReason represents a failure in Git source operation.
|
||||
GitOperationFailedReason string = "GitOperationFailed"
|
||||
|
||||
// UpdateFailedReason represents a failure during source update.
|
||||
UpdateFailedReason string = "UpdateFailed"
|
||||
|
||||
// InvalidPolicySelectorReason represents an invalid policy selector.
|
||||
InvalidPolicySelectorReason string = "InvalidPolicySelector"
|
||||
)
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 The Flux authors
|
||||
Copyright 2024 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.
|
||||
|
@ -14,11 +14,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package v1alpha1 contains API types for the image v1alpha1 API
|
||||
// group. The types here are concerned with automated updates to git,
|
||||
// based on metadata from OCI image registries gathered by the
|
||||
// Package v1beta2 contains API types for the image API group, version
|
||||
// v1beta2. The types here are concerned with automated updates to
|
||||
// git, based on metadata from OCI image registries gathered by the
|
||||
// image-reflector-controller.
|
||||
//
|
||||
// +kubebuilder:object:generate=true
|
||||
// +groupName=image.toolkit.fluxcd.io
|
||||
package v1alpha1
|
||||
package v1beta2
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 The Flux authors
|
||||
Copyright 2024 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.
|
||||
|
@ -14,11 +14,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1alpha2
|
||||
package v1beta2
|
||||
|
||||
import (
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
)
|
||||
|
||||
type GitSpec struct {
|
||||
|
@ -39,6 +39,14 @@ type GitSpec struct {
|
|||
Push *PushSpec `json:"push,omitempty"`
|
||||
}
|
||||
|
||||
// HasRefspec returns if the GitSpec has a Refspec.
|
||||
func (gs GitSpec) HasRefspec() bool {
|
||||
if gs.Push == nil {
|
||||
return false
|
||||
}
|
||||
return gs.Push.Refspec != ""
|
||||
}
|
||||
|
||||
type GitCheckoutSpec struct {
|
||||
// Reference gives a branch, tag or commit to clone from the Git
|
||||
// repository.
|
||||
|
@ -59,6 +67,11 @@ type CommitSpec struct {
|
|||
// into which will be interpolated the details of the change made.
|
||||
// +optional
|
||||
MessageTemplate string `json:"messageTemplate,omitempty"`
|
||||
|
||||
// MessageTemplateValues provides additional values to be available to the
|
||||
// templating rendering.
|
||||
// +optional
|
||||
MessageTemplateValues map[string]string `json:"messageTemplateValues,omitempty"`
|
||||
}
|
||||
|
||||
type CommitUser struct {
|
||||
|
@ -85,6 +98,20 @@ type PushSpec struct {
|
|||
// Branch specifies that commits should be pushed to the branch
|
||||
// named. The branch is created using `.spec.checkout.branch` as the
|
||||
// starting point, if it doesn't already exist.
|
||||
// +required
|
||||
Branch string `json:"branch"`
|
||||
// +optional
|
||||
Branch string `json:"branch,omitempty"`
|
||||
|
||||
// Refspec specifies the Git Refspec to use for a push operation.
|
||||
// If both Branch and Refspec are provided, then the commit is pushed
|
||||
// to the branch and also using the specified refspec.
|
||||
// For more details about Git Refspecs, see:
|
||||
// https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
|
||||
// +optional
|
||||
Refspec string `json:"refspec,omitempty"`
|
||||
|
||||
// Options specifies the push options that are sent to the Git
|
||||
// server when performing a push operation. For details, see:
|
||||
// https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt
|
||||
// +optional
|
||||
Options map[string]string `json:"options,omitempty"`
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 The Flux authors
|
||||
Copyright 2024 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.
|
||||
|
@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package v1alpha1 contains API Schema definitions for the image v1alpha1 API group
|
||||
// Package v1beta2 contains API Schema definitions for the image v1beta2 API group
|
||||
// +kubebuilder:object:generate=true
|
||||
// +groupName=image.toolkit.fluxcd.io
|
||||
package v1alpha1
|
||||
package v1beta2
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
@ -26,7 +26,7 @@ import (
|
|||
|
||||
var (
|
||||
// GroupVersion is group version used to register these objects
|
||||
GroupVersion = schema.GroupVersion{Group: "image.toolkit.fluxcd.io", Version: "v1alpha1"}
|
||||
GroupVersion = schema.GroupVersion{Group: "image.toolkit.fluxcd.io", Version: "v1beta2"}
|
||||
|
||||
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
|
||||
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 The Flux authors
|
||||
Copyright 2024 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.
|
||||
|
@ -14,23 +14,28 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1alpha2
|
||||
package v1beta2
|
||||
|
||||
import (
|
||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
)
|
||||
|
||||
const ImageUpdateAutomationKind = "ImageUpdateAutomation"
|
||||
const (
|
||||
ImageUpdateAutomationKind = "ImageUpdateAutomation"
|
||||
ImageUpdateAutomationFinalizer = "finalizers.fluxcd.io"
|
||||
)
|
||||
|
||||
// ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation
|
||||
type ImageUpdateAutomationSpec struct {
|
||||
// SourceRef refers to the resource giving access details
|
||||
// to a git repository.
|
||||
// +required
|
||||
SourceRef SourceReference `json:"sourceRef"`
|
||||
SourceRef CrossNamespaceSourceReference `json:"sourceRef"`
|
||||
|
||||
// GitSpec contains all the git-specific definitions. This is
|
||||
// technically optional, but in practice mandatory until there are
|
||||
// other kinds of source allowed.
|
||||
|
@ -39,9 +44,16 @@ type ImageUpdateAutomationSpec struct {
|
|||
|
||||
// Interval gives an lower bound for how often the automation
|
||||
// run should be attempted.
|
||||
// +kubebuilder:validation:Type=string
|
||||
// +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$"
|
||||
// +required
|
||||
Interval metav1.Duration `json:"interval"`
|
||||
|
||||
// PolicySelector allows to filter applied policies based on labels.
|
||||
// By default includes all policies in namespace.
|
||||
// +optional
|
||||
PolicySelector *metav1.LabelSelector `json:"policySelector,omitempty"`
|
||||
|
||||
// Update gives the specification for how to update the files in
|
||||
// the repository. This can be left empty, to use the default
|
||||
// value.
|
||||
|
@ -99,33 +111,25 @@ type ImageUpdateAutomationStatus struct {
|
|||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
// +optional
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
// ObservedPolicies is the list of observed ImagePolicies that were
|
||||
// considered by the ImageUpdateAutomation update process.
|
||||
// +optional
|
||||
ObservedPolicies ObservedPolicies `json:"observedPolicies,omitempty"`
|
||||
// ObservedPolicies []ObservedPolicy `json:"observedPolicies,omitempty"`
|
||||
// ObservedSourceRevision is the last observed source revision. This can be
|
||||
// used to determine if the source has been updated since last observation.
|
||||
// +optional
|
||||
ObservedSourceRevision string `json:"observedSourceRevision,omitempty"`
|
||||
|
||||
meta.ReconcileRequestStatus `json:",inline"`
|
||||
}
|
||||
|
||||
const (
|
||||
// GitNotAvailableReason is used for ConditionReady when the
|
||||
// automation run cannot proceed because the git repository is
|
||||
// missing or cannot be cloned.
|
||||
GitNotAvailableReason = "GitRepositoryNotAvailable"
|
||||
// NoStrategyReason is used for ConditionReady when the automation
|
||||
// run cannot proceed because there is no update strategy given in
|
||||
// the spec.
|
||||
NoStrategyReason = "MissingUpdateStrategy"
|
||||
)
|
||||
|
||||
// SetImageUpdateAutomationReadiness sets the ready condition with the given status, reason and message.
|
||||
func SetImageUpdateAutomationReadiness(auto *ImageUpdateAutomation, status metav1.ConditionStatus, reason, message string) {
|
||||
auto.Status.ObservedGeneration = auto.ObjectMeta.Generation
|
||||
newCondition := metav1.Condition{
|
||||
Type: meta.ReadyCondition,
|
||||
Status: status,
|
||||
Reason: reason,
|
||||
Message: message,
|
||||
}
|
||||
apimeta.SetStatusCondition(auto.GetStatusConditions(), newCondition)
|
||||
}
|
||||
// ObservedPolicies is a map of policy name and ImageRef of their latest
|
||||
// ImageRef.
|
||||
type ObservedPolicies map[string]ImageRef
|
||||
|
||||
//+kubebuilder:storageversion
|
||||
//+kubebuilder:object:root=true
|
||||
//+kubebuilder:subresource:status
|
||||
//+kubebuilder:printcolumn:name="Last run",type=string,JSONPath=`.status.lastAutomationRunTime`
|
||||
|
@ -135,12 +139,25 @@ type ImageUpdateAutomation struct {
|
|||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec ImageUpdateAutomationSpec `json:"spec,omitempty"`
|
||||
Spec ImageUpdateAutomationSpec `json:"spec,omitempty"`
|
||||
// +kubebuilder:default={"observedGeneration":-1}
|
||||
Status ImageUpdateAutomationStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
func (auto *ImageUpdateAutomation) GetStatusConditions() *[]metav1.Condition {
|
||||
return &auto.Status.Conditions
|
||||
// GetRequeueAfter returns the duration after which the ImageUpdateAutomation
|
||||
// must be reconciled again.
|
||||
func (auto ImageUpdateAutomation) GetRequeueAfter() time.Duration {
|
||||
return auto.Spec.Interval.Duration
|
||||
}
|
||||
|
||||
// GetConditions returns the status conditions of the object.
|
||||
func (auto ImageUpdateAutomation) GetConditions() []metav1.Condition {
|
||||
return auto.Status.Conditions
|
||||
}
|
||||
|
||||
// SetConditions sets the status conditions on the object.
|
||||
func (auto *ImageUpdateAutomation) SetConditions(conditions []metav1.Condition) {
|
||||
auto.Status.Conditions = conditions
|
||||
}
|
||||
|
||||
//+kubebuilder:object:root=true
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
Copyright 2024 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 v1beta2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// CrossNamespaceSourceReference contains enough information to let you locate the
|
||||
// typed Kubernetes resource object at cluster level.
|
||||
type CrossNamespaceSourceReference struct {
|
||||
// API version of the referent.
|
||||
// +optional
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
|
||||
// Kind of the referent.
|
||||
// +kubebuilder:validation:Enum=GitRepository
|
||||
// +kubebuilder:default=GitRepository
|
||||
// +required
|
||||
Kind string `json:"kind"`
|
||||
|
||||
// Name of the referent.
|
||||
// +required
|
||||
Name string `json:"name"`
|
||||
|
||||
// Namespace of the referent, defaults to the namespace of the Kubernetes resource object that contains the reference.
|
||||
// +optional
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
func (s *CrossNamespaceSourceReference) String() string {
|
||||
if s.Namespace != "" {
|
||||
return fmt.Sprintf("%s/%s/%s", s.Kind, s.Namespace, s.Name)
|
||||
}
|
||||
return fmt.Sprintf("%s/%s", s.Kind, s.Name)
|
||||
}
|
||||
|
||||
// ImageRef represents an image reference.
|
||||
type ImageRef struct {
|
||||
// Name is the bare image's name.
|
||||
// +required
|
||||
Name string `json:"name"`
|
||||
// Tag is the image's tag.
|
||||
// +required
|
||||
Tag string `json:"tag"`
|
||||
// Digest is the image's digest.
|
||||
// +optional
|
||||
Digest string `json:"digest,omitempty"`
|
||||
}
|
||||
|
||||
func (in *ImageRef) String() string {
|
||||
res := in.Name + ":" + in.Tag
|
||||
if in.Digest != "" {
|
||||
res += "@" + in.Digest
|
||||
}
|
||||
return res
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2020 The Flux authors
|
||||
Copyright 2024 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.
|
||||
|
@ -19,7 +18,7 @@ limitations under the License.
|
|||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha2
|
||||
package v1beta2
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -35,6 +34,13 @@ func (in *CommitSpec) DeepCopyInto(out *CommitSpec) {
|
|||
*out = new(SigningKey)
|
||||
**out = **in
|
||||
}
|
||||
if in.MessageTemplateValues != nil {
|
||||
in, out := &in.MessageTemplateValues, &out.MessageTemplateValues
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommitSpec.
|
||||
|
@ -62,6 +68,21 @@ func (in *CommitUser) DeepCopy() *CommitUser {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CrossNamespaceSourceReference) DeepCopyInto(out *CrossNamespaceSourceReference) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CrossNamespaceSourceReference.
|
||||
func (in *CrossNamespaceSourceReference) DeepCopy() *CrossNamespaceSourceReference {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CrossNamespaceSourceReference)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GitCheckoutSpec) DeepCopyInto(out *GitCheckoutSpec) {
|
||||
*out = *in
|
||||
|
@ -90,7 +111,7 @@ func (in *GitSpec) DeepCopyInto(out *GitSpec) {
|
|||
if in.Push != nil {
|
||||
in, out := &in.Push, &out.Push
|
||||
*out = new(PushSpec)
|
||||
**out = **in
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,6 +125,21 @@ func (in *GitSpec) DeepCopy() *GitSpec {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageRef) DeepCopyInto(out *ImageRef) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageRef.
|
||||
func (in *ImageRef) DeepCopy() *ImageRef {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImageRef)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageUpdateAutomation) DeepCopyInto(out *ImageUpdateAutomation) {
|
||||
*out = *in
|
||||
|
@ -173,6 +209,11 @@ func (in *ImageUpdateAutomationSpec) DeepCopyInto(out *ImageUpdateAutomationSpec
|
|||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
out.Interval = in.Interval
|
||||
if in.PolicySelector != nil {
|
||||
in, out := &in.PolicySelector, &out.PolicySelector
|
||||
*out = new(v1.LabelSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Update != nil {
|
||||
in, out := &in.Update, &out.Update
|
||||
*out = new(UpdateStrategy)
|
||||
|
@ -208,6 +249,13 @@ func (in *ImageUpdateAutomationStatus) DeepCopyInto(out *ImageUpdateAutomationSt
|
|||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.ObservedPolicies != nil {
|
||||
in, out := &in.ObservedPolicies, &out.ObservedPolicies
|
||||
*out = make(ObservedPolicies, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
out.ReconcileRequestStatus = in.ReconcileRequestStatus
|
||||
}
|
||||
|
||||
|
@ -221,9 +269,37 @@ func (in *ImageUpdateAutomationStatus) DeepCopy() *ImageUpdateAutomationStatus {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in ObservedPolicies) DeepCopyInto(out *ObservedPolicies) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(ObservedPolicies, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObservedPolicies.
|
||||
func (in ObservedPolicies) DeepCopy() ObservedPolicies {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ObservedPolicies)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSpec) DeepCopyInto(out *PushSpec) {
|
||||
*out = *in
|
||||
if in.Options != nil {
|
||||
in, out := &in.Options, &out.Options
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSpec.
|
||||
|
@ -252,21 +328,6 @@ func (in *SigningKey) DeepCopy() *SigningKey {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SourceReference) DeepCopyInto(out *SourceReference) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceReference.
|
||||
func (in *SourceReference) DeepCopy() *SourceReference {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SourceReference)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *UpdateStrategy) DeepCopyInto(out *UpdateStrategy) {
|
||||
*out = *in
|
File diff suppressed because it is too large
Load Diff
|
@ -5,4 +5,4 @@ resources:
|
|||
images:
|
||||
- name: fluxcd/image-automation-controller
|
||||
newName: fluxcd/image-automation-controller
|
||||
newTag: v0.26.0
|
||||
newTag: v0.41.1
|
||||
|
|
|
@ -1,11 +1,38 @@
|
|||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: manager-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- events
|
||||
verbs:
|
||||
- create
|
||||
- patch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- secrets
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- image.toolkit.fluxcd.io
|
||||
resources:
|
||||
- imagepolicies
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- image.toolkit.fluxcd.io
|
||||
resources:
|
||||
- imagepolicies/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- image.toolkit.fluxcd.io
|
||||
resources:
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
apiVersion: image.toolkit.fluxcd.io/v1alpha1
|
||||
kind: ImageUpdateAutomation
|
||||
metadata:
|
||||
name: imageupdateautomation-sample
|
||||
spec:
|
||||
checkout:
|
||||
gitRepositoryRef:
|
||||
name: app-repo
|
||||
branch: main
|
||||
interval: 5m
|
||||
# update strategy is left to default to "Setters"
|
||||
commit:
|
||||
authorName: Fluxbot
|
||||
authorEmail: fluxbot@example.com
|
|
@ -0,0 +1,29 @@
|
|||
apiVersion: image.toolkit.fluxcd.io/v1beta2
|
||||
kind: ImageUpdateAutomation
|
||||
metadata:
|
||||
name: imageupdateautomation-sample
|
||||
spec:
|
||||
interval: 5m
|
||||
sourceRef:
|
||||
kind: GitRepository # the only valid value, but good practice to be explicit here
|
||||
name: sample-repo
|
||||
git:
|
||||
checkout:
|
||||
ref:
|
||||
branch: main
|
||||
commit:
|
||||
author:
|
||||
name: fluxbot
|
||||
email: fluxbot@example.com
|
||||
messageTemplate: |
|
||||
An automated update from FluxBot
|
||||
|
||||
[ci skip]
|
||||
signingKey:
|
||||
secretRef:
|
||||
name: git-pgp
|
||||
push:
|
||||
branch: auto
|
||||
update:
|
||||
strategy: Setters
|
||||
path: ./cluster/sample
|
|
@ -1,76 +0,0 @@
|
|||
/*
|
||||
Copyright 2020, 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 controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLibgit2ErrorTidy(t *testing.T) {
|
||||
// this is what GitLab sends if the deploy key doesn't have write access
|
||||
gitlabMessage := `remote:
|
||||
remote: ========================================================================
|
||||
remote:
|
||||
remote: This deploy key does not have write access to this project.
|
||||
remote:
|
||||
remote: ========================================================================
|
||||
remote:
|
||||
`
|
||||
expectedReformat := "remote: This deploy key does not have write access to this project."
|
||||
|
||||
err := errors.New(gitlabMessage)
|
||||
err = libgit2PushError(err)
|
||||
reformattedMessage := err.Error()
|
||||
if reformattedMessage != expectedReformat {
|
||||
t.Errorf("expected %q, got %q", expectedReformat, reformattedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLibgit2Multiline(t *testing.T) {
|
||||
// this is a hypothetical error message, in which the useful
|
||||
// content spans more than one line
|
||||
multilineMessage := `remote:
|
||||
remote: ========================================================================
|
||||
remote:
|
||||
remote: This deploy key does not have write access to this project.
|
||||
remote: You will need to create a new deploy key.
|
||||
remote:
|
||||
remote: ========================================================================
|
||||
remote:
|
||||
`
|
||||
expectedReformat := "remote: This deploy key does not have write access to this project. You will need to create a new deploy key."
|
||||
|
||||
err := errors.New(multilineMessage)
|
||||
err = libgit2PushError(err)
|
||||
reformattedMessage := err.Error()
|
||||
if reformattedMessage != expectedReformat {
|
||||
t.Errorf("expected %q, got %q", expectedReformat, reformattedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLibgit2ErrorUnchanged(t *testing.T) {
|
||||
// this is (roughly) what GitHub sends if the deploy key doesn't have write access
|
||||
regularMessage := `remote: ERROR: deploy key does not have permissions`
|
||||
expectedReformat := regularMessage
|
||||
err := errors.New(regularMessage)
|
||||
err = libgit2PushError(err)
|
||||
reformattedMessage := err.Error()
|
||||
if reformattedMessage != expectedReformat {
|
||||
t.Errorf("expected %q, got %q", expectedReformat, reformattedMessage)
|
||||
}
|
||||
}
|
|
@ -1,318 +0,0 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
libgit2 "github.com/libgit2/git2go/v33"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"github.com/fluxcd/pkg/gittestserver"
|
||||
"github.com/fluxcd/source-controller/pkg/git"
|
||||
)
|
||||
|
||||
func populateRepoFromFixture(repo *libgit2.Repository, fixture string) error {
|
||||
absFixture, err := filepath.Abs(fixture)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := filepath.Walk(absFixture, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return os.MkdirAll(filepath.Join(path[len(fixture):]), info.Mode())
|
||||
}
|
||||
// copy symlinks as-is, so I can test what happens with broken symlinks
|
||||
if info.Mode()&os.ModeSymlink > 0 {
|
||||
target, err := os.Readlink(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Symlink(target, path[len(fixture):])
|
||||
}
|
||||
|
||||
fileBytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ff, err := os.Create(path[len(fixture):])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ff.Close()
|
||||
|
||||
_, err = ff.Write(fileBytes)
|
||||
return err
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sig := &libgit2.Signature{
|
||||
Name: "Testbot",
|
||||
Email: "test@example.com",
|
||||
When: time.Now(),
|
||||
}
|
||||
|
||||
if _, err := commitWorkDir(repo, "main", "Initial revision from "+fixture, sig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestRepoForFixture(t *testing.T) {
|
||||
tmp, err := os.MkdirTemp("", "flux-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
repo, err := initGitRepoPlain("testdata/pathconfig", tmp)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
repo.Free()
|
||||
}
|
||||
|
||||
func TestIgnoreBrokenSymlink(t *testing.T) {
|
||||
// init a git repo in the filesystem so we can operate on files there
|
||||
tmp, err := os.MkdirTemp("", "flux-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
repo, err := initGitRepoPlain("testdata/brokenlink", tmp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = commitChangedManifests(logr.Discard(), repo, tmp, nil, nil, "unused")
|
||||
if err != errNoChanges {
|
||||
t.Fatalf("expected no changes but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// this is a hook script that will reject a ref update for a branch
|
||||
// that's not `main`
|
||||
const rejectBranch = `
|
||||
if [ "$1" != "refs/heads/main" ]; then
|
||||
echo "*** Rejecting push to non-main branch $1" >&2
|
||||
exit 1
|
||||
fi
|
||||
`
|
||||
|
||||
func TestPushRejected(t *testing.T) {
|
||||
// Check that pushing to a repository which rejects a ref update
|
||||
// results in an error. Why would a repo reject an update? If yu
|
||||
// use e.g., branch protection in GitHub, this is what happens --
|
||||
// see
|
||||
// https://github.com/fluxcd/image-automation-controller/issues/194.
|
||||
|
||||
branch := "push-branch"
|
||||
|
||||
gitServer, err := gittestserver.NewTempGitServer()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gitServer.AutoCreate()
|
||||
gitServer.InstallUpdateHook(rejectBranch)
|
||||
|
||||
if err = gitServer.StartHTTP(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// We use "test" as the branch to init repo, to avoid potential conflicts
|
||||
// with the default branch(main/master) of the system this test is running
|
||||
// on. If, for e.g., we used main as the branch and the default branch is
|
||||
// supposed to be main, this will fail as this would try to create a branch
|
||||
// named main explicitly.
|
||||
if err = initGitRepo(gitServer, "testdata/appconfig", "test", "/appconfig.git"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
repoURL := gitServer.HTTPAddressWithCredentials() + "/appconfig.git"
|
||||
cloneCtx, cancel := context.WithTimeout(ctx, time.Second*10)
|
||||
defer cancel()
|
||||
repo, err := clone(cloneCtx, repoURL, "test", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer repo.Free()
|
||||
|
||||
cleanup, err := configureTransportOptsForRepo(repo, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
// This is here to guard against push in general being broken
|
||||
err = push(context.TODO(), repo.Workdir(), "test", repoAccess{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// This is not under test, but needed for the next bit
|
||||
if err = repo.SetHead(fmt.Sprintf("refs/heads/%s", branch)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// This is supposed to fail, because the hook rejects the branch
|
||||
// pushed to.
|
||||
err = push(context.TODO(), repo.Workdir(), branch, repoAccess{})
|
||||
if err == nil {
|
||||
t.Error("push to a forbidden branch is expected to fail, but succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEarlyEOF(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
gitServer, err := gittestserver.NewTempGitServer()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
defer os.RemoveAll(gitServer.Root())
|
||||
|
||||
username := "norris"
|
||||
password := "chuck"
|
||||
|
||||
gitServer.
|
||||
AutoCreate().
|
||||
KeyDir(filepath.Join(t.TempDir(), "keys")).
|
||||
Auth(username, password).
|
||||
ReadOnly(true)
|
||||
|
||||
err = gitServer.StartHTTP()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = initGitRepo(gitServer, "testdata/appconfig", "test", "/appconfig.git")
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
repoURL := gitServer.HTTPAddressWithCredentials() + "/appconfig.git"
|
||||
cloneCtx, cancel := context.WithTimeout(ctx, time.Second*10)
|
||||
defer cancel()
|
||||
|
||||
access := repoAccess{
|
||||
auth: &git.AuthOptions{
|
||||
Username: username,
|
||||
Password: password,
|
||||
},
|
||||
}
|
||||
|
||||
repo, err := clone(cloneCtx, repoURL, "test", access.auth)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
defer repo.Free()
|
||||
|
||||
cleanup, err := configureTransportOptsForRepo(repo, access.auth)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
defer cleanup()
|
||||
|
||||
err = push(context.TODO(), repo.Workdir(), "test", access)
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(err.Error()).To(ContainSubstring("early EOF (the SSH key may not have write access to the repository)"))
|
||||
}
|
||||
|
||||
func Test_switchToBranch(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
gitServer, err := gittestserver.NewTempGitServer()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
gitServer.AutoCreate()
|
||||
g.Expect(gitServer.StartHTTP()).To(Succeed())
|
||||
|
||||
// We use "test" as the branch to init repo, to avoid potential conflicts
|
||||
// with the default branch(main/master) of the system this test is running
|
||||
// on. If, for e.g., we used main as the branch and the default branch is
|
||||
// supposed to be main, this will fail as this would try to create a branch
|
||||
// named main explicitly.
|
||||
branch := "test"
|
||||
g.Expect(initGitRepo(gitServer, "testdata/appconfig", branch, "/appconfig.git")).To(Succeed())
|
||||
|
||||
repoURL := gitServer.HTTPAddressWithCredentials() + "/appconfig.git"
|
||||
cloneCtx, cancel := context.WithTimeout(ctx, time.Second*10)
|
||||
defer cancel()
|
||||
repo, err := clone(cloneCtx, repoURL, branch, nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
defer repo.Free()
|
||||
|
||||
head, err := repo.Head()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
defer head.Free()
|
||||
target := head.Target()
|
||||
|
||||
// register transport options and update remote to transport url
|
||||
cleanup, err := configureTransportOptsForRepo(repo, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
// calling switchToBranch with a branch that doesn't exist on origin
|
||||
// should result in the branch being created and switched to.
|
||||
branch = "not-on-origin"
|
||||
switchToBranch(repo, context.TODO(), branch, repoAccess{})
|
||||
|
||||
head, err = repo.Head()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
name, err := head.Branch().Name()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(name).To(Equal(branch))
|
||||
|
||||
cc, err := repo.LookupCommit(head.Target())
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
defer cc.Free()
|
||||
g.Expect(cc.Id().String()).To(Equal(target.String()))
|
||||
|
||||
// create a branch with the HEAD commit and push it to origin
|
||||
branch = "exists-on-origin"
|
||||
_, err = repo.CreateBranch(branch, cc, false)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
origin, err := repo.Remotes.Lookup(originRemote)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
defer origin.Free()
|
||||
|
||||
g.Expect(origin.Push(
|
||||
[]string{fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch)}, &libgit2.PushOptions{},
|
||||
)).To(Succeed())
|
||||
|
||||
// push a new commit to the branch. this is done to test whether we properly
|
||||
// sync our local branch with the remote branch, before switching.
|
||||
policyKey := types.NamespacedName{
|
||||
Name: "policy",
|
||||
Namespace: "ns",
|
||||
}
|
||||
commitID := commitInRepo(g, repoURL, branch, "Install setter marker", func(tmp string) {
|
||||
g.Expect(replaceMarker(tmp, policyKey)).To(Succeed())
|
||||
})
|
||||
|
||||
// calling switchToBranch with a branch that exists should make sure to fetch latest
|
||||
// for that branch from origin, and then switch to it.
|
||||
switchToBranch(repo, context.TODO(), branch, repoAccess{})
|
||||
head, err = repo.Head()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
name, err = head.Branch().Name()
|
||||
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(name).To(Equal(branch))
|
||||
g.Expect(head.Target().String()).To(Equal(commitID.String()))
|
||||
|
||||
// push a commit after switching to the branch, to check if the local
|
||||
// branch is synced with origin.
|
||||
replaceMarker(repo.Workdir(), policyKey)
|
||||
sig := &libgit2.Signature{
|
||||
Name: "Testbot",
|
||||
Email: "test@example.com",
|
||||
When: time.Now(),
|
||||
}
|
||||
_, err = commitWorkDir(repo, branch, "update policy", sig)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(push(context.TODO(), repo.Workdir(), branch, repoAccess{})).To(Succeed())
|
||||
}
|
|
@ -1,942 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 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 controllers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
"github.com/go-logr/logr"
|
||||
libgit2 "github.com/libgit2/git2go/v33"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
kuberecorder "k8s.io/client-go/tools/record"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/ratelimiter"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta1"
|
||||
apiacl "github.com/fluxcd/pkg/apis/acl"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/fluxcd/pkg/runtime/acl"
|
||||
helper "github.com/fluxcd/pkg/runtime/controller"
|
||||
"github.com/fluxcd/pkg/runtime/events"
|
||||
"github.com/fluxcd/pkg/runtime/logger"
|
||||
"github.com/fluxcd/pkg/runtime/predicates"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
"github.com/fluxcd/source-controller/pkg/git"
|
||||
"github.com/fluxcd/source-controller/pkg/git/libgit2/managed"
|
||||
gitstrat "github.com/fluxcd/source-controller/pkg/git/strategy"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||
"github.com/fluxcd/image-automation-controller/pkg/update"
|
||||
)
|
||||
|
||||
const originRemote = "origin"
|
||||
|
||||
const defaultMessageTemplate = `Update from image update automation`
|
||||
|
||||
const repoRefKey = ".spec.gitRepository"
|
||||
|
||||
const signingSecretKey = "git.asc"
|
||||
|
||||
// TemplateData is the type of the value given to the commit message
|
||||
// template.
|
||||
type TemplateData struct {
|
||||
AutomationObject types.NamespacedName
|
||||
Updated update.Result
|
||||
}
|
||||
|
||||
// ImageUpdateAutomationReconciler reconciles a ImageUpdateAutomation object
|
||||
type ImageUpdateAutomationReconciler struct {
|
||||
client.Client
|
||||
EventRecorder kuberecorder.EventRecorder
|
||||
helper.Metrics
|
||||
|
||||
NoCrossNamespaceRef bool
|
||||
}
|
||||
|
||||
type ImageUpdateAutomationReconcilerOptions struct {
|
||||
MaxConcurrentReconciles int
|
||||
RateLimiter ratelimiter.RateLimiter
|
||||
RecoverPanic bool
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=image.toolkit.fluxcd.io,resources=imageupdateautomations,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=image.toolkit.fluxcd.io,resources=imageupdateautomations/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=gitrepositories,verbs=get;list;watch
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
debuglog := log.V(logger.DebugLevel)
|
||||
tracelog := log.V(logger.TraceLevel)
|
||||
start := time.Now()
|
||||
var templateValues TemplateData
|
||||
|
||||
var auto imagev1.ImageUpdateAutomation
|
||||
if err := r.Get(ctx, req.NamespacedName, &auto); err != nil {
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
// Add our finalizer if it does not exist.
|
||||
if !controllerutil.ContainsFinalizer(&auto, imagev1.ImageUpdateAutomationFinalizer) {
|
||||
patch := client.MergeFrom(auto.DeepCopy())
|
||||
controllerutil.AddFinalizer(&auto, imagev1.ImageUpdateAutomationFinalizer)
|
||||
if err := r.Patch(ctx, &auto, patch); err != nil {
|
||||
log.Error(err, "unable to register finalizer")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// If the object is under deletion, record the readiness, and remove our finalizer.
|
||||
if !auto.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||
controllerutil.RemoveFinalizer(&auto, imagev1.ImageUpdateAutomationFinalizer)
|
||||
if err := r.Update(ctx, &auto); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// record suspension metrics
|
||||
r.RecordSuspend(ctx, &auto, auto.Spec.Suspend)
|
||||
|
||||
if auto.Spec.Suspend {
|
||||
log.Info("ImageUpdateAutomation is suspended, skipping automation run")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
templateValues.AutomationObject = req.NamespacedName
|
||||
|
||||
defer func() {
|
||||
// Always record readiness and duration metrics
|
||||
r.Metrics.RecordReadiness(ctx, &auto)
|
||||
r.Metrics.RecordDuration(ctx, &auto, start)
|
||||
}()
|
||||
|
||||
// whatever else happens, we've now "seen" the reconcile
|
||||
// annotation if it's there
|
||||
if token, ok := meta.ReconcileAnnotationValue(auto.GetAnnotations()); ok {
|
||||
auto.Status.SetLastHandledReconcileRequest(token)
|
||||
|
||||
if err := r.patchStatus(ctx, req, auto.Status); err != nil {
|
||||
return ctrl.Result{Requeue: true}, err
|
||||
}
|
||||
}
|
||||
|
||||
// failWithError is a helper for bailing on the reconciliation.
|
||||
failWithError := func(err error) (ctrl.Result, error) {
|
||||
r.event(ctx, auto, events.EventSeverityError, err.Error())
|
||||
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionFalse, imagev1.ReconciliationFailedReason, err.Error())
|
||||
if err := r.patchStatus(ctx, req, auto.Status); err != nil {
|
||||
log.Error(err, "failed to reconcile")
|
||||
}
|
||||
return ctrl.Result{Requeue: true}, err
|
||||
}
|
||||
|
||||
// get the git repository object so it can be checked out
|
||||
|
||||
// only GitRepository objects are supported for now
|
||||
if kind := auto.Spec.SourceRef.Kind; kind != sourcev1.GitRepositoryKind {
|
||||
return failWithError(fmt.Errorf("source kind '%s' not supported", kind))
|
||||
}
|
||||
|
||||
gitSpec := auto.Spec.GitSpec
|
||||
if gitSpec == nil {
|
||||
return failWithError(fmt.Errorf("source kind %s neccessitates field .spec.git", sourcev1.GitRepositoryKind))
|
||||
}
|
||||
|
||||
var origin sourcev1.GitRepository
|
||||
gitRepoNamespace := req.Namespace
|
||||
if auto.Spec.SourceRef.Namespace != "" {
|
||||
gitRepoNamespace = auto.Spec.SourceRef.Namespace
|
||||
}
|
||||
originName := types.NamespacedName{
|
||||
Name: auto.Spec.SourceRef.Name,
|
||||
Namespace: gitRepoNamespace,
|
||||
}
|
||||
debuglog.Info("fetching git repository", "gitrepository", originName)
|
||||
|
||||
if r.NoCrossNamespaceRef && gitRepoNamespace != auto.GetNamespace() {
|
||||
err := acl.AccessDeniedError(fmt.Sprintf("can't access '%s/%s', cross-namespace references have been blocked",
|
||||
auto.Spec.SourceRef.Kind, originName))
|
||||
log.Error(err, "access denied to cross-namespaced resource")
|
||||
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionFalse, apiacl.AccessDeniedReason,
|
||||
err.Error())
|
||||
if err := r.patchStatus(ctx, req, auto.Status); err != nil {
|
||||
return ctrl.Result{Requeue: true}, err
|
||||
}
|
||||
r.event(ctx, auto, events.EventSeverityError, err.Error())
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
if err := r.Get(ctx, originName, &origin); err != nil {
|
||||
if client.IgnoreNotFound(err) == nil {
|
||||
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionFalse, imagev1.GitNotAvailableReason, "referenced git repository is missing")
|
||||
log.Error(err, fmt.Sprintf("referenced git repository %s does not exist.", originName.String()))
|
||||
if err := r.patchStatus(ctx, req, auto.Status); err != nil {
|
||||
return ctrl.Result{Requeue: true}, err
|
||||
}
|
||||
return ctrl.Result{}, nil // and assume we'll hear about it when it arrives
|
||||
}
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// validate the git spec and default any values needed later, before proceeding
|
||||
var ref *sourcev1.GitRepositoryRef
|
||||
if gitSpec.Checkout != nil {
|
||||
ref = &gitSpec.Checkout.Reference
|
||||
tracelog.Info("using git repository ref from .spec.git.checkout", "ref", ref)
|
||||
} else if r := origin.Spec.Reference; r != nil {
|
||||
ref = r
|
||||
tracelog.Info("using git repository ref from GitRepository spec", "ref", ref)
|
||||
} // else remain as `nil`, which is an acceptable value for cloneInto, later.
|
||||
|
||||
var pushBranch string
|
||||
if gitSpec.Push != nil {
|
||||
pushBranch = gitSpec.Push.Branch
|
||||
tracelog.Info("using push branch from .spec.push.branch", "branch", pushBranch)
|
||||
} else {
|
||||
// Here's where it gets constrained. If there's no push branch
|
||||
// given, then the checkout ref must include a branch, and
|
||||
// that can be used.
|
||||
if ref == nil || ref.Branch == "" {
|
||||
return failWithError(fmt.Errorf("Push branch not given explicitly, and cannot be inferred from .spec.git.checkout.ref or GitRepository .spec.ref"))
|
||||
}
|
||||
pushBranch = ref.Branch
|
||||
tracelog.Info("using push branch from $ref.branch", "branch", pushBranch)
|
||||
}
|
||||
|
||||
tmp, err := os.MkdirTemp("", fmt.Sprintf("%s-%s", originName.Namespace, originName.Name))
|
||||
if err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := os.RemoveAll(tmp); err != nil {
|
||||
log.Error(err, "failed to remove working directory", "path", tmp)
|
||||
}
|
||||
}()
|
||||
|
||||
// FIXME use context with deadline for at least the following ops
|
||||
|
||||
debuglog.Info("attempting to clone git repository", "gitrepository", originName, "ref", ref, "working", tmp)
|
||||
|
||||
access, err := r.getRepoAccess(ctx, &origin)
|
||||
if err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
|
||||
// We set the TransportOptionsURL of this set of authentication options here by constructing
|
||||
// a unique URL that won't clash in a multi tenant environment. This unique URL is used by
|
||||
// libgit2 managed transports. This enables us to bypass the inbuilt credentials callback in
|
||||
// libgit2, which is inflexible and unstable.
|
||||
// NB: The Transport Options URL must be unique, therefore it must use the object under
|
||||
// reconciliation details, instead of the repository it depends on.
|
||||
if strings.HasPrefix(origin.Spec.URL, "http") {
|
||||
access.auth.TransportOptionsURL = fmt.Sprintf("http://%s/%s/%d", auto.Name, auto.UID, auto.Generation)
|
||||
} else if strings.HasPrefix(origin.Spec.URL, "ssh") {
|
||||
access.auth.TransportOptionsURL = fmt.Sprintf("ssh://%s/%s/%d", auto.Name, auto.UID, auto.Generation)
|
||||
} else {
|
||||
return failWithError(fmt.Errorf("git repository URL '%s' has invalid transport type, supported types are: http, https, ssh", origin.Spec.URL))
|
||||
}
|
||||
|
||||
// Use the git operations timeout for the repo.
|
||||
cloneCtx, cancel := context.WithTimeout(ctx, origin.Spec.Timeout.Duration)
|
||||
defer cancel()
|
||||
var repo *libgit2.Repository
|
||||
if repo, err = cloneInto(cloneCtx, access, ref, tmp); err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
defer repo.Free()
|
||||
|
||||
// Checkout removes TransportOptions before returning, therefore this
|
||||
// must happen after cloneInto.
|
||||
// TODO(pjbgf): Git consolidation should improve the API workflow.
|
||||
managed.AddTransportOptions(access.auth.TransportOptionsURL, managed.TransportOptions{
|
||||
TargetURL: origin.Spec.URL,
|
||||
AuthOpts: access.auth,
|
||||
ProxyOptions: &libgit2.ProxyOptions{Type: libgit2.ProxyTypeAuto},
|
||||
Context: cloneCtx,
|
||||
})
|
||||
|
||||
defer managed.RemoveTransportOptions(access.auth.TransportOptionsURL)
|
||||
|
||||
// When there's a push spec, the pushed-to branch is where commits
|
||||
// shall be made
|
||||
|
||||
if gitSpec.Push != nil && !(ref != nil && ref.Branch == pushBranch) {
|
||||
// Use the git operations timeout for the repo.
|
||||
fetchCtx, cancel := context.WithTimeout(ctx, origin.Spec.Timeout.Duration)
|
||||
defer cancel()
|
||||
if err := switchToBranch(repo, fetchCtx, pushBranch, access); err != nil && err != errRemoteBranchMissing {
|
||||
return failWithError(err)
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case auto.Spec.Update != nil && auto.Spec.Update.Strategy == imagev1.UpdateStrategySetters:
|
||||
// For setters we first want to compile a list of _all_ the
|
||||
// policies in the same namespace (maybe in the future this
|
||||
// could be filtered by the automation object).
|
||||
var policies imagev1_reflect.ImagePolicyList
|
||||
if err := r.List(ctx, &policies, &client.ListOptions{Namespace: req.NamespacedName.Namespace}); err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
|
||||
manifestsPath := tmp
|
||||
if auto.Spec.Update.Path != "" {
|
||||
tracelog.Info("adjusting update path according to .spec.update.path", "base", tmp, "spec-path", auto.Spec.Update.Path)
|
||||
p, err := securejoin.SecureJoin(tmp, auto.Spec.Update.Path)
|
||||
if err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
manifestsPath = p
|
||||
}
|
||||
|
||||
debuglog.Info("updating with setters according to image policies", "count", len(policies.Items), "manifests-path", manifestsPath)
|
||||
if tracelog.Enabled() {
|
||||
for _, item := range policies.Items {
|
||||
tracelog.Info("found policy", "namespace", item.Namespace, "name", item.Name, "latest-image", item.Status.LatestImage)
|
||||
}
|
||||
}
|
||||
|
||||
if result, err := updateAccordingToSetters(ctx, tracelog, manifestsPath, policies.Items); err != nil {
|
||||
return failWithError(err)
|
||||
} else {
|
||||
templateValues.Updated = result
|
||||
}
|
||||
default:
|
||||
log.Info("no update strategy given in the spec")
|
||||
// no sense rescheduling until this resource changes
|
||||
r.event(ctx, auto, events.EventSeverityInfo, "no known update strategy in spec, failing trivially")
|
||||
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionFalse, imagev1.NoStrategyReason, "no known update strategy is given for object")
|
||||
return ctrl.Result{}, r.patchStatus(ctx, req, auto.Status)
|
||||
}
|
||||
|
||||
debuglog.Info("ran updates to working dir", "working", tmp)
|
||||
|
||||
var statusMessage string
|
||||
|
||||
var signingEntity *openpgp.Entity
|
||||
if gitSpec.Commit.SigningKey != nil {
|
||||
if signingEntity, err = r.getSigningEntity(ctx, auto); err != nil {
|
||||
failWithError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// construct the commit message from template and values
|
||||
message, err := templateMsg(gitSpec.Commit.MessageTemplate, &templateValues)
|
||||
if err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
|
||||
// The status message depends on what happens next. Since there's
|
||||
// more than one way to succeed, there's some if..else below, and
|
||||
// early returns only on failure.
|
||||
signature := &libgit2.Signature{
|
||||
Name: gitSpec.Commit.Author.Name,
|
||||
Email: gitSpec.Commit.Author.Email,
|
||||
When: time.Now(),
|
||||
}
|
||||
|
||||
if rev, err := commitChangedManifests(tracelog, repo, tmp, signingEntity, signature, message); err != nil {
|
||||
if err != errNoChanges {
|
||||
return failWithError(err)
|
||||
}
|
||||
|
||||
log.Info("no changes made in working directory; no commit")
|
||||
statusMessage = "no updates made"
|
||||
|
||||
if auto.Status.LastPushTime != nil && len(auto.Status.LastPushCommit) >= 7 {
|
||||
statusMessage = fmt.Sprintf("%s; last commit %s at %s", statusMessage, auto.Status.LastPushCommit[:7], auto.Status.LastPushTime.Format(time.RFC3339))
|
||||
}
|
||||
} else {
|
||||
// Use the git operations timeout for the repo.
|
||||
pushCtx, cancel := context.WithTimeout(ctx, origin.Spec.Timeout.Duration)
|
||||
defer cancel()
|
||||
if err := push(pushCtx, tmp, pushBranch, access); err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
|
||||
r.event(ctx, auto, events.EventSeverityInfo, fmt.Sprintf("Committed and pushed change %s to %s\n%s", rev, pushBranch, message))
|
||||
log.Info("pushed commit to origin", "revision", rev, "branch", pushBranch)
|
||||
auto.Status.LastPushCommit = rev
|
||||
auto.Status.LastPushTime = &metav1.Time{Time: start}
|
||||
statusMessage = "committed and pushed " + rev + " to " + pushBranch
|
||||
}
|
||||
|
||||
// Getting to here is a successful run.
|
||||
auto.Status.LastAutomationRunTime = &metav1.Time{Time: start}
|
||||
imagev1.SetImageUpdateAutomationReadiness(&auto, metav1.ConditionTrue, imagev1.ReconciliationSucceededReason, statusMessage)
|
||||
if err := r.patchStatus(ctx, req, auto.Status); err != nil {
|
||||
return ctrl.Result{Requeue: true}, err
|
||||
}
|
||||
|
||||
// We're either in this method because something changed, or this
|
||||
// object got requeued. Either way, once successful, we don't need
|
||||
// to see the object again until Interval has passed, or something
|
||||
// changes again.
|
||||
|
||||
interval := intervalOrDefault(&auto)
|
||||
return ctrl.Result{RequeueAfter: interval}, nil
|
||||
}
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) SetupWithManager(mgr ctrl.Manager, opts ImageUpdateAutomationReconcilerOptions) error {
|
||||
ctx := context.Background()
|
||||
// Index the git repository object that each I-U-A refers to
|
||||
if err := mgr.GetFieldIndexer().IndexField(ctx, &imagev1.ImageUpdateAutomation{}, repoRefKey, func(obj client.Object) []string {
|
||||
updater := obj.(*imagev1.ImageUpdateAutomation)
|
||||
ref := updater.Spec.SourceRef
|
||||
return []string{ref.Name}
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&imagev1.ImageUpdateAutomation{}, builder.WithPredicates(
|
||||
predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}))).
|
||||
Watches(&source.Kind{Type: &sourcev1.GitRepository{}}, handler.EnqueueRequestsFromMapFunc(r.automationsForGitRepo)).
|
||||
Watches(&source.Kind{Type: &imagev1_reflect.ImagePolicy{}}, handler.EnqueueRequestsFromMapFunc(r.automationsForImagePolicy)).
|
||||
WithOptions(controller.Options{
|
||||
MaxConcurrentReconciles: opts.MaxConcurrentReconciles,
|
||||
RateLimiter: opts.RateLimiter,
|
||||
RecoverPanic: opts.RecoverPanic,
|
||||
}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) patchStatus(ctx context.Context,
|
||||
req ctrl.Request,
|
||||
newStatus imagev1.ImageUpdateAutomationStatus) error {
|
||||
|
||||
var auto imagev1.ImageUpdateAutomation
|
||||
if err := r.Get(ctx, req.NamespacedName, &auto); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
patch := client.MergeFrom(auto.DeepCopy())
|
||||
auto.Status = newStatus
|
||||
|
||||
return r.Status().Patch(ctx, &auto, patch)
|
||||
}
|
||||
|
||||
// intervalOrDefault gives the interval specified, or if missing, the default
|
||||
func intervalOrDefault(auto *imagev1.ImageUpdateAutomation) time.Duration {
|
||||
if auto.Spec.Interval.Duration < time.Second {
|
||||
return time.Second
|
||||
}
|
||||
return auto.Spec.Interval.Duration
|
||||
}
|
||||
|
||||
// durationSinceLastRun calculates how long it's been since the last
|
||||
// time the automation ran (which you can then use to find how long to
|
||||
// wait until the next run).
|
||||
func durationSinceLastRun(auto *imagev1.ImageUpdateAutomation, now time.Time) time.Duration {
|
||||
last := auto.Status.LastAutomationRunTime
|
||||
if last == nil {
|
||||
return time.Duration(math.MaxInt64) // a fairly long time
|
||||
}
|
||||
return now.Sub(last.Time)
|
||||
}
|
||||
|
||||
// automationsForGitRepo fetches all the automations that refer to a
|
||||
// particular source.GitRepository object.
|
||||
func (r *ImageUpdateAutomationReconciler) automationsForGitRepo(obj client.Object) []reconcile.Request {
|
||||
ctx := context.Background()
|
||||
var autoList imagev1.ImageUpdateAutomationList
|
||||
if err := r.List(ctx, &autoList, client.InNamespace(obj.GetNamespace()),
|
||||
client.MatchingFields{repoRefKey: obj.GetName()}); err != nil {
|
||||
return nil
|
||||
}
|
||||
reqs := make([]reconcile.Request, len(autoList.Items), len(autoList.Items))
|
||||
for i := range autoList.Items {
|
||||
reqs[i].NamespacedName.Name = autoList.Items[i].GetName()
|
||||
reqs[i].NamespacedName.Namespace = autoList.Items[i].GetNamespace()
|
||||
}
|
||||
return reqs
|
||||
}
|
||||
|
||||
// automationsForImagePolicy fetches all the automation objects that
|
||||
// might depend on a image policy object. Since the link is via
|
||||
// markers in the git repo, _any_ automation object in the same
|
||||
// namespace could be affected.
|
||||
func (r *ImageUpdateAutomationReconciler) automationsForImagePolicy(obj client.Object) []reconcile.Request {
|
||||
ctx := context.Background()
|
||||
var autoList imagev1.ImageUpdateAutomationList
|
||||
if err := r.List(ctx, &autoList, client.InNamespace(obj.GetNamespace())); err != nil {
|
||||
return nil
|
||||
}
|
||||
reqs := make([]reconcile.Request, len(autoList.Items), len(autoList.Items))
|
||||
for i := range autoList.Items {
|
||||
reqs[i].NamespacedName.Name = autoList.Items[i].GetName()
|
||||
reqs[i].NamespacedName.Namespace = autoList.Items[i].GetNamespace()
|
||||
}
|
||||
return reqs
|
||||
}
|
||||
|
||||
type repoAccess struct {
|
||||
auth *git.AuthOptions
|
||||
url string
|
||||
}
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) getRepoAccess(ctx context.Context, repository *sourcev1.GitRepository) (repoAccess, error) {
|
||||
var access repoAccess
|
||||
access.url = repository.Spec.URL
|
||||
access.auth = &git.AuthOptions{}
|
||||
|
||||
if repository.Spec.SecretRef != nil {
|
||||
name := types.NamespacedName{
|
||||
Namespace: repository.GetNamespace(),
|
||||
Name: repository.Spec.SecretRef.Name,
|
||||
}
|
||||
|
||||
secret := &corev1.Secret{}
|
||||
err := r.Client.Get(ctx, name, secret)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("auth secret error: %w", err)
|
||||
return access, err
|
||||
}
|
||||
|
||||
access.auth, err = git.AuthOptionsFromSecret(access.url, secret)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("auth error: %w", err)
|
||||
return access, err
|
||||
}
|
||||
}
|
||||
return access, nil
|
||||
}
|
||||
|
||||
// cloneInto clones the upstream repository at the `ref` given (which
|
||||
// can be `nil`). It returns a `*libgit2.Repository` since that is used
|
||||
// for committing changes.
|
||||
func cloneInto(ctx context.Context, access repoAccess, ref *sourcev1.GitRepositoryRef,
|
||||
path string) (*libgit2.Repository, error) {
|
||||
opts := git.CheckoutOptions{}
|
||||
if ref != nil {
|
||||
opts.Tag = ref.Tag
|
||||
opts.SemVer = ref.SemVer
|
||||
opts.Commit = ref.Commit
|
||||
opts.Branch = ref.Branch
|
||||
}
|
||||
checkoutStrat, err := gitstrat.CheckoutStrategyForImplementation(ctx, sourcev1.LibGit2Implementation, opts)
|
||||
if err == nil {
|
||||
_, err = checkoutStrat.Checkout(ctx, path, access.url, access.auth)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return libgit2.OpenRepository(path)
|
||||
}
|
||||
|
||||
func headCommit(repo *libgit2.Repository) (*libgit2.Commit, error) {
|
||||
head, err := repo.Head()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer head.Free()
|
||||
c, err := repo.LookupCommit(head.Target())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
var errNoChanges error = errors.New("no changes made to working directory")
|
||||
|
||||
func commitChangedManifests(tracelog logr.Logger, repo *libgit2.Repository, absRepoPath string, ent *openpgp.Entity, sig *libgit2.Signature, message string) (string, error) {
|
||||
sl, err := repo.StatusList(&libgit2.StatusOptions{
|
||||
Show: libgit2.StatusShowIndexAndWorkdir,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer sl.Free()
|
||||
|
||||
count, err := sl.EntryCount()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
return "", errNoChanges
|
||||
}
|
||||
|
||||
var parentC []*libgit2.Commit
|
||||
head, err := headCommit(repo)
|
||||
if err == nil {
|
||||
defer head.Free()
|
||||
parentC = append(parentC, head)
|
||||
}
|
||||
|
||||
index, err := repo.Index()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer index.Free()
|
||||
|
||||
// add to index any files that are not within .git/
|
||||
if err = filepath.Walk(repo.Workdir(),
|
||||
func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rel, err := filepath.Rel(repo.Workdir(), path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f.IsDir() || strings.HasPrefix(rel, ".git") || rel == "." {
|
||||
return nil
|
||||
}
|
||||
if err := index.AddByPath(rel); err != nil {
|
||||
tracelog.Info("adding file", "file", rel)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := index.Write(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
treeID, err := index.WriteTree()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tree, err := repo.LookupTree(treeID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer tree.Free()
|
||||
|
||||
commitID, err := repo.CreateCommit("HEAD", sig, sig, message, tree, parentC...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// return unsigned commit if pgp entity is not provided
|
||||
if ent == nil {
|
||||
return commitID.String(), nil
|
||||
}
|
||||
|
||||
commit, err := repo.LookupCommit(commitID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer commit.Free()
|
||||
|
||||
signedCommitID, err := commit.WithSignatureUsing(func(commitContent string) (string, string, error) {
|
||||
cipherText := new(bytes.Buffer)
|
||||
err := openpgp.ArmoredDetachSignText(cipherText, ent, strings.NewReader(commitContent), &packet.Config{})
|
||||
if err != nil {
|
||||
return "", "", errors.New("error signing payload")
|
||||
}
|
||||
|
||||
return cipherText.String(), "", nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
signedCommit, err := repo.LookupCommit(signedCommitID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer signedCommit.Free()
|
||||
|
||||
newHead, err := repo.Head()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer newHead.Free()
|
||||
|
||||
ref, err := repo.References.Create(
|
||||
newHead.Name(),
|
||||
signedCommit.Id(),
|
||||
true,
|
||||
"repoint to signed commit",
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer ref.Free()
|
||||
|
||||
return signedCommitID.String(), nil
|
||||
}
|
||||
|
||||
// getSigningEntity retrieves an OpenPGP entity referenced by the
|
||||
// provided imagev1.ImageUpdateAutomation for git commit signing
|
||||
func (r *ImageUpdateAutomationReconciler) getSigningEntity(ctx context.Context, auto imagev1.ImageUpdateAutomation) (*openpgp.Entity, error) {
|
||||
// get kubernetes secret
|
||||
secretName := types.NamespacedName{
|
||||
Namespace: auto.GetNamespace(),
|
||||
Name: auto.Spec.GitSpec.Commit.SigningKey.SecretRef.Name,
|
||||
}
|
||||
var secret corev1.Secret
|
||||
if err := r.Get(ctx, secretName, &secret); err != nil {
|
||||
return nil, fmt.Errorf("could not find signing key secret '%s': %w", secretName, err)
|
||||
}
|
||||
|
||||
// get data from secret
|
||||
data, ok := secret.Data[signingSecretKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("signing key secret '%s' does not contain a 'git.asc' key", secretName)
|
||||
}
|
||||
|
||||
// read entity from secret value
|
||||
entities, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read signing key from secret '%s': %w", secretName, err)
|
||||
}
|
||||
if len(entities) > 1 {
|
||||
return nil, fmt.Errorf("multiple entities read from secret '%s', could not determine which signing key to use", secretName)
|
||||
}
|
||||
return entities[0], nil
|
||||
}
|
||||
|
||||
var errRemoteBranchMissing = errors.New("remote branch missing")
|
||||
|
||||
// switchToBranch switches to a branch after fetching latest from upstream.
|
||||
// If the branch does not exist, it is created using the head as the starting point.
|
||||
func switchToBranch(repo *libgit2.Repository, ctx context.Context, branch string, access repoAccess) error {
|
||||
origin, err := repo.Remotes.Lookup(originRemote)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot lookup remote: %w", err)
|
||||
}
|
||||
defer origin.Free()
|
||||
|
||||
// Override callbacks with dummy ones as they are not needed within Managed Transport.
|
||||
// However, not setting them may lead to git2go panicing.
|
||||
callbacks := managed.RemoteCallbacks()
|
||||
|
||||
// Force the fetching of the remote branch.
|
||||
err = origin.Fetch([]string{branch}, &libgit2.FetchOptions{
|
||||
RemoteCallbacks: callbacks,
|
||||
}, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot fetch remote branch: %w", err)
|
||||
}
|
||||
|
||||
remoteBranch, err := repo.References.Lookup(fmt.Sprintf("refs/remotes/origin/%s", branch))
|
||||
if err != nil && !libgit2.IsErrorCode(err, libgit2.ErrorCodeNotFound) {
|
||||
return err
|
||||
}
|
||||
if remoteBranch != nil {
|
||||
defer remoteBranch.Free()
|
||||
}
|
||||
err = nil
|
||||
|
||||
var commit *libgit2.Commit
|
||||
// tries to get tip commit from remote branch, if it exists.
|
||||
// otherwise gets the commit that local head is pointing to.
|
||||
if remoteBranch != nil {
|
||||
commit, err = repo.LookupCommit(remoteBranch.Target())
|
||||
} else {
|
||||
head, err := repo.Head()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get repo head: %w", err)
|
||||
}
|
||||
defer head.Free()
|
||||
commit, err = repo.LookupCommit(head.Target())
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot find the head commit: %w", err)
|
||||
}
|
||||
defer commit.Free()
|
||||
|
||||
localBranch, err := repo.References.Lookup(fmt.Sprintf("refs/heads/%s", branch))
|
||||
if err != nil && !libgit2.IsErrorCode(err, libgit2.ErrorCodeNotFound) {
|
||||
return fmt.Errorf("cannot lookup branch '%s': %w", branch, err)
|
||||
}
|
||||
if localBranch == nil {
|
||||
lb, err := repo.CreateBranch(branch, commit, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create branch '%s': %w", branch, err)
|
||||
}
|
||||
defer lb.Free()
|
||||
// We could've done something like:
|
||||
// localBranch = lb.Reference
|
||||
// But for some reason, calling `lb.Free()` AND using it, causes a really
|
||||
// nasty crash. Since, we can't avoid calling `lb.Free()`, in order to prevent
|
||||
// memory leaks, we don't use `lb` and instead manually lookup the ref.
|
||||
localBranch, err = repo.References.Lookup(fmt.Sprintf("refs/heads/%s", branch))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot lookup branch '%s': %w", branch, err)
|
||||
}
|
||||
}
|
||||
defer localBranch.Free()
|
||||
|
||||
tree, err := repo.LookupTree(commit.TreeId())
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot lookup tree for branch '%s': %w", branch, err)
|
||||
}
|
||||
defer tree.Free()
|
||||
|
||||
err = repo.CheckoutTree(tree, &libgit2.CheckoutOpts{
|
||||
// the remote branch should take precedence if it exists at this point in time.
|
||||
Strategy: libgit2.CheckoutForce,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot checkout tree for branch '%s': %w", branch, err)
|
||||
}
|
||||
|
||||
ref, err := localBranch.SetTarget(commit.Id(), "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update branch '%s' to be at target commit: %w", branch, err)
|
||||
}
|
||||
ref.Free()
|
||||
|
||||
return repo.SetHead("refs/heads/" + branch)
|
||||
}
|
||||
|
||||
// push pushes the branch given to the origin using the git library
|
||||
// indicated by `impl`. It's passed both the path to the repo and a
|
||||
// libgit2.Repository value, since the latter may as well be used if the
|
||||
// implementation is libgit2.
|
||||
func push(ctx context.Context, path, branch string, access repoAccess) error {
|
||||
repo, err := libgit2.OpenRepository(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer repo.Free()
|
||||
origin, err := repo.Remotes.Lookup(originRemote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer origin.Free()
|
||||
|
||||
// Override callbacks with dummy ones as they are not needed within Managed Transport.
|
||||
// However, not setting them may lead to git2go panicing.
|
||||
callbacks := managed.RemoteCallbacks()
|
||||
|
||||
// calling repo.Push will succeed even if a reference update is
|
||||
// rejected; to detect this case, this callback is supplied.
|
||||
var callbackErr error
|
||||
callbacks.PushUpdateReferenceCallback = func(refname, status string) error {
|
||||
if status != "" {
|
||||
callbackErr = fmt.Errorf("ref %s rejected: %s", refname, status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
err = origin.Push([]string{fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch)}, &libgit2.PushOptions{
|
||||
RemoteCallbacks: callbacks,
|
||||
ProxyOptions: libgit2.ProxyOptions{Type: libgit2.ProxyTypeAuto},
|
||||
})
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "early EOF") {
|
||||
return fmt.Errorf("%w (the SSH key may not have write access to the repository)", err)
|
||||
}
|
||||
return libgit2PushError(err)
|
||||
}
|
||||
return callbackErr
|
||||
}
|
||||
|
||||
func libgit2PushError(err error) error {
|
||||
if err == nil {
|
||||
return err
|
||||
}
|
||||
// libgit2 returns the whole output from stderr, and we only need
|
||||
// the message. GitLab likes to return a banner, so as an
|
||||
// heuristic, strip any lines that are just "remote:" and spaces
|
||||
// or fencing.
|
||||
msg := err.Error()
|
||||
lines := strings.Split(msg, "\n")
|
||||
if len(lines) == 1 {
|
||||
return err
|
||||
}
|
||||
var b strings.Builder
|
||||
// the following removes the prefix "remote:" from each line; to
|
||||
// retain a bit of fidelity to the original error, start with it.
|
||||
b.WriteString("remote: ")
|
||||
|
||||
var appending bool
|
||||
for _, line := range lines {
|
||||
m := strings.TrimPrefix(line, "remote:")
|
||||
if m = strings.Trim(m, " \t="); m != "" {
|
||||
if appending {
|
||||
b.WriteString(" ")
|
||||
}
|
||||
b.WriteString(m)
|
||||
appending = true
|
||||
}
|
||||
}
|
||||
return errors.New(b.String())
|
||||
}
|
||||
|
||||
// --- events, metrics
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) event(ctx context.Context, auto imagev1.ImageUpdateAutomation, severity, msg string) {
|
||||
eventtype := "Normal"
|
||||
if severity == events.EventSeverityError {
|
||||
eventtype = "Warning"
|
||||
}
|
||||
r.EventRecorder.Eventf(&auto, eventtype, severity, msg)
|
||||
}
|
||||
|
||||
// --- updates
|
||||
|
||||
// updateAccordingToSetters updates files under the root by treating
|
||||
// the given image policies as kyaml setters.
|
||||
func updateAccordingToSetters(ctx context.Context, tracelog logr.Logger, path string, policies []imagev1_reflect.ImagePolicy) (update.Result, error) {
|
||||
return update.UpdateWithSetters(tracelog, path, path, policies)
|
||||
}
|
||||
|
||||
// templateMsg renders a msg template, returning the message or an error.
|
||||
func templateMsg(messageTemplate string, templateValues *TemplateData) (string, error) {
|
||||
if messageTemplate == "" {
|
||||
messageTemplate = defaultMessageTemplate
|
||||
}
|
||||
|
||||
// Includes only functions that are guaranteed to always evaluate to the same result for given input.
|
||||
// This removes the possibility of accidentally relying on where or when the template runs.
|
||||
// https://github.com/Masterminds/sprig/blob/3ac42c7bc5e4be6aa534e036fb19dde4a996da2e/functions.go#L70
|
||||
t, err := template.New("commit message").Funcs(sprig.HermeticTxtFuncMap()).Parse(messageTemplate)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to create commit message template from spec: %w", err)
|
||||
}
|
||||
|
||||
b := &strings.Builder{}
|
||||
if err := t.Execute(b, *templateValues); err != nil {
|
||||
return "", fmt.Errorf("failed to run template from spec: %w", err)
|
||||
}
|
||||
return b.String(), nil
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
fuzz "github.com/AdaLogics/go-fuzz-headers"
|
||||
)
|
||||
|
||||
func Fuzz_templateMsg(f *testing.F) {
|
||||
f.Add("template", []byte{})
|
||||
f.Add("", []byte{})
|
||||
|
||||
f.Fuzz(func(t *testing.T, template string, seed []byte) {
|
||||
var values TemplateData
|
||||
fuzz.NewConsumer(seed).GenerateStruct(&values)
|
||||
|
||||
_, _ = templateMsg(template, &values)
|
||||
})
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
/surely/does/not/exist
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
|||
<h1>Image update automation API reference</h1>
|
||||
<h1>Image update automation API reference v1beta1</h1>
|
||||
<p>Packages:</p>
|
||||
<ul class="simple">
|
||||
<li>
|
||||
|
@ -207,7 +207,9 @@ string
|
|||
<td>
|
||||
<code>ref</code><br>
|
||||
<em>
|
||||
Source /v1beta2.GitRepositoryRef
|
||||
<a href="https://pkg.go.dev/github.com/fluxcd/source-controller/api/v1#GitRepositoryRef">
|
||||
Source /v1.GitRepositoryRef
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -358,7 +360,7 @@ other kinds of source allowed.</p>
|
|||
<td>
|
||||
<code>interval</code><br>
|
||||
<em>
|
||||
<a href="https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
||||
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
||||
Kubernetes meta/v1.Duration
|
||||
</a>
|
||||
</em>
|
||||
|
@ -466,7 +468,7 @@ other kinds of source allowed.</p>
|
|||
<td>
|
||||
<code>interval</code><br>
|
||||
<em>
|
||||
<a href="https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
||||
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
||||
Kubernetes meta/v1.Duration
|
||||
</a>
|
||||
</em>
|
||||
|
@ -596,7 +598,7 @@ int64
|
|||
<td>
|
||||
<code>ReconcileRequestStatus</code><br>
|
||||
<em>
|
||||
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#ReconcileRequestStatus">
|
||||
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#ReconcileRequestStatus">
|
||||
github.com/fluxcd/pkg/apis/meta.ReconcileRequestStatus
|
||||
</a>
|
||||
</em>
|
||||
|
@ -636,11 +638,42 @@ string
|
|||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Branch specifies that commits should be pushed to the branch
|
||||
named. The branch is created using <code>.spec.checkout.branch</code> as the
|
||||
starting point, if it doesn’t already exist.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>refspec</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Refspec specifies the Git Refspec to use for a push operation.
|
||||
If both Branch and Refspec are provided, then the commit is pushed
|
||||
to the branch and also using the specified refspec.
|
||||
For more details about Git Refspecs, see:
|
||||
<a href="https://git-scm.com/book/en/v2/Git-Internals-The-Refspec">https://git-scm.com/book/en/v2/Git-Internals-The-Refspec</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>options</code><br>
|
||||
<em>
|
||||
map[string]string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Options specifies the push options that are sent to the Git
|
||||
server when performing a push operation. For details, see:
|
||||
<a href="https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt">https://git-scm.com/docs/git-push#Documentation/git-push.txt—push-optionltoptiongt</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -666,7 +699,7 @@ starting point, if it doesn’t already exist.</p>
|
|||
<td>
|
||||
<code>secretRef</code><br>
|
||||
<em>
|
||||
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
||||
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
||||
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
|
||||
</a>
|
||||
</em>
|
|
@ -0,0 +1,907 @@
|
|||
<h1>Image update automation API reference v1beta2</h1>
|
||||
<p>Packages:</p>
|
||||
<ul class="simple">
|
||||
<li>
|
||||
<a href="#image.toolkit.fluxcd.io%2fv1beta2">image.toolkit.fluxcd.io/v1beta2</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h2 id="image.toolkit.fluxcd.io/v1beta2">image.toolkit.fluxcd.io/v1beta2</h2>
|
||||
<p>Package v1beta2 contains API types for the image API group, version
|
||||
v1beta2. The types here are concerned with automated updates to
|
||||
git, based on metadata from OCI image registries gathered by the
|
||||
image-reflector-controller.</p>
|
||||
Resource Types:
|
||||
<ul class="simple"></ul>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.CommitSpec">CommitSpec
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.GitSpec">GitSpec</a>)
|
||||
</p>
|
||||
<p>CommitSpec specifies how to commit changes to the git repository</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>author</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.CommitUser">
|
||||
CommitUser
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Author gives the email and optionally the name to use as the
|
||||
author of commits.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>signingKey</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.SigningKey">
|
||||
SigningKey
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>SigningKey provides the option to sign commits with a GPG key</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>messageTemplate</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>MessageTemplate provides a template for the commit message,
|
||||
into which will be interpolated the details of the change made.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>messageTemplateValues</code><br>
|
||||
<em>
|
||||
map[string]string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>MessageTemplateValues provides additional values to be available to the
|
||||
templating rendering.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.CommitUser">CommitUser
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.CommitSpec">CommitSpec</a>)
|
||||
</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>name</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Name gives the name to provide when making a commit.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>email</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Email gives the email to provide when making a commit.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.CrossNamespaceSourceReference">CrossNamespaceSourceReference
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationSpec">ImageUpdateAutomationSpec</a>)
|
||||
</p>
|
||||
<p>CrossNamespaceSourceReference contains enough information to let you locate the
|
||||
typed Kubernetes resource object at cluster level.</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>apiVersion</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>API version of the referent.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>kind</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Kind of the referent.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>name</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Name of the referent.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>namespace</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Namespace of the referent, defaults to the namespace of the Kubernetes resource object that contains the reference.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.GitCheckoutSpec">GitCheckoutSpec
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.GitSpec">GitSpec</a>)
|
||||
</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>ref</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/github.com/fluxcd/source-controller/api/v1#GitRepositoryRef">
|
||||
Source /v1.GitRepositoryRef
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Reference gives a branch, tag or commit to clone from the Git
|
||||
repository.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.GitSpec">GitSpec
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationSpec">ImageUpdateAutomationSpec</a>)
|
||||
</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>checkout</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.GitCheckoutSpec">
|
||||
GitCheckoutSpec
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Checkout gives the parameters for cloning the git repository,
|
||||
ready to make changes. If not present, the <code>spec.ref</code> field from the
|
||||
referenced <code>GitRepository</code> or its default will be used.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>commit</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.CommitSpec">
|
||||
CommitSpec
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Commit specifies how to commit to the git repository.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>push</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.PushSpec">
|
||||
PushSpec
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Push specifies how and where to push commits made by the
|
||||
automation. If missing, commits are pushed (back) to
|
||||
<code>.spec.checkout.branch</code> or its default.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.ImageRef">ImageRef
|
||||
</h3>
|
||||
<p>ImageRef represents an image reference.</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>name</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Name is the bare image’s name.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>tag</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Tag is the image’s tag.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>digest</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Digest is the image’s digest.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomation">ImageUpdateAutomation
|
||||
</h3>
|
||||
<p>ImageUpdateAutomation is the Schema for the imageupdateautomations API</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>metadata</code><br>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta">
|
||||
Kubernetes meta/v1.ObjectMeta
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
Refer to the Kubernetes API documentation for the fields of the
|
||||
<code>metadata</code> field.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>spec</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationSpec">
|
||||
ImageUpdateAutomationSpec
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<br/>
|
||||
<br/>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<code>sourceRef</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.CrossNamespaceSourceReference">
|
||||
CrossNamespaceSourceReference
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>SourceRef refers to the resource giving access details
|
||||
to a git repository.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>git</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.GitSpec">
|
||||
GitSpec
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>GitSpec contains all the git-specific definitions. This is
|
||||
technically optional, but in practice mandatory until there are
|
||||
other kinds of source allowed.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>interval</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
||||
Kubernetes meta/v1.Duration
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Interval gives an lower bound for how often the automation
|
||||
run should be attempted.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>policySelector</code><br>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#labelselector-v1-meta">
|
||||
Kubernetes meta/v1.LabelSelector
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>PolicySelector allows to filter applied policies based on labels.
|
||||
By default includes all policies in namespace.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>update</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.UpdateStrategy">
|
||||
UpdateStrategy
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Update gives the specification for how to update the files in
|
||||
the repository. This can be left empty, to use the default
|
||||
value.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>suspend</code><br>
|
||||
<em>
|
||||
bool
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Suspend tells the controller to not run this automation, until
|
||||
it is unset (or set to false). Defaults to false.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>status</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationStatus">
|
||||
ImageUpdateAutomationStatus
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationSpec">ImageUpdateAutomationSpec
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomation">ImageUpdateAutomation</a>)
|
||||
</p>
|
||||
<p>ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>sourceRef</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.CrossNamespaceSourceReference">
|
||||
CrossNamespaceSourceReference
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>SourceRef refers to the resource giving access details
|
||||
to a git repository.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>git</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.GitSpec">
|
||||
GitSpec
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>GitSpec contains all the git-specific definitions. This is
|
||||
technically optional, but in practice mandatory until there are
|
||||
other kinds of source allowed.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>interval</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
|
||||
Kubernetes meta/v1.Duration
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Interval gives an lower bound for how often the automation
|
||||
run should be attempted.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>policySelector</code><br>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#labelselector-v1-meta">
|
||||
Kubernetes meta/v1.LabelSelector
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>PolicySelector allows to filter applied policies based on labels.
|
||||
By default includes all policies in namespace.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>update</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.UpdateStrategy">
|
||||
UpdateStrategy
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Update gives the specification for how to update the files in
|
||||
the repository. This can be left empty, to use the default
|
||||
value.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>suspend</code><br>
|
||||
<em>
|
||||
bool
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Suspend tells the controller to not run this automation, until
|
||||
it is unset (or set to false). Defaults to false.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationStatus">ImageUpdateAutomationStatus
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomation">ImageUpdateAutomation</a>)
|
||||
</p>
|
||||
<p>ImageUpdateAutomationStatus defines the observed state of ImageUpdateAutomation</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>lastAutomationRunTime</code><br>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#time-v1-meta">
|
||||
Kubernetes meta/v1.Time
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>LastAutomationRunTime records the last time the controller ran
|
||||
this automation through to completion (even if no updates were
|
||||
made).</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>lastPushCommit</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>LastPushCommit records the SHA1 of the last commit made by the
|
||||
controller, for this automation object</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>lastPushTime</code><br>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#time-v1-meta">
|
||||
Kubernetes meta/v1.Time
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>LastPushTime records the time of the last pushed change.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>observedGeneration</code><br>
|
||||
<em>
|
||||
int64
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>conditions</code><br>
|
||||
<em>
|
||||
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#condition-v1-meta">
|
||||
[]Kubernetes meta/v1.Condition
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>observedPolicies</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ObservedPolicies">
|
||||
ObservedPolicies
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>ObservedPolicies is the list of observed ImagePolicies that were
|
||||
considered by the ImageUpdateAutomation update process.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>observedSourceRevision</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>ObservedPolicies []ObservedPolicy <code>json:"observedPolicies,omitempty"</code>
|
||||
ObservedSourceRevision is the last observed source revision. This can be
|
||||
used to determine if the source has been updated since last observation.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>ReconcileRequestStatus</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#ReconcileRequestStatus">
|
||||
github.com/fluxcd/pkg/apis/meta.ReconcileRequestStatus
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>
|
||||
(Members of <code>ReconcileRequestStatus</code> are embedded into this type.)
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.ObservedPolicies">ObservedPolicies
|
||||
(<code>map[string]./api/v1beta2.ImageRef</code> alias)</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationStatus">ImageUpdateAutomationStatus</a>)
|
||||
</p>
|
||||
<p>ObservedPolicies is a map of policy name and ImageRef of their latest
|
||||
ImageRef.</p>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.PushSpec">PushSpec
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.GitSpec">GitSpec</a>)
|
||||
</p>
|
||||
<p>PushSpec specifies how and where to push commits.</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>branch</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Branch specifies that commits should be pushed to the branch
|
||||
named. The branch is created using <code>.spec.checkout.branch</code> as the
|
||||
starting point, if it doesn’t already exist.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>refspec</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Refspec specifies the Git Refspec to use for a push operation.
|
||||
If both Branch and Refspec are provided, then the commit is pushed
|
||||
to the branch and also using the specified refspec.
|
||||
For more details about Git Refspecs, see:
|
||||
<a href="https://git-scm.com/book/en/v2/Git-Internals-The-Refspec">https://git-scm.com/book/en/v2/Git-Internals-The-Refspec</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>options</code><br>
|
||||
<em>
|
||||
map[string]string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Options specifies the push options that are sent to the Git
|
||||
server when performing a push operation. For details, see:
|
||||
<a href="https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt">https://git-scm.com/docs/git-push#Documentation/git-push.txt—push-optionltoptiongt</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.SigningKey">SigningKey
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.CommitSpec">CommitSpec</a>)
|
||||
</p>
|
||||
<p>SigningKey references a Kubernetes secret that contains a GPG keypair</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>secretRef</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
||||
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>SecretRef holds the name to a secret that contains a ‘git.asc’ key
|
||||
corresponding to the ASCII Armored file containing the GPG signing
|
||||
keypair as the value. It must be in the same namespace as the
|
||||
ImageUpdateAutomation.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.UpdateStrategy">UpdateStrategy
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.ImageUpdateAutomationSpec">ImageUpdateAutomationSpec</a>)
|
||||
</p>
|
||||
<p>UpdateStrategy is a union of the various strategies for updating
|
||||
the Git repository. Parameters for each strategy (if any) can be
|
||||
inlined here.</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>strategy</code><br>
|
||||
<em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.UpdateStrategyName">
|
||||
UpdateStrategyName
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Strategy names the strategy to be used.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>path</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Path to the directory containing the manifests to be updated.
|
||||
Defaults to ‘None’, which translates to the root path
|
||||
of the GitRepositoryRef.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="image.toolkit.fluxcd.io/v1beta2.UpdateStrategyName">UpdateStrategyName
|
||||
(<code>string</code> alias)</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#image.toolkit.fluxcd.io/v1beta2.UpdateStrategy">UpdateStrategy</a>)
|
||||
</p>
|
||||
<p>UpdateStrategyName is the type for names that go in
|
||||
.update.strategy. NB the value in the const immediately below.</p>
|
||||
<div class="admonition note">
|
||||
<p class="last">This page was automatically generated with <code>gen-crd-api-reference-docs</code></p>
|
||||
</div>
|
|
@ -1,6 +1,8 @@
|
|||
<!-- -*- fill-column: 100 -*- -->
|
||||
# Image Update Automations
|
||||
|
||||
<!-- menuweight:50 -->
|
||||
|
||||
The `ImageUpdateAutomation` type defines an automation process that will update a git repository,
|
||||
based on image policy objects in the same namespace.
|
||||
|
||||
|
@ -111,9 +113,8 @@ To be able to commit changes back, the referenced `GitRepository` object must re
|
|||
with write access; e.g., if using a GitHub deploy key, "Allow write access" should be checked when
|
||||
creating it. Only the `url`, `ref`, and `secretRef` fields of the `GitRepository` are used.
|
||||
|
||||
The [`gitImplementation` field][source-docs] in the referenced `GitRepository` is ignored. The
|
||||
automation controller cannot use shallow clones or submodules, so there is no reason to use the
|
||||
go-git implementation rather than libgit2.
|
||||
The [`gitImplementation` field][source-docs] in the referenced `GitRepository` is ignored. All
|
||||
reconciliations are executed using the `go-git` implementation.
|
||||
|
||||
Other fields particular to how the Git repository is used are in the `git` field, [described
|
||||
below](#git-specific-specification).
|
||||
|
@ -169,6 +170,9 @@ When `checkout` is given, it overrides the analogous field in the `GitRepository
|
|||
in `.spec.sourceRef`. You would use this to put automation commits on a different branch than that
|
||||
you are syncing, for example.
|
||||
|
||||
By default the controller will only do shallow clones, but this can be disabled by starting the controller
|
||||
with `--feature-gates=GitShallowClone=false`.
|
||||
|
||||
### Commit
|
||||
|
||||
The `.spec.git.commit` field gives details to use when making a commit to push to the Git repository:
|
||||
|
@ -224,7 +228,21 @@ will result in commits with the author `Fluxbot <flux@example.com>`.
|
|||
|
||||
The optional `signingKey` field can be used to provide a key to sign commits with. It holds a
|
||||
reference to a secret, which is expected to have a file called `git.asc` containing an
|
||||
ASCII-armoured PGP key.
|
||||
ASCII-armoured PGP key. If the private key is protected by a password, you can specify the same
|
||||
in the secret using the `passphrase` key.
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: signing-key
|
||||
namespace: default
|
||||
stringData:
|
||||
git.asc: |
|
||||
<ARMOR ENCODED PGP KEY>
|
||||
passphrase: <private-key-passphrase>
|
||||
```
|
||||
|
||||
The `messageTemplate` field is a string which will be used as a template for the commit message. If
|
||||
empty, there is a default message; but you will likely want to provide your own, especially if you
|
||||
|
@ -248,7 +266,7 @@ The message template is a [Go text template][go-text-template]. The data availab
|
|||
have this structure (not reproduced verbatim):
|
||||
|
||||
```go
|
||||
// controllers/imageupdateautomation_controller.go
|
||||
// internal/controller/imageupdateautomation_controller.go
|
||||
|
||||
// TemplateData is the type of the value given to the commit message
|
||||
// template.
|
||||
|
@ -380,6 +398,7 @@ spec:
|
|||
name: fluxcdbot
|
||||
```
|
||||
There are over 70 available functions. Some of them are defined by the [Go template language](https://pkg.go.dev/text/template) itself. Most of the others are part of the [Sprig template library](http://masterminds.github.io/sprig/).
|
||||
|
||||
### Push
|
||||
|
||||
The optional `push` field defines how commits are pushed to the origin.
|
||||
|
@ -390,19 +409,51 @@ type PushSpec struct {
|
|||
// Branch specifies that commits should be pushed to the branch
|
||||
// named. The branch is created using `.spec.checkout.branch` as the
|
||||
// starting point, if it doesn't already exist.
|
||||
// +required
|
||||
Branch string `json:"branch"`
|
||||
// +optional
|
||||
Branch string `json:"branch,omitempty"`
|
||||
|
||||
// Refspec specifies the Git Refspec to use for a push operation.
|
||||
// If both Branch and Refspec are provided, then the commit is pushed
|
||||
// to the branch and also using the specified refspec.
|
||||
// For more details about Git Refspecs, see:
|
||||
// https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
|
||||
// +optional
|
||||
Refspec string `json:"refspec,omitempty"`
|
||||
|
||||
// Options specifies the push options that are sent to the Git
|
||||
// server when performing a push operation. For details, see:
|
||||
// https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt
|
||||
Options map[string]string `json:"options,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
If `push` is not present, commits are made on the branch given in `.spec.git.checkout.branch` and
|
||||
If `.push` is not present, commits are made on the branch given in `.spec.git.checkout.branch` and
|
||||
pushed to the same branch at the origin. If `.spec.git.checkout` is not present, it will fall back
|
||||
to the branch given in the `GitRepository` referenced by `.spec.sourceRef`. If none of these yield a
|
||||
branch name, the automation will fail.
|
||||
|
||||
When `push` is present, the `branch` field specifies a branch to push to at the origin. The branch
|
||||
If `.push.refspec` is present, the refspec specified is used to perform the push operation.
|
||||
An example of a valid refspec is `refs/heads/branch:refs/heads/branch`. This allows users to
|
||||
push to an arbitary destination reference.
|
||||
|
||||
If `.push.branch` is present, the specified branch is pushed to at the origin. The branch
|
||||
will be created locally if it does not already exist, starting from the checkout branch. If it does
|
||||
already exist, updates will be calculated on top of any commits already on the branch.
|
||||
already exist, it will be overwritten with the cloned version plus the changes made by the
|
||||
controller. Alternatively, force push can be disabled by starting the controller with `--feature-gates=GitForcePushBranch=false`,
|
||||
in which case the updates will be calculated on top of any commits already on the push branch.
|
||||
Note that without force push in push branches, if the target branch is stale, the controller may not
|
||||
be able to conclude the operation and will consistently fail until the branch is either deleted or
|
||||
refreshed.
|
||||
|
||||
If both `.push.refspec` and `.push.branch` are specified, then the reconciler will perform
|
||||
two push operations, one to the specified branch and another using the specified refspec.
|
||||
This is particularly useful for working with Gerrit servers. For more information about this,
|
||||
please refer to the [Gerrit](#gerrit) section.
|
||||
|
||||
**Note:** If both `.push.refspec` and `.push.branch` are essentially equal to
|
||||
each other (for e.g.: `.push.refspec: refs/heads/main:refs/heads/main` and
|
||||
`.push.branch: main`), then the reconciler might fail to perform the second push
|
||||
operation and error out with an `already up-to-date` error.
|
||||
|
||||
In the following snippet, updates will be pushed as commits to the branch `auto`, and when that
|
||||
branch does not exist at the origin, it will be created locally starting from the branch `main`, and
|
||||
|
@ -418,6 +469,117 @@ spec:
|
|||
branch: auto
|
||||
```
|
||||
|
||||
In the following snippet, updates and commits will be made on the `main` branch locally.
|
||||
The commits will be then pushed using the `refs/heads/main:refs/heads/auto` refspec:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
git:
|
||||
checkout:
|
||||
ref:
|
||||
branch: main
|
||||
push:
|
||||
refspec: refs/heads/main:refs/heads/auto
|
||||
```
|
||||
|
||||
To specify the [push options](https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt)
|
||||
to be sent to the upstream Git server, use `.push.options`. These options can be
|
||||
used to perform operations as a result of the push. For example, using the below
|
||||
push options will open a GitLab Merge Request to the `release` branch
|
||||
automatically with the commit the controller pushed to the `dev` branch:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
git:
|
||||
push:
|
||||
branch: dev
|
||||
options:
|
||||
merge_request.create: ""
|
||||
merge_request.target: release
|
||||
```
|
||||
|
||||
#### Gerrit
|
||||
|
||||
[Gerrit](https://www.gerritcodereview.com/) operates differently from a
|
||||
standard Git server. Rather than sending individual commits to a branch,
|
||||
all changes are bundled into a single commit. This commit requires a distinct
|
||||
identifier separate from the commit SHA. Additionally, instead of initiating
|
||||
a Pull Request between branches, the commit is pushed using a refspec:
|
||||
`HEAD:refs/for/main`.
|
||||
|
||||
As the image-automation-controller is primarily designed to work with
|
||||
standard Git servers, these special characteristics necessitate a few
|
||||
workarounds. The following is an example configuration that works
|
||||
well with Gerrit:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
git:
|
||||
checkout:
|
||||
ref:
|
||||
branch: main
|
||||
commit:
|
||||
author:
|
||||
email: flux@localdomain
|
||||
name: flux
|
||||
messageTemplate: |
|
||||
Perform automatic image update
|
||||
|
||||
Automation name: {{ .AutomationObject }}
|
||||
|
||||
Files:
|
||||
{{ range $filename, $_ := .Updated.Files -}}
|
||||
- {{ $filename }}
|
||||
{{ end }}
|
||||
Objects:
|
||||
{{ range $resource, $_ := .Updated.Objects -}}
|
||||
- {{ $resource.Kind }} {{ $resource.Name }}
|
||||
{{ end }}
|
||||
Images:
|
||||
{{ range .Updated.Images -}}
|
||||
- {{ . }}
|
||||
{{ end }}
|
||||
{{- $ChangeId := .AutomationObject -}}
|
||||
{{- $ChangeId = printf "%s%s" $ChangeId ( .Updated.Files | toString ) -}}
|
||||
{{- $ChangeId = printf "%s%s" $ChangeId ( .Updated.Objects | toString ) -}}
|
||||
{{- $ChangeId = printf "%s%s" $ChangeId ( .Updated.Images | toString ) }}
|
||||
Change-Id: {{ printf "I%s" ( sha256sum $ChangeId | trunc 40 ) }}
|
||||
push:
|
||||
branch: auto
|
||||
refspec: refs/heads/auto:refs/heads/main
|
||||
```
|
||||
|
||||
This instructs the image-automation-controller to clone the repository using the
|
||||
`main` branch but execute its update logic and commit with the provided message
|
||||
template on the `auto` branch. Commits are then pushed to the `auto` branch,
|
||||
followed by pushing the `HEAD` of the `auto` branch to the `HEAD` of the remote
|
||||
`main` branch. The message template ensures the inclusion of a [Change-Id](https://gerrit-review.googlesource.com/Documentation/concept-changes.html#change-id)
|
||||
at the bottom of the commit message.
|
||||
|
||||
The initial branch push aims to prevent multiple
|
||||
[Patch Sets](https://gerrit-review.googlesource.com/Documentation/concept-patch-sets.html).
|
||||
If we exclude `.push.branch` and only specify
|
||||
`.push.refspec: refs/heads/main:refs/heads/main`, the desired [Change](https://gerrit-review.googlesource.com/Documentation/concept-changes.html)
|
||||
can be created as intended. However, when the controller freshly clones the
|
||||
`main` branch while a Change is open, it executes its update logic on `main`,
|
||||
leading to new commits being pushed with the same changes to the existing open
|
||||
Change. Specifying `.push.branch` circumvents this by instructing the controller
|
||||
to apply the update logic to the `auto` branch, already containing the desired
|
||||
commit. This approach is also recommended in the
|
||||
[Gerrit documentation](https://gerrit-review.googlesource.com/Documentation/intro-gerrit-walkthrough-github.html#create-change).
|
||||
|
||||
Another thing to note is the syntax of `.push.refspec`. Instead of it being
|
||||
`HEAD:refs/for/main`, commonly used by Gerrit users, we specify the full
|
||||
refname `refs/heads/auto` in the source part of the refpsec.
|
||||
|
||||
**Note:** A known limitation of using the image-automation-controller with
|
||||
Gerrit involves handling multiple concurrent Changes. This is due to the
|
||||
calculation of the Change-Id, relying on factors like file names and image
|
||||
tags. If the controller introduces a new file or modifies a previously updated
|
||||
image tag to a different one, it leads to a distinct Change-Id for the commit.
|
||||
Consequently, this action will trigger the creation of an additional Change,
|
||||
even when an existing Change containing outdated modifications remains open.
|
||||
|
||||
## Update strategy
|
||||
|
||||
The `.spec.update` field specifies how to carry out updates on the git repository. There is one
|
||||
|
@ -669,7 +831,7 @@ spec:
|
|||
```
|
||||
|
||||
[image-auto-guide]: https://fluxcd.io/flux/guides/image-update/#configure-image-update-for-custom-resources
|
||||
[git-repo-ref]: https://fluxcd.io/flux/components/source/gitrepositories/#specification
|
||||
[git-repo-ref]: https://fluxcd.io/flux/components/source/gitrepositories/#writing-a-gitrepository-spec
|
||||
[durations]: https://godoc.org/time#ParseDuration
|
||||
[source-docs]: https://fluxcd.io/flux/components/source/gitrepositories/#git-implementation
|
||||
[source-docs]: https://fluxcd.io/flux/components/source/api/v1beta2/#source.toolkit.fluxcd.io/v1beta2.GitRepositorySpec
|
||||
[go-text-template]: https://golang.org/pkg/text/template/
|
||||
|
|
File diff suppressed because it is too large
Load Diff
271
go.mod
271
go.mod
|
@ -1,165 +1,180 @@
|
|||
module github.com/fluxcd/image-automation-controller
|
||||
|
||||
go 1.18
|
||||
go 1.24.0
|
||||
|
||||
replace github.com/fluxcd/image-automation-controller/api => ./api
|
||||
|
||||
// Flux has its own git2go fork to enable changes in behaviour for improved
|
||||
// reliability.
|
||||
//
|
||||
// For more information refer to:
|
||||
// - fluxcd/image-automation-controller/#339.
|
||||
// - libgit2/git2go#918.
|
||||
replace github.com/libgit2/git2go/v33 => github.com/fluxcd/git2go/v33 v33.0.9-flux
|
||||
|
||||
require (
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20221007124625-37f5449ff7df
|
||||
github.com/Masterminds/sprig/v3 v3.2.2
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20220930113650-c6815a8c17ad
|
||||
github.com/cyphar/filepath-securejoin v0.2.3
|
||||
github.com/fluxcd/image-automation-controller/api v0.26.0
|
||||
github.com/fluxcd/image-reflector-controller/api v0.22.1
|
||||
github.com/fluxcd/pkg/apis/acl v0.1.0
|
||||
github.com/fluxcd/pkg/apis/meta v0.17.0
|
||||
github.com/fluxcd/pkg/gittestserver v0.7.0
|
||||
github.com/fluxcd/pkg/runtime v0.22.0
|
||||
github.com/fluxcd/pkg/ssh v0.6.0
|
||||
github.com/fluxcd/source-controller v0.31.0
|
||||
github.com/fluxcd/source-controller/api v0.31.0
|
||||
github.com/go-git/go-billy/v5 v5.3.1
|
||||
github.com/go-git/go-git/v5 v5.4.2
|
||||
github.com/go-logr/logr v1.2.3
|
||||
github.com/google/go-containerregistry v0.11.0
|
||||
github.com/libgit2/git2go/v33 v33.0.9
|
||||
github.com/onsi/gomega v1.22.1
|
||||
github.com/otiai10/copy v1.7.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
golang.org/x/crypto v0.1.0
|
||||
k8s.io/api v0.25.3
|
||||
k8s.io/apimachinery v0.25.3
|
||||
k8s.io/client-go v0.25.3
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280
|
||||
sigs.k8s.io/controller-runtime v0.13.0
|
||||
sigs.k8s.io/kustomize/kyaml v0.13.9
|
||||
// Pin kustomize to v5.6.0
|
||||
replace (
|
||||
sigs.k8s.io/kustomize/api => sigs.k8s.io/kustomize/api v0.19.0
|
||||
sigs.k8s.io/kustomize/kyaml => sigs.k8s.io/kustomize/kyaml v0.19.0
|
||||
)
|
||||
|
||||
// Fix CVE-2022-32149
|
||||
replace golang.org/x/text => golang.org/x/text v0.4.0
|
||||
|
||||
// Fix CVE-2022-1996 (for v2, Go Modules incompatible)
|
||||
replace github.com/emicklei/go-restful => github.com/emicklei/go-restful v2.16.0+incompatible
|
||||
require (
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/ProtonMail/go-crypto v1.2.0
|
||||
github.com/cyphar/filepath-securejoin v0.4.1
|
||||
github.com/fluxcd/image-automation-controller/api v0.41.1
|
||||
github.com/fluxcd/image-reflector-controller/api v0.35.2
|
||||
github.com/fluxcd/pkg/apis/acl v0.7.0
|
||||
github.com/fluxcd/pkg/apis/event v0.17.0
|
||||
github.com/fluxcd/pkg/apis/meta v1.12.0
|
||||
github.com/fluxcd/pkg/auth v0.17.0
|
||||
github.com/fluxcd/pkg/cache v0.9.0
|
||||
github.com/fluxcd/pkg/git v0.32.0
|
||||
github.com/fluxcd/pkg/git/gogit v0.35.0
|
||||
github.com/fluxcd/pkg/gittestserver v0.17.0
|
||||
github.com/fluxcd/pkg/runtime v0.60.0
|
||||
github.com/fluxcd/pkg/ssh v0.19.0
|
||||
github.com/fluxcd/source-controller/api v1.6.1
|
||||
github.com/go-git/go-billy/v5 v5.6.2
|
||||
github.com/go-git/go-git/v5 v5.16.2
|
||||
github.com/go-logr/logr v1.4.2
|
||||
github.com/google/go-containerregistry v0.20.3
|
||||
github.com/onsi/gomega v1.37.0
|
||||
github.com/otiai10/copy v1.14.1
|
||||
github.com/spf13/pflag v1.0.6
|
||||
k8s.io/api v0.33.0
|
||||
k8s.io/apimachinery v0.33.0
|
||||
k8s.io/client-go v0.33.0
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff
|
||||
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e
|
||||
sigs.k8s.io/controller-runtime v0.21.0
|
||||
sigs.k8s.io/kustomize/kyaml v0.19.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.10.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
|
||||
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.3 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.43.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.33.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect
|
||||
github.com/aws/smithy-go v1.22.2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.2 // indirect
|
||||
github.com/cloudflare/circl v1.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/bradleyfalzon/ghinstallation/v2 v2.15.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.3 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/cli v27.5.0+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.2 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
|
||||
github.com/fluxcd/cli-utils v0.36.0-flux.13 // indirect
|
||||
github.com/fluxcd/gitkit v0.6.0 // indirect
|
||||
github.com/fluxcd/pkg/gitutil v0.2.0 // indirect
|
||||
github.com/fluxcd/pkg/version v0.2.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-git/gcfg v1.5.0 // indirect
|
||||
github.com/go-logr/zapr v1.2.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/gofrs/uuid v4.2.0+incompatible // indirect
|
||||
github.com/fluxcd/pkg/version v0.7.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
|
||||
github.com/go-errors/errors v1.5.1 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-logr/zapr v1.3.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/gnostic v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/gnostic-models v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/go-github/v71 v71.0.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-hclog v1.3.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/matryer/is v1.4.0 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/moby/spdystream v0.5.0 // indirect
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/otiai10/mint v1.6.3 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/client_golang v1.13.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.0 // indirect
|
||||
github.com/russross/blackfriday v1.6.0 // indirect
|
||||
github.com/sergi/go-diff v1.2.0 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/cobra v1.6.0 // indirect
|
||||
github.com/stretchr/objx v0.4.0 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.1 // indirect
|
||||
github.com/xlab/treeprint v1.1.0 // indirect
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/goleak v1.2.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
go.uber.org/zap v1.23.0 // indirect
|
||||
golang.org/x/mod v0.6.0 // indirect
|
||||
golang.org/x/net v0.1.0 // indirect
|
||||
golang.org/x/oauth2 v0.1.0 // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
golang.org/x/term v0.1.0 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
golang.org/x/time v0.1.0 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
github.com/prometheus/client_golang v1.22.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.63.0 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/spf13/cast v1.8.0 // indirect
|
||||
github.com/spf13/cobra v1.9.1 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xlab/treeprint v1.2.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/crypto v0.39.0 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/oauth2 v0.29.0 // indirect
|
||||
golang.org/x/sync v0.15.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/term v0.32.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gotest.tools/v3 v3.1.0 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.25.2 // indirect
|
||||
k8s.io/cli-runtime v0.25.2 // indirect
|
||||
k8s.io/component-base v0.25.2 // indirect
|
||||
k8s.io/klog/v2 v2.80.1 // indirect
|
||||
k8s.io/kubectl v0.25.2 // indirect
|
||||
k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85 // indirect
|
||||
sigs.k8s.io/cli-utils v0.33.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.12.1 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.33.0 // indirect
|
||||
k8s.io/cli-runtime v0.33.0 // indirect
|
||||
k8s.io/component-base v0.33.0 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kubectl v0.33.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.19.0 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"externalPackages": [
|
||||
{
|
||||
"typeMatchPrefix": "^k8s\\.io/apimachinery/pkg/apis/meta/v1\\.Duration$",
|
||||
"docsURLTemplate": "https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Duration"
|
||||
"docsURLTemplate": "https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration"
|
||||
},
|
||||
{
|
||||
"typeMatchPrefix": "^k8s\\.io/(api|apimachinery/pkg/apis)/",
|
||||
|
@ -17,15 +17,15 @@
|
|||
},
|
||||
{
|
||||
"typeMatchPrefix": "^github.com/fluxcd/pkg/runtime/dependency\\.CrossNamespaceDependencyReference$",
|
||||
"docsURLTemplate": "https://godoc.org/github.com/fluxcd/pkg/runtime/dependency#CrossNamespaceDependencyReference"
|
||||
"docsURLTemplate": "https://pkg.go.dev/github.com/fluxcd/pkg/runtime/dependency#CrossNamespaceDependencyReference"
|
||||
},
|
||||
{
|
||||
"typeMatchPrefix": "^github.com/fluxcd/pkg/apis/meta",
|
||||
"docsURLTemplate": "https://godoc.org/github.com/fluxcd/pkg/apis/meta#{{ .TypeIdentifier }}"
|
||||
"docsURLTemplate": "https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#{{ .TypeIdentifier }}"
|
||||
},
|
||||
{
|
||||
"typeMatchPrefix": "^github.com/fluxcd/source-controller/api/v1beta1",
|
||||
"docsURLTemplate": "https://godoc.org/github.com/fluxcd/source-controller/api/v1beta2#{{ .TypeIdentifier }}"
|
||||
"typeMatchPrefix": "^github.com/fluxcd/source-controller/api/v1",
|
||||
"docsURLTemplate": "https://pkg.go.dev/github.com/fluxcd/source-controller/api/v1#{{ .TypeIdentifier }}"
|
||||
}
|
||||
],
|
||||
"typeDisplayNamePrefixOverrides": {
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
{{ define "packages" }}
|
||||
<h1>Image update automation API reference</h1>
|
||||
<h1>Image update automation API reference
|
||||
{{- with (index .packages 0) -}}
|
||||
{{ with (index .GoPackages 0 ) -}}
|
||||
{{ printf " %s" .Name -}}
|
||||
{{ end -}}
|
||||
{{ end }}</h1>
|
||||
|
||||
{{ with .packages}}
|
||||
<p>Packages:</p>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 The Flux authors
|
||||
Copyright 2024 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.
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
IMG_TAG="${IMG_TAG:-.}"
|
||||
|
||||
function extract(){
|
||||
PLATFORM=$1
|
||||
DIR=$2
|
||||
|
||||
id=$(docker create --platform="${PLATFORM}" "${IMG_TAG}")
|
||||
docker cp "${id}":/usr/local - > output.tar.gz
|
||||
docker rm -v "${id}"
|
||||
|
||||
tar -xf output.tar.gz "local/${DIR}"
|
||||
rm output.tar.gz
|
||||
}
|
||||
|
||||
function setup() {
|
||||
PLATFORM=$1
|
||||
DIR=$2
|
||||
|
||||
extract "${PLATFORM}" "${DIR}"
|
||||
|
||||
NEW_DIR="$(/bin/pwd)/build/libgit2"
|
||||
INSTALLED_DIR="/usr/local/${DIR}"
|
||||
|
||||
mkdir -p "./build"
|
||||
|
||||
# Make a few movements to account for the change in
|
||||
# behaviour in tar between MacOS and Linux
|
||||
mv "local/${DIR}/" "libgit2"
|
||||
rm -rf "local"
|
||||
mv "libgit2/" "./build/"
|
||||
|
||||
# Update the prefix paths included in the .pc files.
|
||||
# This will make it easier to update to the location in which they will be used.
|
||||
if [[ $OSTYPE == 'darwin'* ]]; then
|
||||
# sed has a sight different behaviour in MacOS
|
||||
find "${NEW_DIR}" -type f -name "*.pc" | xargs -I {} sed -i "" "s;${INSTALLED_DIR};${NEW_DIR};g" {}
|
||||
else
|
||||
find "${NEW_DIR}" -type f -name "*.pc" | xargs -I {} sed -i "s;${INSTALLED_DIR};${NEW_DIR};g" {}
|
||||
fi
|
||||
}
|
||||
|
||||
function setup_current() {
|
||||
if [ -d "./build/libgit2" ]; then
|
||||
echo "Skipping libgit2 setup as it already exists"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
DIR="x86_64-alpine-linux-musl"
|
||||
PLATFORM="linux/amd64"
|
||||
|
||||
if [[ "$(uname -m)" == armv7* ]]; then
|
||||
DIR="armv7-alpine-linux-musleabihf"
|
||||
PLATFORM="linux/arm/v7"
|
||||
elif [ "$(uname -m)" = "arm64" ] || [ "$(uname -m)" = "aarch64" ]; then
|
||||
DIR="aarch64-alpine-linux-musl"
|
||||
PLATFORM="linux/arm64"
|
||||
fi
|
||||
|
||||
setup "${PLATFORM}" "${DIR}"
|
||||
}
|
||||
|
||||
setup_current
|
|
@ -1,157 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
IMG="${IMG:-}"
|
||||
TAG="${TAG:-}"
|
||||
IMG_TAG="${IMG}:${TAG}"
|
||||
DOWNLOAD_URL="https://github.com/fluxcd/golang-with-libgit2/releases/download/${TAG}"
|
||||
SKIP_COSIGN_VERIFICATION="${SKIP_COSIGN_VERIFICATION:-false}"
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
|
||||
function cleanup(){
|
||||
rm -rf "${TMP_DIR}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
fatal() {
|
||||
echo '[ERROR] ' "$@" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
download() {
|
||||
[[ $# -eq 2 ]] || fatal 'download needs exactly 2 arguments'
|
||||
|
||||
curl -o "$1" -sfL "$2"
|
||||
|
||||
[[ $? -eq 0 ]] || fatal 'Download failed'
|
||||
}
|
||||
|
||||
download_files() {
|
||||
[[ $# -eq 1 ]] || fatal 'download_files needs exactly 1 arguments'
|
||||
|
||||
FILE_NAMES="checksums.txt checksums.txt.sig checksums.txt.pem $1"
|
||||
|
||||
for FILE_NAME in ${FILE_NAMES}; do
|
||||
download "${TMP_DIR}/${FILE_NAME}" "${DOWNLOAD_URL}/${FILE_NAME}"
|
||||
done
|
||||
}
|
||||
|
||||
cosign_verify(){
|
||||
[[ $# -eq 3 ]] || fatal 'cosign_verify needs exactly 3 arguments'
|
||||
|
||||
COSIGN_EXPERIMENTAL=1 cosign verify-blob --cert "$1" --signature "$2" "$3"
|
||||
|
||||
[[ $? -eq 0 ]] || fatal 'signature verification failed'
|
||||
}
|
||||
|
||||
assure_provenance() {
|
||||
[[ $# -eq 1 ]] || fatal 'assure_provenance needs exactly 1 arguments'
|
||||
|
||||
if "${SKIP_COSIGN_VERIFICATION}"; then
|
||||
echo 'Skipping cosign verification...'
|
||||
else
|
||||
cosign_verify "${TMP_DIR}/checksums.txt.pem" \
|
||||
"${TMP_DIR}/checksums.txt.sig" \
|
||||
"${TMP_DIR}/checksums.txt"
|
||||
fi
|
||||
|
||||
pushd "${TMP_DIR}" || exit
|
||||
if command -v sha256sum; then
|
||||
grep "$1" "checksums.txt" | sha256sum --check
|
||||
else
|
||||
grep "$1" "checksums.txt" | shasum -a 256 --check
|
||||
fi
|
||||
popd || exit
|
||||
|
||||
[[ $? -eq 0 ]] || fatal 'integrity verification failed'
|
||||
}
|
||||
|
||||
extract_libraries(){
|
||||
[[ $# -eq 2 ]] || fatal 'extract_libraries needs exactly 2 arguments'
|
||||
|
||||
tar -xf "${TMP_DIR}/$1"
|
||||
|
||||
rm "${TMP_DIR}/$1"
|
||||
mv "${2}" "${TAG}"
|
||||
mv "${TAG}/" "./build/libgit2"
|
||||
}
|
||||
|
||||
fix_pkgconfigs(){
|
||||
DIR="$1"
|
||||
NEW_DIR="$(/bin/pwd)/build/libgit2/${TAG}"
|
||||
|
||||
# Update the prefix paths included in the .pc files.
|
||||
if [[ $OSTYPE == 'darwin'* ]]; then
|
||||
INSTALLED_DIR="/Users/runner/work/golang-with-libgit2/golang-with-libgit2/build/${DIR}"
|
||||
|
||||
# This will make it easier to update to the location in which they will be used.
|
||||
# sed has a sight different behaviour in MacOS
|
||||
# NB: Some macOS users may override their sed with gsed. If gsed is the PATH, use that instead.
|
||||
if command -v gsed &> /dev/null; then
|
||||
find "${NEW_DIR}" -type f -name "*.pc" | xargs -I {} gsed -i "s;${INSTALLED_DIR};${NEW_DIR};g" {}
|
||||
else
|
||||
find "${NEW_DIR}" -type f -name "*.pc" | xargs -I {} sed -i "" "s;${INSTALLED_DIR};${NEW_DIR};g" {}
|
||||
fi
|
||||
else
|
||||
INSTALLED_DIR="/home/runner/work/golang-with-libgit2/golang-with-libgit2/build/${DIR}"
|
||||
|
||||
find "${NEW_DIR}" -type f -name "*.pc" | xargs -I {} sed -i "s;${INSTALLED_DIR};${NEW_DIR};g" {}
|
||||
fi
|
||||
}
|
||||
|
||||
extract_from_image(){
|
||||
PLATFORM=$1
|
||||
DIR=$2
|
||||
|
||||
id=$(docker create --platform="${PLATFORM}" "${IMG_TAG}" sh)
|
||||
docker cp "${id}":/usr/local - > output.tar.gz
|
||||
docker rm -v "${id}"
|
||||
|
||||
tar -xf output.tar.gz "local/${DIR}"
|
||||
rm output.tar.gz
|
||||
|
||||
NEW_DIR="$(/bin/pwd)/build/libgit2/${TAG}"
|
||||
INSTALLED_DIR="/usr/local/${DIR}"
|
||||
|
||||
mv "local/${DIR}" "${TAG}"
|
||||
rm -rf "local"
|
||||
mv "${TAG}/" "./build/libgit2"
|
||||
|
||||
# Update the prefix paths included in the .pc files.
|
||||
# This will make it easier to update to the location in which they will be used.
|
||||
find "${NEW_DIR}" -type f -name "*.pc" | xargs -I {} sed -i "s;${INSTALLED_DIR};${NEW_DIR};g" {}
|
||||
}
|
||||
|
||||
install_libraries(){
|
||||
if [ -d "./build/libgit2/${TAG}" ]; then
|
||||
echo "Skipping: libgit2 ${TAG} already installed"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "./build/libgit2"
|
||||
|
||||
# Linux ARM support is still based on the container image libraries.
|
||||
if [[ $OSTYPE == 'linux'* ]]; then
|
||||
if [ "$(uname -m)" = "arm64" ] || [ "$(uname -m)" = "aarch64" ]; then
|
||||
extract_from_image "linux/arm64" "aarch64-alpine-linux-musl"
|
||||
fix_pkgconfigs "aarch64-alpine-linux-musl"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
FILE_NAME="linux-$(uname -m)-libgit2-only.tar.gz"
|
||||
DIR="linux-libgit2-only"
|
||||
if [[ $OSTYPE == 'darwin'* ]]; then
|
||||
FILE_NAME="darwin-libgit2-only.tar.gz"
|
||||
DIR="darwin-libgit2-only"
|
||||
fi
|
||||
|
||||
download_files "${FILE_NAME}"
|
||||
assure_provenance "${FILE_NAME}"
|
||||
extract_libraries "${FILE_NAME}" "${DIR}"
|
||||
fix_pkgconfigs "${DIR}"
|
||||
}
|
||||
|
||||
install_libraries
|
|
@ -1,73 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -eoux pipefail
|
||||
|
||||
SPLIT="***"
|
||||
|
||||
cat <<EOF > ATTRIBUTIONS.md
|
||||
# Attributions
|
||||
|
||||
This application uses Open Source components. You can find the source
|
||||
code of their open source projects along with license information below.
|
||||
We acknowledge and are grateful to these developers for their contributions
|
||||
to open source.
|
||||
|
||||
## libssh2
|
||||
|
||||
Libssh2 was obtained in source-code form from its github repository:
|
||||
https://github.com/libssh2/libssh2/
|
||||
|
||||
No changes were made to its original source code.
|
||||
|
||||
Copyright notice (https://raw.githubusercontent.com/libssh2/libssh2/master/COPYING):
|
||||
|
||||
$(curl --max-time 5 -L https://raw.githubusercontent.com/libssh2/libssh2/master/COPYING)
|
||||
|
||||
${SPLIT}
|
||||
|
||||
## libgit2
|
||||
|
||||
Libgit2 was obtained in source-code form from its github repository:
|
||||
https://github.com/libgit2/libgit2/
|
||||
|
||||
No changes were made to its original source code.
|
||||
|
||||
Copyright notice (https://raw.githubusercontent.com/libgit2/libgit2/main/COPYING):
|
||||
|
||||
$(curl --max-time 5 -L https://raw.githubusercontent.com/libgit2/libgit2/main/COPYING)
|
||||
|
||||
${SPLIT}
|
||||
|
||||
## zlib
|
||||
|
||||
Zlib was obtained in binary form via official distribution channels.
|
||||
No changes were made to its original source code.
|
||||
|
||||
Copyright notice (https://zlib.net/zlib_license.html):
|
||||
|
||||
/* zlib.h -- interface of the 'zlib' general purpose compression library
|
||||
version 1.2.11, January 15th, 2017
|
||||
|
||||
Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
Jean-loup Gailly Mark Adler
|
||||
jloup@gzip.org madler@alumni.caltech.edu
|
||||
|
||||
*/
|
||||
EOF
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 The Flux authors
|
||||
Copyright 2025 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.
|
||||
|
@ -14,12 +14,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package v1alpha2 contains API types for the image v1alpha2 API
|
||||
// group. The types here are concerned with automated updates to git,
|
||||
// based on metadata from OCI image registries gathered by the
|
||||
// image-reflector-controller. There is some rearrangement from
|
||||
// v1alpha1 to make room for future enhancements.
|
||||
//
|
||||
// +kubebuilder:object:generate=true
|
||||
// +groupName=image.toolkit.fluxcd.io
|
||||
package v1alpha2
|
||||
package constants
|
||||
|
||||
const (
|
||||
// SetterShortHand is a shorthand that can be used to mark
|
||||
// setters; instead of
|
||||
// # { "$ref": "#/definitions/
|
||||
SetterShortHand = "$imagepolicy"
|
||||
)
|
|
@ -1,5 +1,5 @@
|
|||
//go:build gofuzz
|
||||
// +build gofuzz
|
||||
//go:build gofuzz_libfuzzer
|
||||
// +build gofuzz_libfuzzer
|
||||
|
||||
/*
|
||||
Copyright 2021 The Flux authors
|
||||
|
@ -17,7 +17,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controllers
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -29,36 +29,36 @@ import (
|
|||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
fuzz "github.com/AdaLogics/go-fuzz-headers"
|
||||
"github.com/fluxcd/image-automation-controller/controllers"
|
||||
"github.com/fluxcd/image-automation-controller/pkg/update"
|
||||
"github.com/fluxcd/pkg/gittestserver"
|
||||
"github.com/fluxcd/pkg/runtime/testenv"
|
||||
"github.com/go-git/go-billy/v5/memfs"
|
||||
"github.com/go-git/go-git/v5"
|
||||
gogit "github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/config"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
|
||||
"k8s.io/client-go/rest"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
|
||||
image_automationv1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||
image_reflectv1 "github.com/fluxcd/image-reflector-controller/api/v1beta1"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
image_reflectv1 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
"github.com/fluxcd/pkg/gittestserver"
|
||||
"github.com/fluxcd/pkg/runtime/controller"
|
||||
"github.com/fluxcd/pkg/runtime/testenv"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
image_automationv1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
"github.com/fluxcd/image-automation-controller/pkg/update"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -85,7 +85,9 @@ func Fuzz_ImageUpdateReconciler(f *testing.F) {
|
|||
utilruntime.Must(ensureDependencies(func(m manager.Manager) {
|
||||
utilruntime.Must((&ImageUpdateAutomationReconciler{
|
||||
Client: m.GetClient(),
|
||||
}).SetupWithManager(m, ImageUpdateAutomationReconcilerOptions{MaxConcurrentReconciles: 4}))
|
||||
}).SetupWithManager(context.TODO(), m, ImageUpdateAutomationReconcilerOptions{
|
||||
RateLimiter: controller.GetDefaultRateLimiter(),
|
||||
}))
|
||||
}))
|
||||
})
|
||||
|
||||
|
@ -331,7 +333,7 @@ func FuzzUpdateWithSetters(f *testing.F) {
|
|||
// Initialise a git server with a repo including the files in dir.
|
||||
func initGitRepo(gitServer *gittestserver.GitServer, fixture, branch, repositoryPath string) error {
|
||||
fs := memfs.New()
|
||||
repo, err := git.Init(memory.NewStorage(), fs)
|
||||
repo, err := gogit.Init(memory.NewStorage(), fs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -345,7 +347,7 @@ func initGitRepo(gitServer *gittestserver.GitServer, fixture, branch, repository
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = working.Checkout(&git.CheckoutOptions{
|
||||
if err = working.Checkout(&gogit.CheckoutOptions{
|
||||
Branch: plumbing.NewBranchReferenceName(branch),
|
||||
Create: true,
|
||||
}); err != nil {
|
||||
|
@ -360,7 +362,7 @@ func initGitRepo(gitServer *gittestserver.GitServer, fixture, branch, repository
|
|||
return err
|
||||
}
|
||||
|
||||
return remote.Push(&git.PushOptions{
|
||||
return remote.Push(&gogit.PushOptions{
|
||||
RefSpecs: []config.RefSpec{
|
||||
config.RefSpec(fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch)),
|
||||
},
|
|
@ -0,0 +1,642 @@
|
|||
/*
|
||||
Copyright 2024 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 controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
kerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
kuberecorder "k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
aclapi "github.com/fluxcd/pkg/apis/acl"
|
||||
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/fluxcd/pkg/cache"
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"github.com/fluxcd/pkg/runtime/acl"
|
||||
"github.com/fluxcd/pkg/runtime/conditions"
|
||||
helper "github.com/fluxcd/pkg/runtime/controller"
|
||||
"github.com/fluxcd/pkg/runtime/patch"
|
||||
"github.com/fluxcd/pkg/runtime/predicates"
|
||||
runtimereconcile "github.com/fluxcd/pkg/runtime/reconcile"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
"github.com/fluxcd/image-automation-controller/internal/features"
|
||||
"github.com/fluxcd/image-automation-controller/internal/policy"
|
||||
"github.com/fluxcd/image-automation-controller/internal/source"
|
||||
)
|
||||
|
||||
const repoRefKey = ".spec.gitRepository"
|
||||
|
||||
const readyMessage = "repository up-to-date"
|
||||
|
||||
// imageUpdateAutomationOwnedConditions is a list of conditions owned by the
|
||||
// ImageUpdateAutomationReconciler.
|
||||
var imageUpdateAutomationOwnedConditions = []string{
|
||||
meta.ReadyCondition,
|
||||
meta.ReconcilingCondition,
|
||||
meta.StalledCondition,
|
||||
}
|
||||
|
||||
// imageUpdateAutomationNegativeConditions is a list of negative polarity
|
||||
// conditions owned by ImageUpdateAutomationReconciler. It is used in tests for
|
||||
// compliance with kstatus.
|
||||
var imageUpdateAutomationNegativeConditions = []string{
|
||||
meta.StalledCondition,
|
||||
meta.ReconcilingCondition,
|
||||
}
|
||||
|
||||
var errParsePolicySelector = errors.New("failed to parse policy selector")
|
||||
|
||||
// getPatchOptions composes patch options based on the given parameters.
|
||||
// It is used as the options used when patching an object.
|
||||
func getPatchOptions(ownedConditions []string, controllerName string) []patch.Option {
|
||||
return []patch.Option{
|
||||
patch.WithOwnedConditions{Conditions: ownedConditions},
|
||||
patch.WithFieldOwner(controllerName),
|
||||
}
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=image.toolkit.fluxcd.io,resources=imageupdateautomations,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=image.toolkit.fluxcd.io,resources=imageupdateautomations/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups=image.toolkit.fluxcd.io,resources=imagepolicies,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups=image.toolkit.fluxcd.io,resources=imagepolicies/status,verbs=get
|
||||
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=gitrepositories,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch
|
||||
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
|
||||
|
||||
// ImageUpdateAutomationReconciler reconciles a ImageUpdateAutomation object
|
||||
type ImageUpdateAutomationReconciler struct {
|
||||
client.Client
|
||||
kuberecorder.EventRecorder
|
||||
helper.Metrics
|
||||
|
||||
ControllerName string
|
||||
NoCrossNamespaceRef bool
|
||||
|
||||
features map[string]bool
|
||||
|
||||
patchOptions []patch.Option
|
||||
|
||||
tokenCache *cache.TokenCache
|
||||
}
|
||||
|
||||
type ImageUpdateAutomationReconcilerOptions struct {
|
||||
MaxConcurrentReconciles int
|
||||
RateLimiter workqueue.TypedRateLimiter[reconcile.Request]
|
||||
RecoverPanic bool
|
||||
TokenCache *cache.TokenCache
|
||||
}
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opts ImageUpdateAutomationReconcilerOptions) error {
|
||||
r.patchOptions = getPatchOptions(imageUpdateAutomationOwnedConditions, r.ControllerName)
|
||||
|
||||
if r.features == nil {
|
||||
r.features = features.FeatureGates()
|
||||
}
|
||||
|
||||
r.tokenCache = opts.TokenCache
|
||||
|
||||
// Index the git repository object that each I-U-A refers to
|
||||
if err := mgr.GetFieldIndexer().IndexField(ctx, &imagev1.ImageUpdateAutomation{}, repoRefKey, func(obj client.Object) []string {
|
||||
updater := obj.(*imagev1.ImageUpdateAutomation)
|
||||
ref := updater.Spec.SourceRef
|
||||
return []string{ref.Name}
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&imagev1.ImageUpdateAutomation{}, builder.WithPredicates(
|
||||
predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}))).
|
||||
Watches(
|
||||
&sourcev1.GitRepository{},
|
||||
handler.EnqueueRequestsFromMapFunc(r.automationsForGitRepo),
|
||||
builder.WithPredicates(sourceConfigChangePredicate{}),
|
||||
).
|
||||
Watches(
|
||||
&imagev1_reflect.ImagePolicy{},
|
||||
handler.EnqueueRequestsFromMapFunc(r.automationsForImagePolicy),
|
||||
builder.WithPredicates(latestImageChangePredicate{}),
|
||||
).
|
||||
WithOptions(controller.Options{
|
||||
RateLimiter: opts.RateLimiter,
|
||||
}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
// automationsForGitRepo fetches all the automations that refer to a
|
||||
// particular source.GitRepository object.
|
||||
func (r *ImageUpdateAutomationReconciler) automationsForGitRepo(ctx context.Context, obj client.Object) []reconcile.Request {
|
||||
var autoList imagev1.ImageUpdateAutomationList
|
||||
if err := r.List(ctx, &autoList, client.InNamespace(obj.GetNamespace()),
|
||||
client.MatchingFields{repoRefKey: obj.GetName()}); err != nil {
|
||||
ctrl.LoggerFrom(ctx).Error(err, "failed to list ImageUpdateAutomations for GitRepository change")
|
||||
return nil
|
||||
}
|
||||
reqs := make([]reconcile.Request, len(autoList.Items))
|
||||
for i := range autoList.Items {
|
||||
reqs[i].NamespacedName.Name = autoList.Items[i].GetName()
|
||||
reqs[i].NamespacedName.Namespace = autoList.Items[i].GetNamespace()
|
||||
}
|
||||
return reqs
|
||||
}
|
||||
|
||||
// automationsForImagePolicy fetches all the automation objects that
|
||||
// might depend on a image policy object. Since the link is via
|
||||
// markers in the git repo, _any_ automation object in the same
|
||||
// namespace could be affected.
|
||||
func (r *ImageUpdateAutomationReconciler) automationsForImagePolicy(ctx context.Context, obj client.Object) []reconcile.Request {
|
||||
var autoList imagev1.ImageUpdateAutomationList
|
||||
if err := r.List(ctx, &autoList, client.InNamespace(obj.GetNamespace())); err != nil {
|
||||
ctrl.LoggerFrom(ctx).Error(err, "failed to list ImageUpdateAutomations for ImagePolicy change")
|
||||
return nil
|
||||
}
|
||||
reqs := make([]reconcile.Request, len(autoList.Items))
|
||||
for i := range autoList.Items {
|
||||
reqs[i].NamespacedName.Name = autoList.Items[i].GetName()
|
||||
reqs[i].NamespacedName.Namespace = autoList.Items[i].GetNamespace()
|
||||
}
|
||||
return reqs
|
||||
}
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, retErr error) {
|
||||
start := time.Now()
|
||||
log := ctrl.LoggerFrom(ctx)
|
||||
|
||||
// Fetch the ImageUpdateAutomation.
|
||||
obj := &imagev1.ImageUpdateAutomation{}
|
||||
if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
// Initialize the patch helper with the current version of the object.
|
||||
serialPatcher := patch.NewSerialPatcher(obj, r.Client)
|
||||
|
||||
// Always attempt to patch the object after each reconciliation.
|
||||
defer func() {
|
||||
// Create patch options for the final patch of the object.
|
||||
patchOpts := runtimereconcile.AddPatchOptions(obj, r.patchOptions, imageUpdateAutomationOwnedConditions, r.ControllerName)
|
||||
if err := serialPatcher.Patch(ctx, obj, patchOpts...); err != nil {
|
||||
// Ignore patch error "not found" when the object is being deleted.
|
||||
if !obj.GetDeletionTimestamp().IsZero() {
|
||||
err = kerrors.FilterOut(err, func(e error) bool { return apierrors.IsNotFound(e) })
|
||||
}
|
||||
retErr = kerrors.NewAggregate([]error{retErr, err})
|
||||
}
|
||||
|
||||
// When the reconciliation ends with an error, ensure that the Result is
|
||||
// empty. This is to suppress the runtime warning when returning a
|
||||
// non-zero Result and an error.
|
||||
if retErr != nil {
|
||||
result = ctrl.Result{}
|
||||
}
|
||||
|
||||
// Always record suspend, readiness and duration metrics.
|
||||
r.Metrics.RecordDuration(ctx, obj, start)
|
||||
}()
|
||||
|
||||
// Examine if the object is under deletion.
|
||||
if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||
return r.reconcileDelete(obj)
|
||||
}
|
||||
|
||||
// Add finalizer first if it doesn't exist to avoid the race condition
|
||||
// between init and delete.
|
||||
// Note: Finalizers in general can only be added when the deletionTimestamp
|
||||
// is not set.
|
||||
if !controllerutil.ContainsFinalizer(obj, imagev1.ImageUpdateAutomationFinalizer) {
|
||||
controllerutil.AddFinalizer(obj, imagev1.ImageUpdateAutomationFinalizer)
|
||||
return ctrl.Result{Requeue: true}, nil
|
||||
}
|
||||
|
||||
// Return if the object is suspended.
|
||||
if obj.Spec.Suspend {
|
||||
log.Info("reconciliation is suspended for this object")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
result, retErr = r.reconcile(ctx, serialPatcher, obj, start)
|
||||
return
|
||||
}
|
||||
|
||||
func (r *ImageUpdateAutomationReconciler) reconcile(ctx context.Context, sp *patch.SerialPatcher,
|
||||
obj *imagev1.ImageUpdateAutomation, startTime time.Time) (result ctrl.Result, retErr error) {
|
||||
oldObj := obj.DeepCopy()
|
||||
|
||||
var pushResult *source.PushResult
|
||||
|
||||
// syncNeeded decides if full reconciliation with image update is needed.
|
||||
syncNeeded := false
|
||||
|
||||
defer func() {
|
||||
// Define the meaning of success based on the requeue interval.
|
||||
isSuccess := func(res ctrl.Result, err error) bool {
|
||||
if err != nil || res.RequeueAfter != obj.GetRequeueAfter() || res.Requeue {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
rs := runtimereconcile.NewResultFinalizer(isSuccess, readyMessage)
|
||||
retErr = rs.Finalize(obj, result, retErr)
|
||||
|
||||
// Presence of reconciling means that the reconciliation didn't succeed.
|
||||
// Set the Reconciling reason to ProgressingWithRetry to indicate a
|
||||
// failure retry.
|
||||
if conditions.IsReconciling(obj) {
|
||||
reconciling := conditions.Get(obj, meta.ReconcilingCondition)
|
||||
reconciling.Reason = meta.ProgressingWithRetryReason
|
||||
conditions.Set(obj, reconciling)
|
||||
}
|
||||
|
||||
r.notify(ctx, oldObj, obj, pushResult, syncNeeded)
|
||||
}()
|
||||
|
||||
// TODO: Maybe move this to Reconcile()'s defer and avoid passing startTime
|
||||
// to reconcile()?
|
||||
obj.Status.LastAutomationRunTime = &metav1.Time{Time: startTime}
|
||||
|
||||
// Set reconciling condition.
|
||||
runtimereconcile.ProgressiveStatus(false, obj, meta.ProgressingReason, "reconciliation in progress")
|
||||
|
||||
var reconcileAtVal string
|
||||
if v, ok := meta.ReconcileAnnotationValue(obj.GetAnnotations()); ok {
|
||||
reconcileAtVal = v
|
||||
}
|
||||
|
||||
// Persist reconciling if generation differs or reconciliation is requested.
|
||||
switch {
|
||||
case obj.Generation != obj.Status.ObservedGeneration:
|
||||
runtimereconcile.ProgressiveStatus(false, obj, meta.ProgressingReason,
|
||||
"processing object: new generation %d -> %d", obj.Status.ObservedGeneration, obj.Generation)
|
||||
if err := sp.Patch(ctx, obj, r.patchOptions...); err != nil {
|
||||
result, retErr = ctrl.Result{}, err
|
||||
return
|
||||
}
|
||||
case reconcileAtVal != obj.Status.GetLastHandledReconcileRequest():
|
||||
if err := sp.Patch(ctx, obj, r.patchOptions...); err != nil {
|
||||
result, retErr = ctrl.Result{}, err
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// List the policies and construct observed policies.
|
||||
policies, err := getPolicies(ctx, r.Client, obj.Namespace, obj.Spec.PolicySelector)
|
||||
if err != nil {
|
||||
if errors.Is(err, errParsePolicySelector) {
|
||||
conditions.MarkStalled(obj, imagev1.InvalidPolicySelectorReason, "%s", err)
|
||||
result, retErr = ctrl.Result{}, nil
|
||||
return
|
||||
}
|
||||
result, retErr = ctrl.Result{}, err
|
||||
return
|
||||
}
|
||||
// Update any stale Ready=False condition from policies config failure.
|
||||
if conditions.HasAnyReason(obj, meta.ReadyCondition, imagev1.InvalidPolicySelectorReason) {
|
||||
conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "reconciliation in progress")
|
||||
}
|
||||
|
||||
// Index the policies by their name.
|
||||
observedPolicies := imagev1.ObservedPolicies{}
|
||||
for _, policy := range policies {
|
||||
observedPolicies[policy.Name] = imagev1.ImageRef{
|
||||
Name: policy.Status.LatestRef.Name,
|
||||
Tag: policy.Status.LatestRef.Tag,
|
||||
Digest: policy.Status.LatestRef.Digest,
|
||||
}
|
||||
}
|
||||
|
||||
// If the policies have changed, require a full sync.
|
||||
if observedPoliciesChanged(obj.Status.ObservedPolicies, observedPolicies) {
|
||||
syncNeeded = true
|
||||
}
|
||||
|
||||
// Create source manager with options.
|
||||
smOpts := []source.SourceOption{
|
||||
source.WithSourceOptionInvolvedObject(obj.GetName(), obj.GetNamespace()),
|
||||
source.WithSourceOptionTokenCache(r.tokenCache),
|
||||
}
|
||||
if r.NoCrossNamespaceRef {
|
||||
smOpts = append(smOpts, source.WithSourceOptionNoCrossNamespaceRef())
|
||||
}
|
||||
if r.features[features.GitAllBranchReferences] {
|
||||
smOpts = append(smOpts, source.WithSourceOptionGitAllBranchReferences())
|
||||
}
|
||||
sm, err := source.NewSourceManager(ctx, r.Client, obj, smOpts...)
|
||||
if err != nil {
|
||||
if acl.IsAccessDenied(err) {
|
||||
conditions.MarkStalled(obj, aclapi.AccessDeniedReason, "%s", err)
|
||||
result, retErr = ctrl.Result{}, nil
|
||||
return
|
||||
}
|
||||
if errors.Is(err, source.ErrInvalidSourceConfiguration) {
|
||||
conditions.MarkStalled(obj, imagev1.InvalidSourceConfigReason, "%s", err)
|
||||
result, retErr = ctrl.Result{}, nil
|
||||
return
|
||||
}
|
||||
e := fmt.Errorf("failed configuring source manager: %w", err)
|
||||
conditions.MarkFalse(obj, meta.ReadyCondition, imagev1.SourceManagerFailedReason, "%s", e)
|
||||
result, retErr = ctrl.Result{}, e
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := sm.Cleanup(); err != nil {
|
||||
retErr = err
|
||||
}
|
||||
}()
|
||||
// Update any stale Ready=False condition from SourceManager failure.
|
||||
if conditions.HasAnyReason(obj, meta.ReadyCondition, aclapi.AccessDeniedCondition, imagev1.InvalidSourceConfigReason, imagev1.SourceManagerFailedReason) {
|
||||
conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "reconciliation in progress")
|
||||
}
|
||||
|
||||
// When the checkout and push branches are different or a refspec is
|
||||
// defined, always perform a full sync.
|
||||
// This can be worked around in the future by also querying the HEAD of push
|
||||
// branch to detech if it has drifted.
|
||||
if sm.SwitchBranch() || obj.Spec.GitSpec.HasRefspec() {
|
||||
syncNeeded = true
|
||||
}
|
||||
|
||||
// Build checkout options.
|
||||
checkoutOpts := []source.CheckoutOption{}
|
||||
if r.features[features.GitShallowClone] {
|
||||
checkoutOpts = append(checkoutOpts, source.WithCheckoutOptionShallowClone())
|
||||
}
|
||||
if r.features[features.GitSparseCheckout] && obj.Spec.Update.Path != "" {
|
||||
checkoutOpts = append(checkoutOpts, source.WithCheckoutOptionSparseCheckoutDirectories(obj.Spec.Update.Path))
|
||||
}
|
||||
|
||||
// If full sync is still not needed, configure last observed commit to
|
||||
// perform optimized clone and obtain a non-concrete commit if the remote
|
||||
// has not changed.
|
||||
if !syncNeeded && obj.Status.ObservedSourceRevision != "" {
|
||||
checkoutOpts = append(checkoutOpts, source.WithCheckoutOptionLastObserved(obj.Status.ObservedSourceRevision))
|
||||
}
|
||||
|
||||
commit, err := sm.CheckoutSource(ctx, checkoutOpts...)
|
||||
if err != nil {
|
||||
e := fmt.Errorf("failed to checkout source: %w", err)
|
||||
conditions.MarkFalse(obj, meta.ReadyCondition, imagev1.GitOperationFailedReason, "%s", e)
|
||||
result, retErr = ctrl.Result{}, e
|
||||
return
|
||||
}
|
||||
// Update any stale Ready=False condition from checkout failure.
|
||||
if conditions.HasAnyReason(obj, meta.ReadyCondition, imagev1.GitOperationFailedReason) {
|
||||
conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "reconciliation in progress")
|
||||
}
|
||||
|
||||
// If it's a partial commit, the reconciliation can be skipped. The last
|
||||
// observed commit is only configured above when full sync is not needed.
|
||||
// No change in the policies and remote git repository. Skip reconciliation.
|
||||
if !git.IsConcreteCommit(*commit) {
|
||||
// Remove any stale Ready condition, most likely False, set above. Its value
|
||||
// is derived from the overall result of the reconciliation in the deferred
|
||||
// block at the very end.
|
||||
conditions.Delete(obj, meta.ReadyCondition)
|
||||
result, retErr = ctrl.Result{RequeueAfter: obj.GetRequeueAfter()}, nil
|
||||
return
|
||||
} else {
|
||||
// Concrete commit indicates full sync is needed due to new remote
|
||||
// revision.
|
||||
syncNeeded = true
|
||||
}
|
||||
// Continue with full sync with a concrete commit.
|
||||
|
||||
// Apply the policies and check if there's anything to update.
|
||||
policyResult, err := policy.ApplyPolicies(ctx, sm.WorkDirectory(), obj, policies)
|
||||
if err != nil {
|
||||
if errors.Is(err, policy.ErrNoUpdateStrategy) || errors.Is(err, policy.ErrUnsupportedUpdateStrategy) {
|
||||
conditions.MarkStalled(obj, imagev1.InvalidUpdateStrategyReason, "%s", err)
|
||||
result, retErr = ctrl.Result{}, nil
|
||||
return
|
||||
}
|
||||
e := fmt.Errorf("failed to apply policies: %w", err)
|
||||
conditions.MarkFalse(obj, meta.ReadyCondition, imagev1.UpdateFailedReason, "%s", e)
|
||||
result, retErr = ctrl.Result{}, e
|
||||
return
|
||||
}
|
||||
// Update any stale Ready=False condition from apply policies failure.
|
||||
if conditions.HasAnyReason(obj, meta.ReadyCondition, imagev1.InvalidUpdateStrategyReason, imagev1.UpdateFailedReason) {
|
||||
conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "reconciliation in progress")
|
||||
}
|
||||
|
||||
if len(policyResult.FileChanges) == 0 {
|
||||
// Remove any stale Ready condition, most likely False, set above. Its
|
||||
// value is derived from the overall result of the reconciliation in the
|
||||
// deferred block at the very end.
|
||||
conditions.Delete(obj, meta.ReadyCondition)
|
||||
|
||||
// Persist observations.
|
||||
obj.Status.ObservedSourceRevision = commit.String()
|
||||
obj.Status.ObservedPolicies = observedPolicies
|
||||
|
||||
result, retErr = ctrl.Result{RequeueAfter: obj.GetRequeueAfter()}, nil
|
||||
return
|
||||
}
|
||||
|
||||
// Build push config.
|
||||
pushCfg := []source.PushConfig{}
|
||||
// Enable force only when branch is changed for push.
|
||||
if r.features[features.GitForcePushBranch] && sm.SwitchBranch() {
|
||||
pushCfg = append(pushCfg, source.WithPushConfigForce())
|
||||
}
|
||||
// Include any push options.
|
||||
if obj.Spec.GitSpec.Push != nil && obj.Spec.GitSpec.Push.Options != nil {
|
||||
pushCfg = append(pushCfg, source.WithPushConfigOptions(obj.Spec.GitSpec.Push.Options))
|
||||
}
|
||||
|
||||
pushResult, err = sm.CommitAndPush(ctx, obj, policyResult, pushCfg...)
|
||||
if err != nil {
|
||||
e := fmt.Errorf("failed to update source: %w", err)
|
||||
conditions.MarkFalse(obj, meta.ReadyCondition, imagev1.GitOperationFailedReason, "%s", e)
|
||||
result, retErr = ctrl.Result{}, e
|
||||
return
|
||||
}
|
||||
// Update any stale Ready=False condition from commit and push failure.
|
||||
if conditions.HasAnyReason(obj, meta.ReadyCondition, imagev1.GitOperationFailedReason) {
|
||||
conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "reconciliation in progress")
|
||||
}
|
||||
|
||||
if pushResult == nil {
|
||||
// NOTE: This should not happen. This exists as a legacy behavior from
|
||||
// the old implementation where no commit is made due to no stagged
|
||||
// files. If nothing is pushed, the repository is up-to-date. Persist
|
||||
// observations and return with successful result.
|
||||
conditions.Delete(obj, meta.ReadyCondition)
|
||||
obj.Status.ObservedSourceRevision = commit.String()
|
||||
obj.Status.ObservedPolicies = observedPolicies
|
||||
result, retErr = ctrl.Result{RequeueAfter: obj.GetRequeueAfter()}, nil
|
||||
return
|
||||
}
|
||||
|
||||
// Persist observations.
|
||||
obj.Status.ObservedSourceRevision = pushResult.Commit().String()
|
||||
// If the push branch is different, store the checkout branch commit as the
|
||||
// observed source revision.
|
||||
if pushResult.SwitchBranch() {
|
||||
obj.Status.ObservedSourceRevision = commit.String()
|
||||
}
|
||||
obj.Status.ObservedPolicies = observedPolicies
|
||||
obj.Status.LastPushCommit = pushResult.Commit().Hash.String()
|
||||
obj.Status.LastPushTime = pushResult.Time()
|
||||
|
||||
// Remove any stale Ready condition, most likely False, set above. Its value
|
||||
// is derived from the overall result of the reconciliation in the deferred
|
||||
// block at the very end.
|
||||
conditions.Delete(obj, meta.ReadyCondition)
|
||||
result, retErr = ctrl.Result{RequeueAfter: obj.GetRequeueAfter()}, nil
|
||||
return
|
||||
}
|
||||
|
||||
// reconcileDelete handles the deletion of the object.
|
||||
func (r *ImageUpdateAutomationReconciler) reconcileDelete(obj *imagev1.ImageUpdateAutomation) (ctrl.Result, error) {
|
||||
// Remove our finalizer from the list.
|
||||
controllerutil.RemoveFinalizer(obj, imagev1.ImageUpdateAutomationFinalizer)
|
||||
|
||||
// Cleanup caches.
|
||||
r.tokenCache.DeleteEventsForObject(imagev1.ImageUpdateAutomationKind,
|
||||
obj.GetName(), obj.GetNamespace(), cache.OperationReconcile)
|
||||
|
||||
// Stop reconciliation as the object is being deleted.
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// getPolicies returns list of policies in the given namespace that have latest
|
||||
// image.
|
||||
func getPolicies(ctx context.Context, kclient client.Client, namespace string, selector *metav1.LabelSelector) ([]imagev1_reflect.ImagePolicy, error) {
|
||||
policySelector := labels.Everything()
|
||||
var err error
|
||||
if selector != nil {
|
||||
if policySelector, err = metav1.LabelSelectorAsSelector(selector); err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errParsePolicySelector, err)
|
||||
}
|
||||
}
|
||||
|
||||
var policies imagev1_reflect.ImagePolicyList
|
||||
if err := kclient.List(ctx, &policies, &client.ListOptions{Namespace: namespace, LabelSelector: policySelector}); err != nil {
|
||||
return nil, fmt.Errorf("failed to list policies: %w", err)
|
||||
}
|
||||
|
||||
readyPolicies := []imagev1_reflect.ImagePolicy{}
|
||||
for _, policy := range policies.Items {
|
||||
// Ignore the policies that don't have a latest image.
|
||||
if policy.Status.LatestRef == nil {
|
||||
continue
|
||||
}
|
||||
readyPolicies = append(readyPolicies, policy)
|
||||
}
|
||||
|
||||
return readyPolicies, nil
|
||||
}
|
||||
|
||||
// observedPoliciesChanged returns if the previous and current observedPolicies
|
||||
// have changed.
|
||||
func observedPoliciesChanged(previous, current imagev1.ObservedPolicies) bool {
|
||||
if len(previous) != len(current) {
|
||||
return true
|
||||
}
|
||||
for name, imageRef := range current {
|
||||
oldImageRef, ok := previous[name]
|
||||
if !ok {
|
||||
// Changed if an entry is not found.
|
||||
return true
|
||||
}
|
||||
if oldImageRef != imageRef {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// notify emits notifications and events based on the state of the object and
|
||||
// the given PushResult. It tries to always send the PushResult commit message
|
||||
// if there has been any update. Otherwise, a generic up-to-date message. In
|
||||
// case of any failure, the failure message is read from the Ready condition and
|
||||
// included in the event.
|
||||
func (r *ImageUpdateAutomationReconciler) notify(ctx context.Context, oldObj, newObj conditions.Setter, result *source.PushResult, syncNeeded bool) {
|
||||
// Use the Ready message as the notification message by default.
|
||||
ready := conditions.Get(newObj, meta.ReadyCondition)
|
||||
msg := ready.Message
|
||||
|
||||
// If there's a PushResult, use the summary as the notification message.
|
||||
if result != nil {
|
||||
msg = result.Summary()
|
||||
}
|
||||
|
||||
// Was ready before and is ready now, with new push result,
|
||||
if conditions.IsReady(oldObj) && conditions.IsReady(newObj) && result != nil {
|
||||
eventLogf(ctx, r.EventRecorder, newObj, corev1.EventTypeNormal, ready.Reason, "%s", msg)
|
||||
return
|
||||
}
|
||||
|
||||
// Emit events when reconciliation fails or recovers from failure.
|
||||
|
||||
// Became ready from not ready.
|
||||
if !conditions.IsReady(oldObj) && conditions.IsReady(newObj) {
|
||||
eventLogf(ctx, r.EventRecorder, newObj, corev1.EventTypeNormal, ready.Reason, "%s", msg)
|
||||
return
|
||||
}
|
||||
// Not ready, failed. Use the failure message from ready condition.
|
||||
if !conditions.IsReady(newObj) {
|
||||
eventLogf(ctx, r.EventRecorder, newObj, corev1.EventTypeWarning, ready.Reason, "%s", ready.Message)
|
||||
return
|
||||
}
|
||||
|
||||
// No change.
|
||||
|
||||
if !syncNeeded {
|
||||
// Full reconciliation skipped.
|
||||
msg = "no change since last reconciliation"
|
||||
}
|
||||
eventLogf(ctx, r.EventRecorder, newObj, eventv1.EventTypeTrace, meta.SucceededReason, "%s", msg)
|
||||
}
|
||||
|
||||
// eventLogf records events, and logs at the same time.
|
||||
//
|
||||
// This log is different from the debug log in the EventRecorder, in the sense
|
||||
// that this is a simple log. While the debug log contains complete details
|
||||
// about the event.
|
||||
func eventLogf(ctx context.Context, r kuberecorder.EventRecorder, obj runtime.Object, eventType string, reason string, messageFmt string, args ...interface{}) {
|
||||
msg := fmt.Sprintf(messageFmt, args...)
|
||||
// Log and emit event.
|
||||
if eventType == corev1.EventTypeWarning {
|
||||
ctrl.LoggerFrom(ctx).Error(errors.New(reason), msg)
|
||||
} else {
|
||||
ctrl.LoggerFrom(ctx).Info(msg)
|
||||
}
|
||||
r.Eventf(obj, eventType, reason, msg)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
Copyright 2024 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 controller
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
// latestImageChangePredicate implements a predicate for latest image change.
|
||||
// This can be used to filter events from ImagePolicies for change in the latest
|
||||
// image.
|
||||
type latestImageChangePredicate struct {
|
||||
predicate.Funcs
|
||||
}
|
||||
|
||||
func (latestImageChangePredicate) Create(e event.CreateEvent) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (latestImageChangePredicate) Delete(e event.DeleteEvent) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (latestImageChangePredicate) Update(e event.UpdateEvent) bool {
|
||||
if e.ObjectOld == nil || e.ObjectNew == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
oldSource, ok := e.ObjectOld.(*imagev1_reflect.ImagePolicy)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
newSource, ok := e.ObjectNew.(*imagev1_reflect.ImagePolicy)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if newSource.Status.LatestRef == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if oldSource.Status.LatestRef == nil || *oldSource.Status.LatestRef != *newSource.Status.LatestRef {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// sourceConfigChangePredicate implements a predicate for source configuration
|
||||
// change. This can be used to filter events from source objects for change in
|
||||
// source configuration.
|
||||
type sourceConfigChangePredicate struct {
|
||||
predicate.Funcs
|
||||
}
|
||||
|
||||
func (sourceConfigChangePredicate) Create(e event.CreateEvent) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (sourceConfigChangePredicate) Delete(e event.DeleteEvent) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (sourceConfigChangePredicate) Update(e event.UpdateEvent) bool {
|
||||
if e.ObjectOld == nil || e.ObjectNew == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration()
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
Copyright 2024 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 controller
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
)
|
||||
|
||||
func Test_latestImageChangePredicate_Update(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
beforeFunc func(oldObj, newObj *imagev1_reflect.ImagePolicy)
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "no latest image",
|
||||
beforeFunc: func(oldObj, newObj *imagev1_reflect.ImagePolicy) {
|
||||
oldObj.Status.LatestRef = nil
|
||||
newObj.Status.LatestRef = nil
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "new image, no old image",
|
||||
beforeFunc: func(oldObj, newObj *imagev1_reflect.ImagePolicy) {
|
||||
oldObj.Status.LatestRef = nil
|
||||
newObj.Status.LatestRef = &imagev1_reflect.ImageRef{Name: "foo"}
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "different old and new image",
|
||||
beforeFunc: func(oldObj, newObj *imagev1_reflect.ImagePolicy) {
|
||||
oldObj.Status.LatestRef = &imagev1_reflect.ImageRef{Name: "bar"}
|
||||
newObj.Status.LatestRef = &imagev1_reflect.ImageRef{Name: "foo"}
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
oldObj := &imagev1_reflect.ImagePolicy{}
|
||||
newObj := oldObj.DeepCopy()
|
||||
if tt.beforeFunc != nil {
|
||||
tt.beforeFunc(oldObj, newObj)
|
||||
}
|
||||
e := event.UpdateEvent{
|
||||
ObjectOld: oldObj,
|
||||
ObjectNew: newObj,
|
||||
}
|
||||
p := latestImageChangePredicate{}
|
||||
g.Expect(p.Update(e)).To(Equal(tt.want))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_sourceConfigChangePredicate_Update(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
beforeFunc func(oldObj, newObj *sourcev1.GitRepository)
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "no generation change, same config",
|
||||
beforeFunc: func(oldObj, newObj *sourcev1.GitRepository) {
|
||||
oldObj.Generation = 0
|
||||
newObj.Generation = 0
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "new generation, config change",
|
||||
beforeFunc: func(oldObj, newObj *sourcev1.GitRepository) {
|
||||
oldObj.Generation = 1
|
||||
newObj.Generation = 2
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
oldObj := &sourcev1.GitRepository{}
|
||||
newObj := oldObj.DeepCopy()
|
||||
if tt.beforeFunc != nil {
|
||||
tt.beforeFunc(oldObj, newObj)
|
||||
}
|
||||
e := event.UpdateEvent{
|
||||
ObjectOld: oldObj,
|
||||
ObjectNew: newObj,
|
||||
}
|
||||
p := sourceConfigChangePredicate{}
|
||||
g.Expect(p.Update(e)).To(Equal(tt.want))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controllers
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -24,17 +24,18 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
git2go "github.com/libgit2/git2go/v33"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/tools/record"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta1"
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
"github.com/fluxcd/pkg/runtime/controller"
|
||||
"github.com/fluxcd/pkg/runtime/testenv"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
"github.com/fluxcd/source-controller/pkg/git/libgit2/managed"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
|
@ -45,8 +46,9 @@ import (
|
|||
// Gomega.
|
||||
|
||||
var (
|
||||
testEnv *testenv.Environment
|
||||
ctx = ctrl.SetupSignalHandler()
|
||||
k8sClient client.Client
|
||||
testEnv *testenv.Environment
|
||||
ctx = ctrl.SetupSignalHandler()
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -54,31 +56,50 @@ func init() {
|
|||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
mustHaveNoThreadSupport()
|
||||
|
||||
utilruntime.Must(imagev1_reflect.AddToScheme(scheme.Scheme))
|
||||
utilruntime.Must(sourcev1.AddToScheme(scheme.Scheme))
|
||||
utilruntime.Must(imagev1.AddToScheme(scheme.Scheme))
|
||||
|
||||
testEnv = testenv.New(testenv.WithCRDPath(
|
||||
filepath.Join("..", "config", "crd", "bases"),
|
||||
filepath.Join("testdata", "crds"),
|
||||
))
|
||||
code := runTestsWithFeatures(m, nil)
|
||||
if code != 0 {
|
||||
fmt.Println("failed with default feature values")
|
||||
}
|
||||
|
||||
managed.InitManagedTransport()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func runTestsWithFeatures(m *testing.M, feats map[string]bool) int {
|
||||
testEnv = testenv.New(
|
||||
testenv.WithCRDPath(
|
||||
filepath.Join("..", "..", "config", "crd", "bases"),
|
||||
filepath.Join("testdata", "crds"),
|
||||
),
|
||||
testenv.WithMaxConcurrentReconciles(2),
|
||||
)
|
||||
|
||||
var err error
|
||||
// Initialize a cacheless client for tests that need the latest objects.
|
||||
k8sClient, err = client.New(testEnv.Config, client.Options{Scheme: scheme.Scheme})
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to create k8s client: %v", err))
|
||||
}
|
||||
|
||||
controllerName := "image-automation-controller"
|
||||
if err := (&ImageUpdateAutomationReconciler{
|
||||
Client: testEnv,
|
||||
EventRecorder: testEnv.GetEventRecorderFor(controllerName),
|
||||
}).SetupWithManager(testEnv, ImageUpdateAutomationReconcilerOptions{}); err != nil {
|
||||
panic(fmt.Sprintf("Failed to start ImageUpdateAutomationReconciler: %v", err))
|
||||
Client: testEnv,
|
||||
EventRecorder: record.NewFakeRecorder(32),
|
||||
features: feats,
|
||||
ControllerName: controllerName,
|
||||
}).SetupWithManager(ctx, testEnv, ImageUpdateAutomationReconcilerOptions{
|
||||
RateLimiter: controller.GetDefaultRateLimiter(),
|
||||
}); err != nil {
|
||||
panic(fmt.Sprintf("failed to start ImageUpdateAutomationReconciler: %v", err))
|
||||
}
|
||||
|
||||
go func() {
|
||||
fmt.Println("Starting the test environment")
|
||||
if err := testEnv.Start(ctx); err != nil {
|
||||
panic(fmt.Sprintf("Failed to start the test environment manager: %v", err))
|
||||
panic(fmt.Sprintf("failed to start the test environment manager: %v", err))
|
||||
}
|
||||
}()
|
||||
<-testEnv.Manager.Elected()
|
||||
|
@ -87,27 +108,8 @@ func TestMain(m *testing.M) {
|
|||
|
||||
fmt.Println("Stopping the test environment")
|
||||
if err := testEnv.Stop(); err != nil {
|
||||
panic(fmt.Sprintf("Failed to stop the test environment: %v", err))
|
||||
panic(fmt.Sprintf("failed to stop the test environment: %v", err))
|
||||
}
|
||||
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// This provides a regression assurance for image-automation-controller/#339.
|
||||
// Validates that:
|
||||
// - libgit2 was built with no support for threads.
|
||||
// - git2go accepts libgit2 built with no support for threads.
|
||||
//
|
||||
// The logic below does the validation of the former, whilst
|
||||
// referring to git2go forces its init() execution, which is
|
||||
// where any validation to that effect resides.
|
||||
//
|
||||
// git2go does not support threadless libgit2 by default,
|
||||
// hence a fork is being used which disables such validation.
|
||||
//
|
||||
// TODO: extract logic into pkg.
|
||||
func mustHaveNoThreadSupport() {
|
||||
if git2go.Features()&git2go.FeatureThreads != 0 {
|
||||
panic("libgit2 must not be build with thread support")
|
||||
}
|
||||
return code
|
||||
}
|
|
@ -15,25 +15,63 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
// Package features sets the feature gates that
|
||||
// source-controller supports, and their default
|
||||
// image-automation-controller supports, and their default
|
||||
// states.
|
||||
package features
|
||||
|
||||
import feathelper "github.com/fluxcd/pkg/runtime/features"
|
||||
|
||||
const (
|
||||
// GitManagedTransport implements a managed transport for GitRepository
|
||||
// objects that use the libgit2 implementation.
|
||||
//
|
||||
// When enabled, improves the reliability of libgit2 reconciliations,
|
||||
// by enforcing timeouts and ensuring libgit2 cannot hijack the process
|
||||
// and hang it indefinitely.
|
||||
GitManagedTransport = "GitManagedTransport"
|
||||
import (
|
||||
"github.com/fluxcd/pkg/auth"
|
||||
feathelper "github.com/fluxcd/pkg/runtime/features"
|
||||
)
|
||||
|
||||
var features = map[string]bool{}
|
||||
const (
|
||||
// GitForcePushBranch enables the use of "force push" when push branches
|
||||
// are configured.
|
||||
GitForcePushBranch = "GitForcePushBranch"
|
||||
// GitShallowClone enables the use of shallow clones when pulling source from
|
||||
// Git repositories.
|
||||
GitShallowClone = "GitShallowClone"
|
||||
// GitAllBranchReferences enables the download of all branch head references
|
||||
// when push branches are configured. When enabled fixes fluxcd/flux2#3384.
|
||||
GitAllBranchReferences = "GitAllBranchReferences"
|
||||
// GitSparseCheckout enables the use of sparse checkout when pulling source from
|
||||
// Git repositories.
|
||||
GitSparseCheckout = "GitSparseCheckout"
|
||||
// CacheSecretsAndConfigMaps controls whether Secrets and ConfigMaps should
|
||||
// be cached.
|
||||
//
|
||||
// When enabled, it will cache both object types, resulting in increased
|
||||
// memory usage and cluster-wide RBAC permissions (list and watch).
|
||||
CacheSecretsAndConfigMaps = "CacheSecretsAndConfigMaps"
|
||||
)
|
||||
|
||||
// DefaultFeatureGates contains a list of all supported feature gates and
|
||||
var features = map[string]bool{
|
||||
// GitForcePushBranch
|
||||
// opt-out from v0.27
|
||||
GitForcePushBranch: true,
|
||||
|
||||
// GitShallowClone
|
||||
// opt-out from v0.28
|
||||
GitShallowClone: true,
|
||||
|
||||
// GitAllBranchReferences
|
||||
// opt-out from v0.28
|
||||
GitAllBranchReferences: true,
|
||||
|
||||
// GitSparseCheckout
|
||||
// opt-in from v0.42
|
||||
GitSparseCheckout: false,
|
||||
|
||||
// CacheSecretsAndConfigMaps
|
||||
// opt-in from v0.29
|
||||
CacheSecretsAndConfigMaps: false,
|
||||
}
|
||||
|
||||
func init() {
|
||||
auth.SetFeatureGates(features)
|
||||
}
|
||||
|
||||
// FeatureGates contains a list of all supported feature gates and
|
||||
// their default values.
|
||||
func FeatureGates() map[string]bool {
|
||||
return features
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
Copyright 2024 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 policy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
"github.com/fluxcd/pkg/runtime/logger"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
"github.com/fluxcd/image-automation-controller/pkg/update"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoUpdateStrategy is an update error when the update strategy is not
|
||||
// specified.
|
||||
ErrNoUpdateStrategy = errors.New("no update strategy")
|
||||
// ErrUnsupportedUpdateStrategy is an update error when the provided update
|
||||
// strategy is not supported.
|
||||
ErrUnsupportedUpdateStrategy = errors.New("unsupported update strategy")
|
||||
)
|
||||
|
||||
// ApplyPolicies applies the given set of policies on the source present in the
|
||||
// workDir based on the provided ImageUpdateAutomation configuration.
|
||||
func ApplyPolicies(ctx context.Context, workDir string, obj *imagev1.ImageUpdateAutomation, policies []imagev1_reflect.ImagePolicy) (update.ResultV2, error) {
|
||||
var result update.ResultV2
|
||||
if obj.Spec.Update == nil {
|
||||
return result, ErrNoUpdateStrategy
|
||||
}
|
||||
if obj.Spec.Update.Strategy != imagev1.UpdateStrategySetters {
|
||||
return result, fmt.Errorf("%w: %s", ErrUnsupportedUpdateStrategy, obj.Spec.Update.Strategy)
|
||||
}
|
||||
|
||||
// Resolve the path to the manifests to apply policies on.
|
||||
manifestPath := workDir
|
||||
if obj.Spec.Update.Path != "" {
|
||||
p, err := securejoin.SecureJoin(workDir, obj.Spec.Update.Path)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("failed to secure join manifest path: %w", err)
|
||||
}
|
||||
manifestPath = p
|
||||
}
|
||||
|
||||
tracelog := log.FromContext(ctx).V(logger.TraceLevel)
|
||||
return update.UpdateV2WithSetters(tracelog, manifestPath, manifestPath, policies)
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
Copyright 2024 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 policy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/otiai10/copy"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
"github.com/fluxcd/image-automation-controller/internal/testutil"
|
||||
"github.com/fluxcd/image-automation-controller/pkg/test"
|
||||
"github.com/fluxcd/image-automation-controller/pkg/update"
|
||||
)
|
||||
|
||||
func testdataPath(path string) string {
|
||||
return filepath.Join("testdata", path)
|
||||
}
|
||||
|
||||
func Test_applyPolicies(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
updateStrategy *imagev1.UpdateStrategy
|
||||
policyLatestImages map[string]string
|
||||
targetPolicyName string
|
||||
replaceMarkerFunc func(g *WithT, path string, policyKey types.NamespacedName)
|
||||
inputPath string
|
||||
expectedPath string
|
||||
wantErr bool
|
||||
wantResult update.Result
|
||||
}{
|
||||
{
|
||||
name: "valid update strategy and one policy",
|
||||
updateStrategy: &imagev1.UpdateStrategy{
|
||||
Strategy: imagev1.UpdateStrategySetters,
|
||||
},
|
||||
policyLatestImages: map[string]string{
|
||||
"policy1": "helloworld:1.0.1",
|
||||
},
|
||||
targetPolicyName: "policy1",
|
||||
inputPath: testdataPath("appconfig"),
|
||||
expectedPath: testdataPath("appconfig-setters-expected"),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "no update strategy",
|
||||
updateStrategy: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "unknown update strategy",
|
||||
updateStrategy: &imagev1.UpdateStrategy{
|
||||
Strategy: "foo",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "valid update strategy and multiple policies",
|
||||
updateStrategy: &imagev1.UpdateStrategy{
|
||||
Strategy: imagev1.UpdateStrategySetters,
|
||||
},
|
||||
policyLatestImages: map[string]string{
|
||||
"policy1": "foo:1.1.1",
|
||||
"policy2": "helloworld:1.0.1",
|
||||
"policy3": "bar:2.2.2",
|
||||
},
|
||||
targetPolicyName: "policy2",
|
||||
inputPath: testdataPath("appconfig"),
|
||||
expectedPath: testdataPath("appconfig-setters-expected"),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid update strategy with update path",
|
||||
updateStrategy: &imagev1.UpdateStrategy{
|
||||
Strategy: imagev1.UpdateStrategySetters,
|
||||
Path: "./yes",
|
||||
},
|
||||
policyLatestImages: map[string]string{
|
||||
"policy1": "helloworld:1.0.1",
|
||||
},
|
||||
targetPolicyName: "policy1",
|
||||
replaceMarkerFunc: func(g *WithT, path string, policyKey types.NamespacedName) {
|
||||
g.Expect(testutil.ReplaceMarker(filepath.Join(path, "yes", "deploy.yaml"), policyKey)).ToNot(HaveOccurred())
|
||||
g.Expect(testutil.ReplaceMarker(filepath.Join(path, "no", "deploy.yaml"), policyKey)).ToNot(HaveOccurred())
|
||||
},
|
||||
inputPath: testdataPath("pathconfig"),
|
||||
expectedPath: testdataPath("pathconfig-expected"),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
testNS := "test-ns"
|
||||
workDir := t.TempDir()
|
||||
|
||||
// Create all the policy objects.
|
||||
policyList := []imagev1_reflect.ImagePolicy{}
|
||||
for name, image := range tt.policyLatestImages {
|
||||
policy := &imagev1_reflect.ImagePolicy{}
|
||||
policy.Name = name
|
||||
policy.Namespace = testNS
|
||||
policy.Status = imagev1_reflect.ImagePolicyStatus{
|
||||
LatestRef: testutil.ImageToRef(image),
|
||||
}
|
||||
policyList = append(policyList, *policy)
|
||||
}
|
||||
targetPolicyKey := types.NamespacedName{
|
||||
Name: tt.targetPolicyName, Namespace: testNS,
|
||||
}
|
||||
|
||||
if tt.inputPath != "" {
|
||||
g.Expect(copy.Copy(tt.inputPath, workDir)).ToNot(HaveOccurred())
|
||||
// Update the test files with the target policy.
|
||||
if tt.replaceMarkerFunc != nil {
|
||||
tt.replaceMarkerFunc(g, workDir, targetPolicyKey)
|
||||
} else {
|
||||
g.Expect(testutil.ReplaceMarker(filepath.Join(workDir, "deploy.yaml"), targetPolicyKey)).ToNot(HaveOccurred())
|
||||
}
|
||||
}
|
||||
|
||||
updateAuto := &imagev1.ImageUpdateAutomation{}
|
||||
updateAuto.Name = "test-update"
|
||||
updateAuto.Namespace = testNS
|
||||
updateAuto.Spec = imagev1.ImageUpdateAutomationSpec{
|
||||
Update: tt.updateStrategy,
|
||||
}
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
imagev1_reflect.AddToScheme(scheme)
|
||||
imagev1.AddToScheme(scheme)
|
||||
|
||||
_, err := ApplyPolicies(context.TODO(), workDir, updateAuto, policyList)
|
||||
g.Expect(err != nil).To(Equal(tt.wantErr))
|
||||
|
||||
// Check the results if there wasn't any error.
|
||||
if !tt.wantErr {
|
||||
expected := t.TempDir()
|
||||
copy.Copy(tt.expectedPath, expected)
|
||||
// Update the markers in the expected test data.
|
||||
if tt.replaceMarkerFunc != nil {
|
||||
tt.replaceMarkerFunc(g, expected, targetPolicyKey)
|
||||
} else {
|
||||
g.Expect(testutil.ReplaceMarker(filepath.Join(expected, "deploy.yaml"), targetPolicyKey)).ToNot(HaveOccurred())
|
||||
}
|
||||
test.ExpectMatchingDirectories(g, workDir, expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -7,4 +7,4 @@ spec:
|
|||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: helloworld:1.2.0 # SETTER_SITE
|
||||
image: helloworld:1.0.0 # SETTER_SITE
|
|
@ -0,0 +1,10 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: update-no
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: helloworld:1.0.0 # SETTER_SITE
|
|
@ -0,0 +1,10 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: update-yes
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: helloworld:1.0.1 # SETTER_SITE
|
|
@ -0,0 +1,10 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: update-no
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: helloworld:1.0.0 # SETTER_SITE
|
|
@ -0,0 +1,10 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: update-yes
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: helloworld:1.0.0 # SETTER_SITE
|
|
@ -0,0 +1,342 @@
|
|||
/*
|
||||
Copyright 2024 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 source
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/fluxcd/pkg/auth"
|
||||
authutils "github.com/fluxcd/pkg/auth/utils"
|
||||
"github.com/fluxcd/pkg/cache"
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"github.com/fluxcd/pkg/git/github"
|
||||
"github.com/fluxcd/pkg/git/gogit"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
const (
|
||||
signingSecretKey = "git.asc"
|
||||
signingPassphraseKey = "passphrase"
|
||||
)
|
||||
|
||||
// gitSrcCfg contains all the Git configurations related to a source derived
|
||||
// from the given configurations and the environment.
|
||||
type gitSrcCfg struct {
|
||||
srcKey types.NamespacedName
|
||||
url string
|
||||
pushBranch string
|
||||
switchBranch bool
|
||||
timeout *metav1.Duration
|
||||
checkoutRef *sourcev1.GitRepositoryRef
|
||||
authOpts *git.AuthOptions
|
||||
clientOpts []gogit.ClientOption
|
||||
signingEntity *openpgp.Entity
|
||||
}
|
||||
|
||||
func buildGitConfig(ctx context.Context, c client.Client, originKey, srcKey types.NamespacedName, gitSpec *imagev1.GitSpec, opts SourceOptions) (*gitSrcCfg, error) {
|
||||
cfg := &gitSrcCfg{
|
||||
srcKey: srcKey,
|
||||
}
|
||||
|
||||
// Get the repo.
|
||||
repo := &sourcev1.GitRepository{}
|
||||
if err := c.Get(ctx, srcKey, repo); err != nil {
|
||||
if client.IgnoreNotFound(err) == nil {
|
||||
return nil, fmt.Errorf("referenced git repository does not exist: %w", err)
|
||||
}
|
||||
}
|
||||
cfg.url = repo.Spec.URL
|
||||
|
||||
// Configure Git operation timeout from the GitRepository configuration.
|
||||
if repo.Spec.Timeout != nil {
|
||||
cfg.timeout = repo.Spec.Timeout
|
||||
} else {
|
||||
cfg.timeout = &metav1.Duration{Duration: time.Minute}
|
||||
}
|
||||
|
||||
// Get the checkout ref for the source, prioritizing the image automation
|
||||
// object gitSpec checkout reference and falling back to the GitRepository
|
||||
// reference if not provided.
|
||||
// var checkoutRef *sourcev1.GitRepositoryRef
|
||||
if gitSpec.Checkout != nil {
|
||||
cfg.checkoutRef = &gitSpec.Checkout.Reference
|
||||
} else if repo.Spec.Reference != nil {
|
||||
cfg.checkoutRef = repo.Spec.Reference
|
||||
} // else remain as `nil` and git.DefaultBranch will be used.
|
||||
|
||||
// Configure push first as the client options below depend on the push
|
||||
// configuration.
|
||||
if err := configurePush(cfg, gitSpec, cfg.checkoutRef); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxyOpts, proxyURL, err := getProxyOpts(ctx, c, repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.authOpts, err = getAuthOpts(ctx, c, repo, opts, proxyURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.clientOpts = []gogit.ClientOption{gogit.WithDiskStorage()}
|
||||
if cfg.authOpts.Transport == git.HTTP {
|
||||
cfg.clientOpts = append(cfg.clientOpts, gogit.WithInsecureCredentialsOverHTTP())
|
||||
}
|
||||
if proxyOpts != nil {
|
||||
cfg.clientOpts = append(cfg.clientOpts, gogit.WithProxy(*proxyOpts))
|
||||
}
|
||||
// If the push branch is different from the checkout ref, we need to
|
||||
// have all the references downloaded at clone time, to ensure that
|
||||
// SwitchBranch will have access to the target branch state. fluxcd/flux2#3384
|
||||
//
|
||||
// To always overwrite the push branch, the feature gate
|
||||
// GitAllBranchReferences can be set to false, which will cause
|
||||
// the SwitchBranch operation to ignore the remote branch state.
|
||||
if cfg.switchBranch {
|
||||
cfg.clientOpts = append(cfg.clientOpts, gogit.WithSingleBranch(!opts.gitAllBranchReferences))
|
||||
}
|
||||
|
||||
if gitSpec.Commit.SigningKey != nil {
|
||||
if cfg.signingEntity, err = getSigningEntity(ctx, c, originKey.Namespace, gitSpec); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func configurePush(cfg *gitSrcCfg, gitSpec *imagev1.GitSpec, checkoutRef *sourcev1.GitRepositoryRef) error {
|
||||
if gitSpec.Push != nil && gitSpec.Push.Branch != "" {
|
||||
cfg.pushBranch = gitSpec.Push.Branch
|
||||
|
||||
if checkoutRef != nil {
|
||||
if cfg.pushBranch != checkoutRef.Branch {
|
||||
cfg.switchBranch = true
|
||||
}
|
||||
} else {
|
||||
// Compare with the git default branch when no checkout ref is
|
||||
// explicitly defined.
|
||||
if cfg.pushBranch != git.DefaultBranch {
|
||||
cfg.switchBranch = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// If no push branch is configured above, use the branch from checkoutRef.
|
||||
|
||||
// Here's where it gets constrained. If there's no push branch
|
||||
// given, then the checkout ref must include a branch, and
|
||||
// that can be used.
|
||||
if checkoutRef == nil || checkoutRef.Branch == "" {
|
||||
return errors.New("push spec not provided, and cannot be inferred from .spec.git.checkout.ref or GitRepository .spec.ref")
|
||||
}
|
||||
cfg.pushBranch = checkoutRef.Branch
|
||||
return nil
|
||||
}
|
||||
|
||||
func getAuthOpts(ctx context.Context, c client.Client, repo *sourcev1.GitRepository,
|
||||
srcOpts SourceOptions, proxyURL *url.URL) (*git.AuthOptions, error) {
|
||||
var data map[string][]byte
|
||||
var err error
|
||||
if repo.Spec.SecretRef != nil {
|
||||
data, err = getSecretData(ctx, c, repo.Spec.SecretRef.Name, repo.GetNamespace())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get auth secret '%s/%s': %w", repo.GetNamespace(), repo.Spec.SecretRef.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
u, err := url.Parse(repo.Spec.URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse URL '%s': %w", repo.Spec.URL, err)
|
||||
}
|
||||
|
||||
opts, err := git.NewAuthOptions(*u, data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to configure authentication options: %w", err)
|
||||
}
|
||||
|
||||
var getCreds func() (*authutils.GitCredentials, error)
|
||||
switch provider := repo.GetProvider(); provider {
|
||||
case sourcev1.GitProviderAzure: // If AWS or GCP are added in the future they can be added here separated by a comma.
|
||||
getCreds = func() (*authutils.GitCredentials, error) {
|
||||
var opts []auth.Option
|
||||
|
||||
if srcOpts.tokenCache != nil {
|
||||
involvedObject := cache.InvolvedObject{
|
||||
Kind: imagev1.ImageUpdateAutomationKind,
|
||||
Name: srcOpts.objName,
|
||||
Namespace: srcOpts.objNamespace,
|
||||
Operation: cache.OperationReconcile,
|
||||
}
|
||||
opts = append(opts, auth.WithCache(*srcOpts.tokenCache, involvedObject))
|
||||
}
|
||||
|
||||
if proxyURL != nil {
|
||||
opts = append(opts, auth.WithProxyURL(*proxyURL))
|
||||
}
|
||||
|
||||
return authutils.GetGitCredentials(ctx, provider, opts...)
|
||||
}
|
||||
case sourcev1.GitProviderGitHub:
|
||||
// if provider is github, but secret ref is not specified
|
||||
if repo.Spec.SecretRef == nil {
|
||||
return nil, fmt.Errorf("secretRef with github app data must be specified when provider is set to github: %w", ErrInvalidSourceConfiguration)
|
||||
}
|
||||
|
||||
getCreds = func() (*authutils.GitCredentials, error) {
|
||||
var opts []github.OptFunc
|
||||
|
||||
if len(data) > 0 {
|
||||
opts = append(opts, github.WithAppData(data))
|
||||
}
|
||||
|
||||
if proxyURL != nil {
|
||||
opts = append(opts, github.WithProxyURL(proxyURL))
|
||||
}
|
||||
|
||||
if srcOpts.tokenCache != nil {
|
||||
opts = append(opts, github.WithCache(srcOpts.tokenCache, imagev1.ImageUpdateAutomationKind,
|
||||
srcOpts.objName, srcOpts.objNamespace, cache.OperationReconcile))
|
||||
}
|
||||
|
||||
username, password, err := github.GetCredentials(ctx, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &authutils.GitCredentials{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}, nil
|
||||
}
|
||||
default:
|
||||
// analyze secret, if it has github app data, perhaps provider should have been github.
|
||||
if appID := data[github.AppIDKey]; len(appID) != 0 {
|
||||
return nil, fmt.Errorf("secretRef '%s/%s' has github app data but provider is not set to github: %w", repo.GetNamespace(), repo.Spec.SecretRef.Name, ErrInvalidSourceConfiguration)
|
||||
}
|
||||
}
|
||||
if getCreds != nil {
|
||||
creds, err := getCreds()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to configure authentication options: %w", err)
|
||||
}
|
||||
opts.BearerToken = creds.BearerToken
|
||||
opts.Username = creds.Username
|
||||
opts.Password = creds.Password
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func getProxyOpts(ctx context.Context, c client.Client, repo *sourcev1.GitRepository) (*transport.ProxyOptions, *url.URL, error) {
|
||||
if repo.Spec.ProxySecretRef == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
name := repo.Spec.ProxySecretRef.Name
|
||||
namespace := repo.GetNamespace()
|
||||
proxyData, err := getSecretData(ctx, c, name, namespace)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get proxy secret '%s/%s': %w", namespace, name, err)
|
||||
}
|
||||
b, ok := proxyData["address"]
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("invalid proxy secret '%s/%s': key 'address' is missing", namespace, name)
|
||||
}
|
||||
|
||||
address := string(b)
|
||||
username := string(proxyData["username"])
|
||||
password := string(proxyData["password"])
|
||||
|
||||
proxyOpts := &transport.ProxyOptions{
|
||||
URL: address,
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
proxyURL, err := url.Parse(string(address))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid address in proxy secret '%s/%s': %w", namespace, name, err)
|
||||
}
|
||||
switch {
|
||||
case username != "" && password == "":
|
||||
proxyURL.User = url.User(username)
|
||||
case username != "" && password != "":
|
||||
proxyURL.User = url.UserPassword(username, password)
|
||||
}
|
||||
|
||||
return proxyOpts, proxyURL, nil
|
||||
}
|
||||
|
||||
func getSigningEntity(ctx context.Context, c client.Client, namespace string, gitSpec *imagev1.GitSpec) (*openpgp.Entity, error) {
|
||||
secretName := gitSpec.Commit.SigningKey.SecretRef.Name
|
||||
secretData, err := getSecretData(ctx, c, secretName, namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not find signing key secret '%s': %w", secretName, err)
|
||||
}
|
||||
|
||||
data, ok := secretData[signingSecretKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("signing key secret '%s' does not contain a 'git.asc' key", secretName)
|
||||
}
|
||||
|
||||
// Read entity from secret value
|
||||
entities, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read signing key from secret '%s': %w", secretName, err)
|
||||
}
|
||||
if len(entities) > 1 {
|
||||
return nil, fmt.Errorf("multiple entities read from secret '%s', could not determine which signing key to use", secretName)
|
||||
}
|
||||
|
||||
entity := entities[0]
|
||||
if entity.PrivateKey != nil && entity.PrivateKey.Encrypted {
|
||||
passphrase, ok := secretData[signingPassphraseKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("can not use passphrase protected signing key without '%s' field present in secret %s",
|
||||
"passphrase", secretName)
|
||||
}
|
||||
if err = entity.PrivateKey.Decrypt([]byte(passphrase)); err != nil {
|
||||
return nil, fmt.Errorf("could not decrypt private key of the signing key present in secret %s: %w", secretName, err)
|
||||
}
|
||||
}
|
||||
return entity, nil
|
||||
}
|
||||
|
||||
func getSecretData(ctx context.Context, c client.Client, name, namespace string) (map[string][]byte, error) {
|
||||
key := types.NamespacedName{
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
}
|
||||
var secret corev1.Secret
|
||||
if err := c.Get(ctx, key, &secret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return secret.Data, nil
|
||||
}
|
|
@ -0,0 +1,685 @@
|
|||
/*
|
||||
Copyright 2024 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 source
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing/transport"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
"github.com/fluxcd/image-automation-controller/internal/testutil"
|
||||
"github.com/fluxcd/pkg/apis/meta"
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"github.com/fluxcd/pkg/git/github"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
)
|
||||
|
||||
func Test_getAuthOpts(t *testing.T) {
|
||||
namespace := "default"
|
||||
|
||||
invalidAuthSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "invalid-auth",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"password": []byte("pass"),
|
||||
},
|
||||
}
|
||||
|
||||
validAuthSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "valid-auth",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"username": []byte("user"),
|
||||
"password": []byte("pass"),
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
secretName string
|
||||
want *git.AuthOptions
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "non-existing secret",
|
||||
secretName: "non-existing",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid secret",
|
||||
url: "https://example.com",
|
||||
secretName: "invalid-auth",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "valid secret",
|
||||
url: "https://example.com",
|
||||
secretName: "valid-auth",
|
||||
want: &git.AuthOptions{
|
||||
Transport: git.HTTPS,
|
||||
Host: "example.com",
|
||||
Username: "user",
|
||||
Password: "pass",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "no secret",
|
||||
url: "https://example.com",
|
||||
want: &git.AuthOptions{
|
||||
Transport: git.HTTPS,
|
||||
Host: "example.com",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid URL",
|
||||
url: "://example.com",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
clientBuilder := fakeclient.NewClientBuilder().
|
||||
WithScheme(scheme.Scheme).
|
||||
WithObjects(invalidAuthSecret, validAuthSecret)
|
||||
c := clientBuilder.Build()
|
||||
|
||||
gitRepo := &sourcev1.GitRepository{}
|
||||
gitRepo.Namespace = namespace
|
||||
gitRepo.Spec = sourcev1.GitRepositorySpec{
|
||||
URL: tt.url,
|
||||
}
|
||||
if tt.secretName != "" {
|
||||
gitRepo.Spec.SecretRef = &meta.LocalObjectReference{Name: tt.secretName}
|
||||
}
|
||||
|
||||
got, err := getAuthOpts(context.TODO(), c, gitRepo, SourceOptions{}, nil)
|
||||
if (err != nil) != tt.wantErr {
|
||||
g.Fail(fmt.Sprintf("unexpected error: %v", err))
|
||||
return
|
||||
}
|
||||
g.Expect(got).To(Equal(tt.want))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getAuthOpts_providerAuth(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
secret *corev1.Secret
|
||||
beforeFunc func(obj *sourcev1.GitRepository)
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "azure provider",
|
||||
url: "https://dev.azure.com/foo/bar/_git/baz",
|
||||
beforeFunc: func(obj *sourcev1.GitRepository) {
|
||||
obj.Spec.Provider = sourcev1.GitProviderAzure
|
||||
},
|
||||
wantErr: "ManagedIdentityCredential",
|
||||
},
|
||||
{
|
||||
name: "github provider with no secret ref",
|
||||
url: "https://github.com/org/repo.git",
|
||||
beforeFunc: func(obj *sourcev1.GitRepository) {
|
||||
obj.Spec.Provider = sourcev1.GitProviderGitHub
|
||||
},
|
||||
wantErr: "secretRef with github app data must be specified when provider is set to github: invalid source configuration",
|
||||
},
|
||||
{
|
||||
name: "github provider with secret ref that does not exist",
|
||||
url: "https://github.com/org/repo.git",
|
||||
beforeFunc: func(obj *sourcev1.GitRepository) {
|
||||
obj.Spec.Provider = sourcev1.GitProviderGitHub
|
||||
obj.Spec.SecretRef = &meta.LocalObjectReference{
|
||||
Name: "githubAppSecret",
|
||||
}
|
||||
},
|
||||
wantErr: "failed to get auth secret '/githubAppSecret': secrets \"githubAppSecret\" not found",
|
||||
},
|
||||
{
|
||||
name: "github provider with github app data in secret",
|
||||
url: "https://example.com/org/repo",
|
||||
secret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "githubAppSecret",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
github.AppIDKey: []byte("123"),
|
||||
github.AppInstallationIDKey: []byte("456"),
|
||||
github.AppPrivateKey: []byte("abc"),
|
||||
},
|
||||
},
|
||||
beforeFunc: func(obj *sourcev1.GitRepository) {
|
||||
obj.Spec.Provider = sourcev1.GitProviderGitHub
|
||||
obj.Spec.SecretRef = &meta.LocalObjectReference{
|
||||
Name: "githubAppSecret",
|
||||
}
|
||||
},
|
||||
wantErr: "Key must be a PEM encoded PKCS1 or PKCS8 key",
|
||||
},
|
||||
{
|
||||
name: "generic provider with github app data in secret",
|
||||
url: "https://example.com/org/repo",
|
||||
secret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "githubAppSecret",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
github.AppIDKey: []byte("123"),
|
||||
},
|
||||
},
|
||||
beforeFunc: func(obj *sourcev1.GitRepository) {
|
||||
obj.Spec.Provider = sourcev1.GitProviderGeneric
|
||||
obj.Spec.SecretRef = &meta.LocalObjectReference{
|
||||
Name: "githubAppSecret",
|
||||
}
|
||||
},
|
||||
wantErr: "secretRef '/githubAppSecret' has github app data but provider is not set to github: invalid source configuration",
|
||||
},
|
||||
{
|
||||
name: "generic provider",
|
||||
url: "https://example.com/org/repo",
|
||||
beforeFunc: func(obj *sourcev1.GitRepository) {
|
||||
obj.Spec.Provider = sourcev1.GitProviderGeneric
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no provider",
|
||||
url: "https://example.com/org/repo",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
clientBuilder := fakeclient.NewClientBuilder().
|
||||
WithScheme(scheme.Scheme).
|
||||
WithStatusSubresource(&sourcev1.GitRepository{})
|
||||
|
||||
if tt.secret != nil {
|
||||
clientBuilder.WithObjects(tt.secret)
|
||||
}
|
||||
c := clientBuilder.Build()
|
||||
obj := &sourcev1.GitRepository{
|
||||
Spec: sourcev1.GitRepositorySpec{
|
||||
URL: tt.url,
|
||||
},
|
||||
}
|
||||
|
||||
if tt.beforeFunc != nil {
|
||||
tt.beforeFunc(obj)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
opts, err := getAuthOpts(ctx, c, obj, SourceOptions{}, nil)
|
||||
|
||||
if tt.wantErr != "" {
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(err.Error()).To(ContainSubstring(tt.wantErr))
|
||||
} else {
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(opts).ToNot(BeNil())
|
||||
g.Expect(opts.BearerToken).To(BeEmpty())
|
||||
g.Expect(opts.Username).To(BeEmpty())
|
||||
g.Expect(opts.Password).To(BeEmpty())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getProxyOpts(t *testing.T) {
|
||||
namespace := "default"
|
||||
invalidProxy := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "invalid-proxy",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"url": []byte("https://example.com"),
|
||||
},
|
||||
}
|
||||
validProxy := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "valid-proxy",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"address": []byte("https://example.com"),
|
||||
"username": []byte("user"),
|
||||
"password": []byte("pass"),
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
secretName string
|
||||
want *transport.ProxyOptions
|
||||
wantProxyURL *url.URL
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "non-existing secret",
|
||||
secretName: "non-existing",
|
||||
want: nil,
|
||||
wantProxyURL: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid proxy secret",
|
||||
secretName: "invalid-proxy",
|
||||
want: nil,
|
||||
wantProxyURL: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "valid proxy secret",
|
||||
secretName: "valid-proxy",
|
||||
want: &transport.ProxyOptions{
|
||||
URL: "https://example.com",
|
||||
Username: "user",
|
||||
Password: "pass",
|
||||
},
|
||||
wantProxyURL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "example.com",
|
||||
User: url.UserPassword("user", "pass"),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
clientBuilder := fakeclient.NewClientBuilder().
|
||||
WithScheme(scheme.Scheme).
|
||||
WithObjects(invalidProxy, validProxy)
|
||||
c := clientBuilder.Build()
|
||||
|
||||
gitRepo := &sourcev1.GitRepository{}
|
||||
gitRepo.Namespace = namespace
|
||||
if tt.secretName != "" {
|
||||
gitRepo.Spec = sourcev1.GitRepositorySpec{
|
||||
ProxySecretRef: &meta.LocalObjectReference{Name: tt.secretName},
|
||||
}
|
||||
}
|
||||
|
||||
got, gotProxyURL, err := getProxyOpts(context.TODO(), c, gitRepo)
|
||||
if (err != nil) != tt.wantErr {
|
||||
g.Fail(fmt.Sprintf("unexpected error: %v", err))
|
||||
return
|
||||
}
|
||||
g.Expect(got).To(Equal(tt.want))
|
||||
g.Expect(gotProxyURL).To(Equal(tt.wantProxyURL))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getSigningEntity(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
namespace := "default"
|
||||
|
||||
passphrase := "abcde12345"
|
||||
_, keyEncrypted := testutil.GetSigningKeyPair(g, passphrase)
|
||||
encryptedKeySecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "encrypted-key",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
signingSecretKey: keyEncrypted,
|
||||
signingPassphraseKey: []byte(passphrase),
|
||||
},
|
||||
}
|
||||
|
||||
_, keyUnencrypted := testutil.GetSigningKeyPair(g, "")
|
||||
unencryptedKeySecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "unencrypted-key",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
signingSecretKey: keyUnencrypted,
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
secretName string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "non-existing secret",
|
||||
secretName: "non-existing",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "unencrypted key",
|
||||
secretName: "unencrypted-key",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "encrypted key",
|
||||
secretName: "encrypted-key",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
clientBuilder := fakeclient.NewClientBuilder().
|
||||
WithScheme(scheme.Scheme).
|
||||
WithObjects(encryptedKeySecret, unencryptedKeySecret)
|
||||
c := clientBuilder.Build()
|
||||
|
||||
gitSpec := &imagev1.GitSpec{}
|
||||
if tt.secretName != "" {
|
||||
gitSpec.Commit = imagev1.CommitSpec{
|
||||
SigningKey: &imagev1.SigningKey{
|
||||
SecretRef: meta.LocalObjectReference{Name: tt.secretName},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
_, err := getSigningEntity(context.TODO(), c, namespace, gitSpec)
|
||||
if (err != nil) != tt.wantErr {
|
||||
g.Fail(fmt.Sprintf("unexpected error: %v", err))
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_buildGitConfig(t *testing.T) {
|
||||
testGitRepoName := "test-gitrepo"
|
||||
namespace := "foo-ns"
|
||||
testTimeout := &metav1.Duration{Duration: time.Minute}
|
||||
testGitURL := "https://example.com"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
gitSpec *imagev1.GitSpec
|
||||
gitRepoName string
|
||||
gitRepoRef *sourcev1.GitRepositoryRef
|
||||
gitRepoTimeout *metav1.Duration
|
||||
gitRepoURL string
|
||||
gitRepoProxyData map[string][]byte
|
||||
srcOpts SourceOptions
|
||||
wantErr bool
|
||||
wantCheckoutRef *sourcev1.GitRepositoryRef
|
||||
wantPushBranch string
|
||||
wantSwitchBranch bool
|
||||
wantTimeout *metav1.Duration
|
||||
}{
|
||||
{
|
||||
name: "same branch, gitSpec checkoutRef",
|
||||
gitSpec: &imagev1.GitSpec{
|
||||
Checkout: &imagev1.GitCheckoutSpec{
|
||||
Reference: sourcev1.GitRepositoryRef{Branch: "aaa"},
|
||||
},
|
||||
},
|
||||
gitRepoName: testGitRepoName,
|
||||
gitRepoURL: testGitURL,
|
||||
wantErr: false,
|
||||
wantCheckoutRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "aaa",
|
||||
},
|
||||
wantPushBranch: "aaa",
|
||||
wantSwitchBranch: false,
|
||||
wantTimeout: testTimeout,
|
||||
},
|
||||
{
|
||||
name: "different branch, gitSpec checkoutRef",
|
||||
gitSpec: &imagev1.GitSpec{
|
||||
Checkout: &imagev1.GitCheckoutSpec{
|
||||
Reference: sourcev1.GitRepositoryRef{Branch: "aaa"},
|
||||
},
|
||||
Push: &imagev1.PushSpec{
|
||||
Branch: "bbb",
|
||||
},
|
||||
},
|
||||
gitRepoName: testGitRepoName,
|
||||
gitRepoURL: testGitURL,
|
||||
wantErr: false,
|
||||
wantCheckoutRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "aaa",
|
||||
},
|
||||
wantPushBranch: "bbb",
|
||||
wantSwitchBranch: true,
|
||||
wantTimeout: testTimeout,
|
||||
},
|
||||
{
|
||||
name: "same branch, gitrepo checkoutRef",
|
||||
gitSpec: &imagev1.GitSpec{},
|
||||
gitRepoName: testGitRepoName,
|
||||
gitRepoURL: testGitURL,
|
||||
gitRepoRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "ccc",
|
||||
},
|
||||
wantErr: false,
|
||||
wantCheckoutRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "ccc",
|
||||
},
|
||||
wantPushBranch: "ccc",
|
||||
wantSwitchBranch: false,
|
||||
wantTimeout: testTimeout,
|
||||
},
|
||||
{
|
||||
name: "different branch, gitrepo checkoutRef",
|
||||
gitSpec: &imagev1.GitSpec{
|
||||
Push: &imagev1.PushSpec{
|
||||
Branch: "ddd",
|
||||
},
|
||||
},
|
||||
gitRepoName: testGitRepoName,
|
||||
gitRepoURL: testGitURL,
|
||||
gitRepoRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "ccc",
|
||||
},
|
||||
wantErr: false,
|
||||
wantCheckoutRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "ccc",
|
||||
},
|
||||
wantPushBranch: "ddd",
|
||||
wantSwitchBranch: true,
|
||||
wantTimeout: testTimeout,
|
||||
},
|
||||
{
|
||||
name: "no checkoutRef defined",
|
||||
gitSpec: &imagev1.GitSpec{
|
||||
Push: &imagev1.PushSpec{
|
||||
Branch: "aaa",
|
||||
},
|
||||
},
|
||||
gitRepoName: testGitRepoName,
|
||||
gitRepoURL: testGitURL,
|
||||
wantErr: false,
|
||||
wantCheckoutRef: nil, // Use the git default checkout branch.
|
||||
wantPushBranch: "aaa",
|
||||
wantSwitchBranch: true,
|
||||
wantTimeout: testTimeout,
|
||||
},
|
||||
{
|
||||
name: "gitSpec override gitRepo checkout config",
|
||||
gitSpec: &imagev1.GitSpec{
|
||||
Checkout: &imagev1.GitCheckoutSpec{
|
||||
Reference: sourcev1.GitRepositoryRef{Branch: "aaa"},
|
||||
},
|
||||
Push: &imagev1.PushSpec{
|
||||
Branch: "bbb",
|
||||
},
|
||||
},
|
||||
gitRepoName: testGitRepoName,
|
||||
gitRepoURL: testGitURL,
|
||||
gitRepoRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "ccc",
|
||||
},
|
||||
wantErr: false,
|
||||
wantCheckoutRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "aaa",
|
||||
},
|
||||
wantPushBranch: "bbb",
|
||||
wantSwitchBranch: true,
|
||||
wantTimeout: testTimeout,
|
||||
},
|
||||
{
|
||||
name: "non-existing gitRepo",
|
||||
gitSpec: &imagev1.GitSpec{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "use gitrepo timeout",
|
||||
gitSpec: &imagev1.GitSpec{},
|
||||
gitRepoName: testGitRepoName,
|
||||
gitRepoURL: testGitURL,
|
||||
gitRepoRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "ccc",
|
||||
},
|
||||
gitRepoTimeout: &metav1.Duration{Duration: 30 * time.Second},
|
||||
wantErr: false,
|
||||
wantCheckoutRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "ccc",
|
||||
},
|
||||
wantPushBranch: "ccc",
|
||||
wantSwitchBranch: false,
|
||||
wantTimeout: &metav1.Duration{Duration: 30 * time.Second},
|
||||
},
|
||||
{
|
||||
name: "bad git URL",
|
||||
gitSpec: &imagev1.GitSpec{},
|
||||
gitRepoName: testGitRepoName,
|
||||
gitRepoURL: "://example.com",
|
||||
gitRepoRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "ccc",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "proxy config",
|
||||
gitSpec: &imagev1.GitSpec{},
|
||||
gitRepoName: testGitRepoName,
|
||||
gitRepoURL: testGitURL,
|
||||
gitRepoRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "ccc",
|
||||
},
|
||||
gitRepoProxyData: map[string][]byte{
|
||||
"address": []byte("http://example.com"),
|
||||
},
|
||||
wantErr: false,
|
||||
wantCheckoutRef: &sourcev1.GitRepositoryRef{
|
||||
Branch: "ccc",
|
||||
},
|
||||
wantPushBranch: "ccc",
|
||||
wantSwitchBranch: false,
|
||||
wantTimeout: testTimeout,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
testObjects := []client.Object{}
|
||||
|
||||
var proxySecret *corev1.Secret
|
||||
if tt.gitRepoProxyData != nil {
|
||||
proxySecret = &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "valid-proxy",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: tt.gitRepoProxyData,
|
||||
}
|
||||
testObjects = append(testObjects, proxySecret)
|
||||
}
|
||||
|
||||
var gitRepo *sourcev1.GitRepository
|
||||
if tt.gitRepoName != "" {
|
||||
gitRepo = &sourcev1.GitRepository{}
|
||||
gitRepo.Name = testGitRepoName
|
||||
gitRepo.Namespace = namespace
|
||||
gitRepo.Spec = sourcev1.GitRepositorySpec{}
|
||||
if tt.gitRepoURL != "" {
|
||||
gitRepo.Spec.URL = tt.gitRepoURL
|
||||
}
|
||||
if tt.gitRepoRef != nil {
|
||||
gitRepo.Spec.Reference = tt.gitRepoRef
|
||||
}
|
||||
if tt.gitRepoTimeout != nil {
|
||||
gitRepo.Spec.Timeout = tt.gitRepoTimeout
|
||||
}
|
||||
if proxySecret != nil {
|
||||
gitRepo.Spec.ProxySecretRef = &meta.LocalObjectReference{Name: proxySecret.Name}
|
||||
}
|
||||
testObjects = append(testObjects, gitRepo)
|
||||
}
|
||||
|
||||
clientBuilder := fakeclient.NewClientBuilder().
|
||||
WithScheme(scheme.Scheme).
|
||||
WithObjects(testObjects...)
|
||||
c := clientBuilder.Build()
|
||||
|
||||
gitRepoKey := types.NamespacedName{
|
||||
Namespace: namespace,
|
||||
Name: tt.gitRepoName,
|
||||
}
|
||||
|
||||
updateAutoKey := types.NamespacedName{
|
||||
Namespace: namespace,
|
||||
Name: "test-update",
|
||||
}
|
||||
|
||||
gitSrcCfg, err := buildGitConfig(context.TODO(), c, updateAutoKey, gitRepoKey, tt.gitSpec, tt.srcOpts)
|
||||
if (err != nil) != tt.wantErr {
|
||||
g.Fail(fmt.Sprintf("unexpected error: %v", err))
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
g.Expect(gitSrcCfg.checkoutRef).To(Equal(tt.wantCheckoutRef), "unexpected checkoutRef")
|
||||
g.Expect(gitSrcCfg.pushBranch).To(Equal(tt.wantPushBranch), "unexpected push branch")
|
||||
g.Expect(gitSrcCfg.switchBranch).To(Equal(tt.wantSwitchBranch), "unexpected switch branch")
|
||||
g.Expect(gitSrcCfg.timeout).To(Equal(tt.wantTimeout), "unexpected git operation timeout")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,441 @@
|
|||
/*
|
||||
Copyright 2024 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 source
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/fluxcd/pkg/cache"
|
||||
"github.com/fluxcd/pkg/git"
|
||||
"github.com/fluxcd/pkg/git/gogit"
|
||||
"github.com/fluxcd/pkg/git/repository"
|
||||
"github.com/fluxcd/pkg/runtime/acl"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
"github.com/fluxcd/pkg/runtime/logger"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
"github.com/fluxcd/image-automation-controller/pkg/update"
|
||||
)
|
||||
|
||||
// ErrInvalidSourceConfiguration is an error for invalid source configuration.
|
||||
var ErrInvalidSourceConfiguration = errors.New("invalid source configuration")
|
||||
|
||||
const defaultMessageTemplate = `Update from image update automation`
|
||||
|
||||
// TemplateData is the type of the value given to the commit message
|
||||
// template.
|
||||
type TemplateData struct {
|
||||
AutomationObject types.NamespacedName
|
||||
Updated update.Result
|
||||
Changed update.ResultV2
|
||||
Values map[string]string
|
||||
}
|
||||
|
||||
// SourceManager manages source.
|
||||
type SourceManager struct {
|
||||
srcCfg *gitSrcCfg
|
||||
automationObjKey types.NamespacedName
|
||||
gitClient *gogit.Client
|
||||
workingDir string
|
||||
}
|
||||
|
||||
// SourceOptions contains the optional attributes of SourceManager.
|
||||
type SourceOptions struct {
|
||||
noCrossNamespaceRef bool
|
||||
gitAllBranchReferences bool
|
||||
tokenCache *cache.TokenCache
|
||||
objName string
|
||||
objNamespace string
|
||||
}
|
||||
|
||||
// SourceOption configures the SourceManager options.
|
||||
type SourceOption func(*SourceOptions)
|
||||
|
||||
// WithSourceOptionNoCrossNamespaceRef configures the SourceManager to disable
|
||||
// cross namespace references.
|
||||
func WithSourceOptionNoCrossNamespaceRef() SourceOption {
|
||||
return func(so *SourceOptions) {
|
||||
so.noCrossNamespaceRef = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithSourceOptionGitAllBranchReferences configures the SourceManager to fetch
|
||||
// all the Git branch references that are present in the remote repository.
|
||||
func WithSourceOptionGitAllBranchReferences() SourceOption {
|
||||
return func(so *SourceOptions) {
|
||||
so.gitAllBranchReferences = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithSourceOptionTokenCache configures the SourceManager to use the provided
|
||||
// token cache.
|
||||
func WithSourceOptionTokenCache(tc *cache.TokenCache) SourceOption {
|
||||
return func(so *SourceOptions) {
|
||||
so.tokenCache = tc
|
||||
}
|
||||
}
|
||||
|
||||
// WithSourceOptionInvolvedObject configures the SourceManager to use the
|
||||
// provided ImageUpdateAutomation object.
|
||||
func WithSourceOptionInvolvedObject(name, namespace string) SourceOption {
|
||||
return func(so *SourceOptions) {
|
||||
so.objName = name
|
||||
so.objNamespace = namespace
|
||||
}
|
||||
}
|
||||
|
||||
// NewSourceManager takes all the provided inputs, validates them and returns a
|
||||
// SourceManager which can be used to operate on the configured source.
|
||||
func NewSourceManager(ctx context.Context, c client.Client, obj *imagev1.ImageUpdateAutomation, options ...SourceOption) (*SourceManager, error) {
|
||||
opts := &SourceOptions{}
|
||||
for _, o := range options {
|
||||
o(opts)
|
||||
}
|
||||
|
||||
// Only GitRepository source is supported.
|
||||
if obj.Spec.SourceRef.Kind != sourcev1.GitRepositoryKind {
|
||||
return nil, fmt.Errorf("source kind '%s' not supported: %w", obj.Spec.SourceRef.Kind, ErrInvalidSourceConfiguration)
|
||||
}
|
||||
|
||||
if obj.Spec.GitSpec == nil {
|
||||
return nil, fmt.Errorf("source kind '%s' necessitates field .spec.git: %w", sourcev1.GitRepositoryKind, ErrInvalidSourceConfiguration)
|
||||
}
|
||||
|
||||
// Build source reference configuration to fetch and validate it.
|
||||
srcNamespace := obj.GetNamespace()
|
||||
if obj.Spec.SourceRef.Namespace != "" {
|
||||
srcNamespace = obj.Spec.SourceRef.Namespace
|
||||
}
|
||||
|
||||
// srcKey is the GitRepository object key.
|
||||
srcKey := types.NamespacedName{Name: obj.Spec.SourceRef.Name, Namespace: srcNamespace}
|
||||
// originKey is the update automation object key.
|
||||
originKey := client.ObjectKeyFromObject(obj)
|
||||
|
||||
// Check if the source is accessible.
|
||||
if opts.noCrossNamespaceRef && srcKey.Namespace != obj.GetNamespace() {
|
||||
return nil, acl.AccessDeniedError(fmt.Sprintf("can't access '%s/%s', cross-namespace references have been blocked", sourcev1.GitRepositoryKind, srcKey))
|
||||
}
|
||||
|
||||
gitSrcCfg, err := buildGitConfig(ctx, c, originKey, srcKey, obj.Spec.GitSpec, *opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
workDir, err := os.MkdirTemp("", fmt.Sprintf("%s-%s", gitSrcCfg.srcKey.Namespace, gitSrcCfg.srcKey.Name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sm := &SourceManager{
|
||||
srcCfg: gitSrcCfg,
|
||||
automationObjKey: originKey,
|
||||
workingDir: workDir,
|
||||
}
|
||||
return sm, nil
|
||||
}
|
||||
|
||||
// CreateWorkingDirectory creates a working directory for the SourceManager.
|
||||
func (sm SourceManager) WorkDirectory() string {
|
||||
return sm.workingDir
|
||||
}
|
||||
|
||||
// Cleanup deletes the working directory of the SourceManager.
|
||||
func (sm SourceManager) Cleanup() error {
|
||||
return os.RemoveAll(sm.workingDir)
|
||||
}
|
||||
|
||||
// SwitchBranch returns if the checkout branch and push branch are different.
|
||||
func (sm SourceManager) SwitchBranch() bool {
|
||||
return sm.srcCfg.switchBranch
|
||||
}
|
||||
|
||||
// CheckoutOption allows configuring the checkout options.
|
||||
type CheckoutOption func(*repository.CloneConfig)
|
||||
|
||||
// WithCheckoutOptionLastObserved is a CheckoutOption option to configure the
|
||||
// last observed commit.
|
||||
func WithCheckoutOptionLastObserved(commit string) CheckoutOption {
|
||||
return func(cc *repository.CloneConfig) {
|
||||
cc.LastObservedCommit = commit
|
||||
}
|
||||
}
|
||||
|
||||
// WithCheckoutOptionShallowClone is a CheckoutOption option to configure
|
||||
// shallow clone.
|
||||
func WithCheckoutOptionShallowClone() CheckoutOption {
|
||||
return func(cc *repository.CloneConfig) {
|
||||
cc.ShallowClone = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithCheckoutOptionSparseCheckoutDirectories is a CheckoutOption option to configure
|
||||
// SparseCheckoutDirectories.
|
||||
func WithCheckoutOptionSparseCheckoutDirectories(updatePath string) CheckoutOption {
|
||||
return func(cc *repository.CloneConfig) {
|
||||
cleanedPath := filepath.Clean(updatePath)
|
||||
if cleanedPath == "." {
|
||||
// Do not set SparseCheckoutDirectories if repository root is specified
|
||||
return
|
||||
}
|
||||
cc.SparseCheckoutDirectories = []string{cleanedPath}
|
||||
}
|
||||
}
|
||||
|
||||
// CheckoutSource clones and checks out the source. If a push branch is
|
||||
// configured that doesn't match with the checkout branch, a checkout to the
|
||||
// push branch is also performed. This ensures any change and push operation
|
||||
// following the checkout happens on the push branch.
|
||||
func (sm *SourceManager) CheckoutSource(ctx context.Context, options ...CheckoutOption) (*git.Commit, error) {
|
||||
// Configuration clone options.
|
||||
cloneCfg := repository.CloneConfig{}
|
||||
if sm.srcCfg.checkoutRef != nil {
|
||||
cloneCfg.Tag = sm.srcCfg.checkoutRef.Tag
|
||||
cloneCfg.SemVer = sm.srcCfg.checkoutRef.SemVer
|
||||
cloneCfg.Commit = sm.srcCfg.checkoutRef.Commit
|
||||
cloneCfg.Branch = sm.srcCfg.checkoutRef.Branch
|
||||
}
|
||||
// Apply checkout configurations.
|
||||
for _, o := range options {
|
||||
o(&cloneCfg)
|
||||
}
|
||||
|
||||
var err error
|
||||
sm.gitClient, err = gogit.NewClient(sm.workingDir, sm.srcCfg.authOpts, sm.srcCfg.clientOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gitOpCtx, cancel := context.WithTimeout(ctx, sm.srcCfg.timeout.Duration)
|
||||
defer cancel()
|
||||
commit, err := sm.gitClient.Clone(gitOpCtx, sm.srcCfg.url, cloneCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sm.srcCfg.switchBranch {
|
||||
if err := sm.gitClient.SwitchBranch(gitOpCtx, sm.srcCfg.pushBranch); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return commit, nil
|
||||
}
|
||||
|
||||
// PushConfig configures the options used in push operation.
|
||||
type PushConfig func(*repository.PushConfig)
|
||||
|
||||
// WithPushConfigForce configures the PushConfig to use force.
|
||||
func WithPushConfigForce() PushConfig {
|
||||
return func(pc *repository.PushConfig) {
|
||||
pc.Force = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithPushConfigOptions configures the PushConfig Options that are used in
|
||||
// push.
|
||||
func WithPushConfigOptions(opts map[string]string) PushConfig {
|
||||
return func(pc *repository.PushConfig) {
|
||||
pc.Options = opts
|
||||
}
|
||||
}
|
||||
|
||||
// CommitAndPush performs a commit in the source and pushes it to the remote
|
||||
// repository.
|
||||
func (sm SourceManager) CommitAndPush(ctx context.Context, obj *imagev1.ImageUpdateAutomation, policyResult update.ResultV2, pushOptions ...PushConfig) (*PushResult, error) {
|
||||
tracelog := log.FromContext(ctx).V(logger.TraceLevel)
|
||||
|
||||
// Make sure there were file changes that need to be committed.
|
||||
if len(policyResult.FileChanges) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Perform a Git commit.
|
||||
templateValues := &TemplateData{
|
||||
AutomationObject: sm.automationObjKey,
|
||||
Updated: policyResult.ImageResult,
|
||||
Changed: policyResult,
|
||||
Values: obj.Spec.GitSpec.Commit.MessageTemplateValues,
|
||||
}
|
||||
commitMsg, err := templateMsg(obj.Spec.GitSpec.Commit.MessageTemplate, templateValues)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signature := git.Signature{
|
||||
Name: obj.Spec.GitSpec.Commit.Author.Name,
|
||||
Email: obj.Spec.GitSpec.Commit.Author.Email,
|
||||
When: time.Now(),
|
||||
}
|
||||
|
||||
var rev string
|
||||
var commitErr error
|
||||
rev, commitErr = sm.gitClient.Commit(
|
||||
git.Commit{
|
||||
Author: signature,
|
||||
Message: commitMsg,
|
||||
},
|
||||
repository.WithSigner(sm.srcCfg.signingEntity),
|
||||
)
|
||||
|
||||
if commitErr != nil {
|
||||
if !errors.Is(commitErr, git.ErrNoStagedFiles) {
|
||||
return nil, commitErr
|
||||
}
|
||||
log.FromContext(ctx).Info("no changes made in the source; no commit")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Push the commit to push branch.
|
||||
gitOpCtx, cancel := context.WithTimeout(ctx, sm.srcCfg.timeout.Duration)
|
||||
defer cancel()
|
||||
pushConfig := repository.PushConfig{}
|
||||
for _, po := range pushOptions {
|
||||
po(&pushConfig)
|
||||
}
|
||||
if err := sm.gitClient.Push(gitOpCtx, pushConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tracelog.Info("pushed commit to push branch", "revision", rev, "branch", sm.srcCfg.pushBranch)
|
||||
|
||||
// Push to any provided refspec.
|
||||
if obj.Spec.GitSpec.HasRefspec() {
|
||||
pushConfig.Refspecs = append(pushConfig.Refspecs, obj.Spec.GitSpec.Push.Refspec)
|
||||
if err := sm.gitClient.Push(gitOpCtx, pushConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tracelog.Info("pushed commit to refspec", "revision", rev, "refspecs", pushConfig.Refspecs)
|
||||
}
|
||||
|
||||
// Construct the result of the push operation and return.
|
||||
prOpts := []PushResultOption{WithPushResultRefspec(pushConfig.Refspecs)}
|
||||
if sm.srcCfg.switchBranch {
|
||||
prOpts = append(prOpts, WithPushResultSwitchBranch())
|
||||
}
|
||||
return NewPushResult(sm.srcCfg.pushBranch, rev, commitMsg, prOpts...)
|
||||
}
|
||||
|
||||
// templateMsg renders a msg template, returning the message or an error.
|
||||
func templateMsg(messageTemplate string, templateValues *TemplateData) (string, error) {
|
||||
if messageTemplate == "" {
|
||||
messageTemplate = defaultMessageTemplate
|
||||
}
|
||||
|
||||
// Includes only functions that are guaranteed to always evaluate to the same result for given input.
|
||||
// This removes the possibility of accidentally relying on where or when the template runs.
|
||||
// https://github.com/Masterminds/sprig/blob/3ac42c7bc5e4be6aa534e036fb19dde4a996da2e/functions.go#L70
|
||||
t, err := template.New("commit message").Funcs(sprig.HermeticTxtFuncMap()).Parse(messageTemplate)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to create commit message template from spec: %w", err)
|
||||
}
|
||||
|
||||
b := &strings.Builder{}
|
||||
if err := t.Execute(b, *templateValues); err != nil {
|
||||
return "", fmt.Errorf("failed to run template from spec: %w", err)
|
||||
}
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
// PushResultOption allows configuring the options of PushResult.
|
||||
type PushResultOption func(*PushResult)
|
||||
|
||||
// WithPushResultSwitchBranch marks the PushResult with switchBranch.
|
||||
func WithPushResultSwitchBranch() func(*PushResult) {
|
||||
return func(pr *PushResult) {
|
||||
pr.switchBranch = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithPushResultRefspec sets the refspecs in the PushResult.
|
||||
func WithPushResultRefspec(refspecs []string) func(*PushResult) {
|
||||
return func(pr *PushResult) {
|
||||
pr.refspecs = append(pr.refspecs, refspecs...)
|
||||
}
|
||||
}
|
||||
|
||||
// PushResult is the result of a push operation.
|
||||
type PushResult struct {
|
||||
commit *git.Commit
|
||||
switchBranch bool
|
||||
branch string
|
||||
refspecs []string
|
||||
creationTime *metav1.Time
|
||||
}
|
||||
|
||||
// NewPushResult returns a new PushResult.
|
||||
func NewPushResult(branch string, rev string, commitMsg string, opts ...PushResultOption) (*PushResult, error) {
|
||||
if rev == "" {
|
||||
return nil, errors.New("empty push commit revision")
|
||||
}
|
||||
|
||||
pr := &PushResult{}
|
||||
for _, o := range opts {
|
||||
o(pr)
|
||||
}
|
||||
pr.commit = &git.Commit{
|
||||
Hash: git.ExtractHashFromRevision(rev),
|
||||
Reference: plumbing.NewBranchReferenceName(branch).String(),
|
||||
Message: commitMsg,
|
||||
}
|
||||
pr.branch = branch
|
||||
pr.creationTime = &metav1.Time{Time: time.Now()}
|
||||
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
// Commit returns the revision of the pushed commit.
|
||||
func (pr PushResult) Commit() *git.Commit {
|
||||
return pr.commit
|
||||
}
|
||||
|
||||
// Time returns the time at which the push was performed.
|
||||
func (pr PushResult) Time() *metav1.Time {
|
||||
return pr.creationTime
|
||||
}
|
||||
|
||||
// SwitchBranch returns if the source has different checkout and push branch.
|
||||
func (pr PushResult) SwitchBranch() bool {
|
||||
return pr.switchBranch
|
||||
}
|
||||
|
||||
// Summary returns a summary of the PushResult.
|
||||
func (pr PushResult) Summary() string {
|
||||
var summary strings.Builder
|
||||
shortCommitHash := pr.Commit().Hash.String()
|
||||
if len(shortCommitHash) > 7 {
|
||||
shortCommitHash = shortCommitHash[:7]
|
||||
}
|
||||
summary.WriteString(fmt.Sprintf("pushed commit '%s' to branch '%s'", shortCommitHash, pr.branch))
|
||||
if len(pr.refspecs) > 0 {
|
||||
summary.WriteString(fmt.Sprintf(" and refspecs '%s'", strings.Join(pr.refspecs, "', '")))
|
||||
}
|
||||
if pr.Commit().Message != "" {
|
||||
summary.WriteString(fmt.Sprintf("\n%s", pr.Commit().Message))
|
||||
}
|
||||
return summary.String()
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,10 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: hello
|
||||
image: helloworld:1.0.0 # SETTER_SITE
|
|
@ -0,0 +1,480 @@
|
|||
/*
|
||||
Copyright 2024 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 testutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
"github.com/go-git/go-billy/v5/osfs"
|
||||
extgogit "github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/config"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/cache"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/go-git/go-git/v5/storage/filesystem"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
|
||||
"github.com/fluxcd/pkg/gittestserver"
|
||||
|
||||
"github.com/fluxcd/image-automation-controller/internal/constants"
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
const (
|
||||
signingSecretKey = "git.asc"
|
||||
signingPassphraseKey = "passphrase"
|
||||
)
|
||||
|
||||
func CheckoutBranch(g *WithT, repo *extgogit.Repository, branch string) {
|
||||
g.THelper()
|
||||
|
||||
wt, err := repo.Worktree()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = wt.Checkout(&extgogit.CheckoutOptions{
|
||||
Branch: plumbing.NewBranchReferenceName(branch),
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
func ReplaceMarker(path string, policyKey types.NamespacedName) error {
|
||||
return ReplaceMarkerWithMarker(path, policyKey, "SETTER_SITE")
|
||||
}
|
||||
|
||||
func ReplaceMarkerWithMarker(path string, policyKey types.NamespacedName, marker string) error {
|
||||
filebytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newfilebytes := bytes.ReplaceAll(filebytes, []byte(marker), []byte(setterRef(policyKey)))
|
||||
if err = os.WriteFile(path, newfilebytes, os.FileMode(0666)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setterRef(name types.NamespacedName) string {
|
||||
return fmt.Sprintf(`{"%s": "%s:%s"}`, constants.SetterShortHand, name.Namespace, name.Name)
|
||||
}
|
||||
|
||||
func CommitInRepo(ctx context.Context, g *WithT, repoURL, branch, remote, msg string, changeFiles func(path string)) plumbing.Hash {
|
||||
g.THelper()
|
||||
|
||||
repo, cloneDir, err := Clone(ctx, repoURL, branch, remote)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
defer func() { os.RemoveAll(cloneDir) }()
|
||||
|
||||
wt, err := repo.Worktree()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
changeFiles(wt.Filesystem.Root())
|
||||
|
||||
id := CommitWorkDir(g, repo, branch, msg)
|
||||
|
||||
origin, err := repo.Remote(remote)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
g.Expect(origin.Push(&extgogit.PushOptions{
|
||||
RemoteName: remote,
|
||||
RefSpecs: []config.RefSpec{config.RefSpec(BranchRefName(branch))},
|
||||
})).To(Succeed())
|
||||
return id
|
||||
}
|
||||
|
||||
func WaitForNewHead(g *WithT, repo *extgogit.Repository, branch, remote, preChangeHash string) {
|
||||
g.THelper()
|
||||
|
||||
var commitToResetTo *object.Commit
|
||||
|
||||
origin, err := repo.Remote(remote)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Now try to fetch new commits from that remote branch
|
||||
g.Eventually(func() bool {
|
||||
err := origin.Fetch(&extgogit.FetchOptions{
|
||||
RemoteName: remote,
|
||||
RefSpecs: []config.RefSpec{config.RefSpec(BranchRefName(branch))},
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
wt, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
err = wt.Checkout(&extgogit.CheckoutOptions{
|
||||
Branch: plumbing.NewBranchReferenceName(branch),
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
remoteHeadRef, err := repo.Head()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
remoteHeadHash := remoteHeadRef.Hash()
|
||||
|
||||
if preChangeHash != remoteHeadHash.String() {
|
||||
commitToResetTo, _ = repo.CommitObject(remoteHeadHash)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, 10*time.Second, time.Second).Should(BeTrue())
|
||||
|
||||
if commitToResetTo != nil {
|
||||
wt, err := repo.Worktree()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// New commits in the remote branch -- reset the working tree head
|
||||
// to that. Note this does not create a local branch tracking the
|
||||
// remote, so it is a detached head.
|
||||
g.Expect(wt.Reset(&extgogit.ResetOptions{
|
||||
Commit: commitToResetTo.Hash,
|
||||
Mode: extgogit.HardReset,
|
||||
})).To(Succeed())
|
||||
}
|
||||
}
|
||||
|
||||
// Initialise a git server with a repo including the files in dir.
|
||||
func InitGitRepo(g *WithT, gitServer *gittestserver.GitServer, fixture, branch, repoPath string) *extgogit.Repository {
|
||||
g.THelper()
|
||||
|
||||
workDir, err := securejoin.SecureJoin(gitServer.Root(), repoPath)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
repo := InitGitRepoPlain(g, fixture, workDir)
|
||||
|
||||
headRef, err := repo.Head()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
ref := plumbing.NewHashReference(
|
||||
plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branch)),
|
||||
headRef.Hash())
|
||||
|
||||
g.Expect(repo.Storer.SetReference(ref)).ToNot(HaveOccurred())
|
||||
|
||||
return repo
|
||||
}
|
||||
|
||||
func InitGitRepoPlain(g *WithT, fixture, repoPath string) *extgogit.Repository {
|
||||
g.THelper()
|
||||
|
||||
wt := osfs.New(repoPath)
|
||||
dot := osfs.New(filepath.Join(repoPath, extgogit.GitDirName))
|
||||
storer := filesystem.NewStorage(dot, cache.NewObjectLRUDefault())
|
||||
|
||||
repo, err := extgogit.Init(storer, wt)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
g.Expect(copyDir(fixture, repoPath)).ToNot(HaveOccurred())
|
||||
|
||||
_ = CommitWorkDir(g, repo, "main", "Initial commit")
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
return repo
|
||||
}
|
||||
|
||||
func HeadFromBranch(repo *extgogit.Repository, branchName string) (*object.Commit, error) {
|
||||
ref, err := repo.Storer.Reference(plumbing.ReferenceName("refs/heads/" + branchName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return repo.CommitObject(ref.Hash())
|
||||
}
|
||||
|
||||
func CommitWorkDir(g *WithT, repo *extgogit.Repository, branchName, message string) plumbing.Hash {
|
||||
g.THelper()
|
||||
|
||||
wt, err := repo.Worktree()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Checkout to an existing branch. If this is the first commit,
|
||||
// this is a no-op.
|
||||
_ = wt.Checkout(&extgogit.CheckoutOptions{
|
||||
Branch: plumbing.ReferenceName("refs/heads/" + branchName),
|
||||
})
|
||||
|
||||
status, err := wt.Status()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
for file := range status {
|
||||
wt.Add(file)
|
||||
}
|
||||
|
||||
sig := mockSignature(time.Now())
|
||||
c, err := wt.Commit(message, &extgogit.CommitOptions{
|
||||
All: true,
|
||||
Author: sig,
|
||||
Committer: sig,
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
_, err = repo.Branch(branchName)
|
||||
if err == extgogit.ErrBranchNotFound {
|
||||
ref := plumbing.NewHashReference(
|
||||
plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branchName)), c)
|
||||
err = repo.Storer.SetReference(ref)
|
||||
}
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Now the target branch exists, we can checkout to it.
|
||||
err = wt.Checkout(&extgogit.CheckoutOptions{
|
||||
Branch: plumbing.ReferenceName("refs/heads/" + branchName),
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func TagCommit(g *WithT, repo *extgogit.Repository, commit plumbing.Hash, annotated bool, tag string, time time.Time) (*plumbing.Reference, error) {
|
||||
g.THelper()
|
||||
|
||||
var opts *extgogit.CreateTagOptions
|
||||
if annotated {
|
||||
opts = &extgogit.CreateTagOptions{
|
||||
Tagger: mockSignature(time),
|
||||
Message: "Annotated tag for: " + tag,
|
||||
}
|
||||
}
|
||||
return repo.CreateTag(tag, commit, opts)
|
||||
}
|
||||
|
||||
func copyDir(src string, dest string) error {
|
||||
file, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !file.IsDir() {
|
||||
return fmt.Errorf("source %q must be a directory", file.Name())
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(dest, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
srcFile := filepath.Join(src, f.Name())
|
||||
destFile := filepath.Join(dest, f.Name())
|
||||
|
||||
if f.IsDir() {
|
||||
if err = copyDir(srcFile, destFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !f.IsDir() {
|
||||
// ignore symlinks
|
||||
if f.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
continue
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(srcFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = os.WriteFile(destFile, content, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func BranchRefName(branch string) string {
|
||||
return fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch)
|
||||
}
|
||||
|
||||
func mockSignature(time time.Time) *object.Signature {
|
||||
return &object.Signature{
|
||||
Name: "Jane Doe",
|
||||
Email: "author@example.com",
|
||||
When: time,
|
||||
}
|
||||
}
|
||||
|
||||
func Clone(ctx context.Context, repoURL, branchName, remote string) (*extgogit.Repository, string, error) {
|
||||
dir, err := os.MkdirTemp("", "iac-clone-*")
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
opts := &extgogit.CloneOptions{
|
||||
URL: repoURL,
|
||||
RemoteName: remote,
|
||||
ReferenceName: plumbing.NewBranchReferenceName(branchName),
|
||||
}
|
||||
|
||||
wt := osfs.New(dir, osfs.WithBoundOS())
|
||||
dot := osfs.New(filepath.Join(dir, extgogit.GitDirName), osfs.WithBoundOS())
|
||||
storer := filesystem.NewStorage(dot, cache.NewObjectLRUDefault())
|
||||
|
||||
repo, err := extgogit.Clone(storer, wt, opts)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
w, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
err = w.Checkout(&extgogit.CheckoutOptions{
|
||||
Branch: plumbing.NewBranchReferenceName(branchName),
|
||||
Create: false,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return repo, dir, nil
|
||||
}
|
||||
|
||||
func CommitIdFromBranch(repo *extgogit.Repository, branchName string) string {
|
||||
commitId := ""
|
||||
head, err := HeadFromBranch(repo, branchName)
|
||||
|
||||
if err == nil {
|
||||
commitId = head.Hash.String()
|
||||
}
|
||||
return commitId
|
||||
}
|
||||
|
||||
func GetRemoteHead(repo *extgogit.Repository, branchName, remote string) (plumbing.Hash, error) {
|
||||
rmt, err := repo.Remote(remote)
|
||||
if err != nil {
|
||||
return plumbing.ZeroHash, err
|
||||
}
|
||||
|
||||
err = rmt.Fetch(&extgogit.FetchOptions{
|
||||
RemoteName: remote,
|
||||
RefSpecs: []config.RefSpec{config.RefSpec(BranchRefName(branchName))},
|
||||
})
|
||||
if err != nil && !errors.Is(err, extgogit.NoErrAlreadyUpToDate) {
|
||||
return plumbing.ZeroHash, err
|
||||
}
|
||||
|
||||
remoteHeadRef, err := HeadFromBranch(repo, branchName)
|
||||
if err != nil {
|
||||
return plumbing.ZeroHash, err
|
||||
}
|
||||
|
||||
return remoteHeadRef.Hash, nil
|
||||
}
|
||||
|
||||
// SetUpGitTestServer creates and returns a git test server. The caller must
|
||||
// ensure it's stopped and cleaned up.
|
||||
func SetUpGitTestServer(g *WithT) *gittestserver.GitServer {
|
||||
g.THelper()
|
||||
|
||||
gitServer, err := gittestserver.NewTempGitServer()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
username := rand.String(5)
|
||||
password := rand.String(5)
|
||||
|
||||
gitServer.Auth(username, password)
|
||||
gitServer.AutoCreate()
|
||||
g.Expect(gitServer.StartHTTP()).ToNot(HaveOccurred())
|
||||
gitServer.KeyDir(filepath.Join(gitServer.Root(), "keys"))
|
||||
g.Expect(gitServer.ListenSSH()).ToNot(HaveOccurred())
|
||||
return gitServer
|
||||
}
|
||||
|
||||
func GetSigningKeyPairSecret(g *WithT, name, namespace string) (*corev1.Secret, *openpgp.Entity) {
|
||||
g.THelper()
|
||||
|
||||
passphrase := "abcde12345"
|
||||
pgpEntity, key := GetSigningKeyPair(g, passphrase)
|
||||
|
||||
// Create the secret containing signing key.
|
||||
sec := &corev1.Secret{
|
||||
Data: map[string][]byte{
|
||||
signingSecretKey: key,
|
||||
signingPassphraseKey: []byte(passphrase),
|
||||
},
|
||||
}
|
||||
sec.Name = name
|
||||
sec.Namespace = namespace
|
||||
return sec, pgpEntity
|
||||
}
|
||||
|
||||
func GetSigningKeyPair(g *WithT, passphrase string) (*openpgp.Entity, []byte) {
|
||||
g.THelper()
|
||||
|
||||
pgpEntity, err := openpgp.NewEntity("", "", "", nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Configure OpenPGP armor encoder.
|
||||
b := bytes.NewBuffer(nil)
|
||||
w, err := armor.Encode(b, openpgp.PrivateKeyType, nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
// Serialize private key.
|
||||
g.Expect(pgpEntity.SerializePrivate(w, nil)).To(Succeed())
|
||||
g.Expect(w.Close()).To(Succeed())
|
||||
|
||||
if passphrase != "" {
|
||||
g.Expect(pgpEntity.PrivateKey.Encrypt([]byte(passphrase))).To(Succeed())
|
||||
}
|
||||
|
||||
return pgpEntity, b.Bytes()
|
||||
}
|
||||
|
||||
func ImageToRef(image string) *imagev1_reflect.ImageRef {
|
||||
var digest string
|
||||
|
||||
if idx := strings.LastIndex(image, "@"); idx != -1 {
|
||||
image, digest = image[:idx], image[idx+1:]
|
||||
}
|
||||
|
||||
var tag string
|
||||
|
||||
if idx := strings.LastIndex(image, ":"); idx != -1 {
|
||||
image, tag = image[:idx], image[idx+1:]
|
||||
}
|
||||
|
||||
return &imagev1_reflect.ImageRef{
|
||||
Name: image,
|
||||
Tag: tag,
|
||||
Digest: digest,
|
||||
}
|
||||
}
|
141
main.go
141
main.go
|
@ -21,13 +21,22 @@ import (
|
|||
"os"
|
||||
|
||||
flag "github.com/spf13/pflag"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
"k8s.io/utils/pointer"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
ctrlcache "sigs.k8s.io/controller-runtime/pkg/cache"
|
||||
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
ctrlcfg "sigs.k8s.io/controller-runtime/pkg/config"
|
||||
ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
|
||||
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
|
||||
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta1"
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
"github.com/fluxcd/pkg/auth"
|
||||
cache "github.com/fluxcd/pkg/cache"
|
||||
"github.com/fluxcd/pkg/runtime/acl"
|
||||
"github.com/fluxcd/pkg/runtime/client"
|
||||
helper "github.com/fluxcd/pkg/runtime/controller"
|
||||
|
@ -35,24 +44,22 @@ import (
|
|||
feathelper "github.com/fluxcd/pkg/runtime/features"
|
||||
"github.com/fluxcd/pkg/runtime/leaderelection"
|
||||
"github.com/fluxcd/pkg/runtime/logger"
|
||||
"github.com/fluxcd/pkg/runtime/metrics"
|
||||
"github.com/fluxcd/pkg/runtime/pprof"
|
||||
"github.com/fluxcd/pkg/runtime/probes"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta1"
|
||||
"github.com/fluxcd/pkg/git"
|
||||
|
||||
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
"github.com/fluxcd/image-automation-controller/internal/features"
|
||||
"github.com/fluxcd/source-controller/pkg/git"
|
||||
"github.com/fluxcd/source-controller/pkg/git/libgit2/managed"
|
||||
|
||||
// +kubebuilder:scaffold:imports
|
||||
"github.com/fluxcd/image-automation-controller/controllers"
|
||||
"github.com/fluxcd/image-automation-controller/internal/controller"
|
||||
)
|
||||
|
||||
const (
|
||||
controllerName = "image-automation-controller"
|
||||
|
||||
// recoverPanic indicates whether panic caused by reconciles should be recovered.
|
||||
recoverPanic = true
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -69,6 +76,10 @@ func init() {
|
|||
}
|
||||
|
||||
func main() {
|
||||
const (
|
||||
tokenCacheDefaultMaxSize = 100
|
||||
)
|
||||
|
||||
var (
|
||||
metricsAddr string
|
||||
eventsAddr string
|
||||
|
@ -79,15 +90,14 @@ func main() {
|
|||
leaderElectionOptions leaderelection.Options
|
||||
rateLimiterOptions helper.RateLimiterOptions
|
||||
featureGates feathelper.FeatureGates
|
||||
watchAllNamespaces bool
|
||||
watchOptions helper.WatchOptions
|
||||
concurrent int
|
||||
tokenCacheOptions cache.TokenFlags
|
||||
)
|
||||
|
||||
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
|
||||
flag.StringVar(&eventsAddr, "events-addr", "", "The address of the events receiver.")
|
||||
flag.StringVar(&healthAddr, "health-addr", ":9440", "The address the health endpoint binds to.")
|
||||
flag.BoolVar(&watchAllNamespaces, "watch-all-namespaces", true,
|
||||
"Watch for custom resources in all namespaces, if set to false it will only watch the runtime namespace.")
|
||||
flag.IntVar(&concurrent, "concurrent", 4, "The number of concurrent resource reconciles.")
|
||||
flag.StringSliceVar(&git.KexAlgos, "ssh-kex-algos", []string{},
|
||||
"The list of key exchange algorithms to use for ssh connections, arranged from most preferred to the least.")
|
||||
|
@ -100,11 +110,12 @@ func main() {
|
|||
aclOptions.BindFlags(flag.CommandLine)
|
||||
rateLimiterOptions.BindFlags(flag.CommandLine)
|
||||
featureGates.BindFlags(flag.CommandLine)
|
||||
watchOptions.BindFlags(flag.CommandLine)
|
||||
tokenCacheOptions.BindFlags(flag.CommandLine, tokenCacheDefaultMaxSize)
|
||||
|
||||
flag.Parse()
|
||||
|
||||
log := logger.NewLogger(logOptions)
|
||||
ctrl.SetLogger(log)
|
||||
logger.SetLogger(logger.NewLogger(logOptions))
|
||||
|
||||
err := featureGates.WithLogger(setupLog).
|
||||
SupportedFeatures(features.FeatureGates())
|
||||
|
@ -113,32 +124,84 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
switch enabled, err := features.Enabled(auth.FeatureGateObjectLevelWorkloadIdentity); {
|
||||
case err != nil:
|
||||
setupLog.Error(err, "unable to check feature gate "+auth.FeatureGateObjectLevelWorkloadIdentity)
|
||||
os.Exit(1)
|
||||
case enabled:
|
||||
auth.EnableObjectLevelWorkloadIdentity()
|
||||
}
|
||||
|
||||
watchNamespace := ""
|
||||
if !watchAllNamespaces {
|
||||
if !watchOptions.AllNamespaces {
|
||||
watchNamespace = os.Getenv("RUNTIME_NAMESPACE")
|
||||
}
|
||||
|
||||
var disableCacheFor []ctrlclient.Object
|
||||
shouldCache, err := features.Enabled(features.CacheSecretsAndConfigMaps)
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to check feature gate "+features.CacheSecretsAndConfigMaps)
|
||||
os.Exit(1)
|
||||
}
|
||||
if !shouldCache {
|
||||
disableCacheFor = append(disableCacheFor, &corev1.Secret{}, &corev1.ConfigMap{})
|
||||
}
|
||||
|
||||
restConfig := client.GetConfigOrDie(clientOptions)
|
||||
mgr, err := ctrl.NewManager(restConfig, ctrl.Options{
|
||||
|
||||
watchSelector, err := helper.GetWatchSelector(watchOptions)
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to configure watch label selector for manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
leaderElectionID := fmt.Sprintf("%s-leader-election", controllerName)
|
||||
if watchOptions.LabelSelector != "" {
|
||||
leaderElectionID = leaderelection.GenerateID(leaderElectionID, watchOptions.LabelSelector)
|
||||
}
|
||||
|
||||
mgrConfig := ctrl.Options{
|
||||
Scheme: scheme,
|
||||
MetricsBindAddress: metricsAddr,
|
||||
HealthProbeBindAddress: healthAddr,
|
||||
Port: 9443,
|
||||
LeaderElection: leaderElectionOptions.Enable,
|
||||
LeaderElectionReleaseOnCancel: leaderElectionOptions.ReleaseOnCancel,
|
||||
LeaseDuration: &leaderElectionOptions.LeaseDuration,
|
||||
RenewDeadline: &leaderElectionOptions.RenewDeadline,
|
||||
RetryPeriod: &leaderElectionOptions.RetryPeriod,
|
||||
LeaderElectionID: fmt.Sprintf("%s-leader-election", controllerName),
|
||||
Namespace: watchNamespace,
|
||||
})
|
||||
LeaderElectionID: leaderElectionID,
|
||||
Client: ctrlclient.Options{
|
||||
Cache: &ctrlclient.CacheOptions{
|
||||
DisableFor: disableCacheFor,
|
||||
},
|
||||
},
|
||||
Cache: ctrlcache.Options{
|
||||
ByObject: map[ctrlclient.Object]ctrlcache.ByObject{
|
||||
&imagev1.ImageUpdateAutomation{}: {Label: watchSelector},
|
||||
},
|
||||
},
|
||||
Metrics: metricsserver.Options{
|
||||
BindAddress: metricsAddr,
|
||||
ExtraHandlers: pprof.GetHandlers(),
|
||||
},
|
||||
Controller: ctrlcfg.Controller{
|
||||
RecoverPanic: pointer.Bool(true),
|
||||
MaxConcurrentReconciles: concurrent,
|
||||
},
|
||||
}
|
||||
|
||||
if watchNamespace != "" {
|
||||
mgrConfig.Cache.DefaultNamespaces = map[string]ctrlcache.Config{
|
||||
watchNamespace: ctrlcache.Config{},
|
||||
}
|
||||
}
|
||||
|
||||
mgr, err := ctrl.NewManager(restConfig, mgrConfig)
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to start manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
probes.SetupChecks(mgr, setupLog)
|
||||
pprof.SetupHandlers(mgr, setupLog)
|
||||
|
||||
var eventRecorder *events.Recorder
|
||||
if eventRecorder, err = events.NewRecorder(mgr, ctrl.Log, eventsAddr, controllerName); err != nil {
|
||||
|
@ -146,30 +209,40 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
metricsH := helper.MustMakeMetrics(mgr)
|
||||
metricsH := helper.NewMetrics(mgr, metrics.MustMakeRecorder(), imagev1.ImageUpdateAutomationFinalizer)
|
||||
|
||||
if err = (&controllers.ImageUpdateAutomationReconciler{
|
||||
var tokenCache *cache.TokenCache
|
||||
if tokenCacheOptions.MaxSize > 0 {
|
||||
var err error
|
||||
tokenCache, err = cache.NewTokenCache(tokenCacheOptions.MaxSize,
|
||||
cache.WithMaxDuration(tokenCacheOptions.MaxDuration),
|
||||
cache.WithMetricsRegisterer(ctrlmetrics.Registry),
|
||||
cache.WithMetricsPrefix("gotk_token_"))
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to create token cache")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
ctx := ctrl.SetupSignalHandler()
|
||||
|
||||
if err := (&controller.ImageUpdateAutomationReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
EventRecorder: eventRecorder,
|
||||
Metrics: metricsH,
|
||||
NoCrossNamespaceRef: aclOptions.NoCrossNamespaceRefs,
|
||||
}).SetupWithManager(mgr, controllers.ImageUpdateAutomationReconcilerOptions{
|
||||
MaxConcurrentReconciles: concurrent,
|
||||
RateLimiter: helper.GetRateLimiter(rateLimiterOptions),
|
||||
RecoverPanic: recoverPanic,
|
||||
ControllerName: controllerName,
|
||||
}).SetupWithManager(ctx, mgr, controller.ImageUpdateAutomationReconcilerOptions{
|
||||
RateLimiter: helper.GetRateLimiter(rateLimiterOptions),
|
||||
TokenCache: tokenCache,
|
||||
}); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "ImageUpdateAutomation")
|
||||
os.Exit(1)
|
||||
}
|
||||
// +kubebuilder:scaffold:builder
|
||||
|
||||
if err = managed.InitManagedTransport(); err != nil {
|
||||
setupLog.Error(err, "unable to initialize libgit2 managed transport")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
setupLog.Info("starting manager")
|
||||
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
|
||||
if err := mgr.Start(ctx); err != nil {
|
||||
setupLog.Error(err, "problem running manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ func (r *ScreeningLocalReader) Read() ([]*yaml.RNode, error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
if ext := filepath.Ext(p); ext != ".yaml" && ext != ".yml" {
|
||||
if ext := filepath.Ext(p); ext != ".yaml" && ext != ".yml" && filepath.Base(p) != "Kustomization" {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -33,11 +33,12 @@ func TestScreeningLocalReader(t *testing.T) {
|
|||
}
|
||||
nodes, err := r.Read()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
// the test fixture has three files that contain the marker:
|
||||
// - otherns.yaml
|
||||
// the test fixture has four files that contain the marker:
|
||||
// - marked.yaml
|
||||
// - kustomization.yaml
|
||||
g.Expect(len(nodes)).To(Equal(3))
|
||||
// - otherns.yaml
|
||||
// - kustomization.yml
|
||||
// - Kustomization
|
||||
g.Expect(len(nodes)).To(Equal(4))
|
||||
filesSeen := map[string]struct{}{}
|
||||
for i := range nodes {
|
||||
path, _, err := kioutil.GetFileAnnotations(nodes[i])
|
||||
|
@ -45,9 +46,10 @@ func TestScreeningLocalReader(t *testing.T) {
|
|||
filesSeen[path] = struct{}{}
|
||||
}
|
||||
g.Expect(filesSeen).To(Equal(map[string]struct{}{
|
||||
"marked.yaml": {},
|
||||
"kustomization.yaml": {},
|
||||
"otherns.yaml": {},
|
||||
"marked.yaml": {},
|
||||
"otherns.yaml": {},
|
||||
"kustomization.yml": {},
|
||||
"Kustomization": {},
|
||||
}))
|
||||
|
||||
}
|
||||
|
|
|
@ -17,11 +17,12 @@ limitations under the License.
|
|||
package update
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
"sigs.k8s.io/kustomize/kyaml/fieldmeta"
|
||||
"sigs.k8s.io/kustomize/kyaml/openapi"
|
||||
"sigs.k8s.io/kustomize/kyaml/setters2"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
|
@ -112,8 +113,17 @@ func accept(v visitor, object *yaml.RNode, p string, settersSchema *spec.Schema)
|
|||
return nil
|
||||
}
|
||||
|
||||
type setter struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type extension struct {
|
||||
Setter *setter `json:"setter,omitempty"`
|
||||
}
|
||||
|
||||
// set applies the value from ext to field
|
||||
func (s *SetAllCallback) set(field *yaml.RNode, ext *setters2.CliExtension, sch *spec.Schema) (bool, error) {
|
||||
func (s *SetAllCallback) set(field *yaml.RNode, ext *extension, sch *spec.Schema) (bool, error) {
|
||||
// check full setter
|
||||
if ext.Setter == nil {
|
||||
return false, nil
|
||||
|
@ -139,7 +149,7 @@ func (s *SetAllCallback) visitScalar(object *yaml.RNode, p string, fieldSchema *
|
|||
return nil
|
||||
}
|
||||
// get the openAPI for this field describing how to apply the setter
|
||||
ext, err := setters2.GetExtFromSchema(fieldSchema.Schema)
|
||||
ext, err := getExtFromSchema(fieldSchema.Schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -152,3 +162,19 @@ func (s *SetAllCallback) visitScalar(object *yaml.RNode, p string, fieldSchema *
|
|||
_, err = s.set(object, ext, fieldSchema.Schema)
|
||||
return err
|
||||
}
|
||||
|
||||
func getExtFromSchema(schema *spec.Schema) (*extension, error) {
|
||||
cep := schema.VendorExtensible.Extensions[K8sCliExtensionKey]
|
||||
if cep == nil {
|
||||
return nil, nil
|
||||
}
|
||||
b, err := json.Marshal(cep)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
val := &extension{}
|
||||
if err := json.Unmarshal(b, val); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
package update
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func TestSetAllCallbackAccept(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
object *yaml.RNode
|
||||
settersSchema *spec.Schema
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
name: "Accept - Scalar Node",
|
||||
object: yaml.NewRNode(&yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Value: "test",
|
||||
}),
|
||||
settersSchema: &spec.Schema{},
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "Accept - Scalar Node - Error",
|
||||
object: yaml.NewRNode(&yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Value: "test",
|
||||
}),
|
||||
settersSchema: nil,
|
||||
expectedError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
callbackInstance := SetAllCallback{
|
||||
SettersSchema: test.settersSchema,
|
||||
Trace: logr.Discard(),
|
||||
}
|
||||
|
||||
err := accept(&callbackInstance, test.object, "", test.settersSchema)
|
||||
g := NewWithT(t)
|
||||
if test.expectedError {
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
} else {
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetExtFromSchema(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
schema *spec.Schema
|
||||
expectedExtension *extension
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
name: "Extension Present",
|
||||
schema: &spec.Schema{
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: map[string]interface{}{
|
||||
K8sCliExtensionKey: &extension{
|
||||
Setter: &setter{
|
||||
Name: "testSetter",
|
||||
Value: "testValue",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedExtension: &extension{
|
||||
Setter: &setter{
|
||||
Name: "testSetter",
|
||||
Value: "testValue",
|
||||
},
|
||||
},
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "Extension Not Present",
|
||||
schema: &spec.Schema{},
|
||||
expectedError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
ext, err := getExtFromSchema(test.schema)
|
||||
|
||||
if test.expectedError {
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
} else {
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(ext).To(Equal(test.expectedExtension))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -96,3 +96,66 @@ func (r Result) Objects() map[ObjectIdentifier][]ImageRef {
|
|||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ResultV2 contains Result of update and also the file changes made during the
|
||||
// update. This extends the Result to include details about the exact changes
|
||||
// made to the files and the objects in them. It has a nested structure
|
||||
// file->objects->changes.
|
||||
type ResultV2 struct {
|
||||
ImageResult Result
|
||||
FileChanges map[string]ObjectChanges
|
||||
}
|
||||
|
||||
// ObjectChanges contains all the changes made to objects.
|
||||
type ObjectChanges map[ObjectIdentifier][]Change
|
||||
|
||||
// Change contains the setter that resulted in a Change, the old and the new
|
||||
// value after the Change.
|
||||
type Change struct {
|
||||
OldValue string
|
||||
NewValue string
|
||||
Setter string
|
||||
}
|
||||
|
||||
// AddChange adds changes to Resultv2 for a given file, object and changes
|
||||
// associated with it.
|
||||
func (r *ResultV2) AddChange(file string, objectID ObjectIdentifier, changes ...Change) {
|
||||
if r.FileChanges == nil {
|
||||
r.FileChanges = map[string]ObjectChanges{}
|
||||
}
|
||||
// Create an entry for the file if not present.
|
||||
_, ok := r.FileChanges[file]
|
||||
if !ok {
|
||||
r.FileChanges[file] = ObjectChanges{}
|
||||
}
|
||||
// Append to the changes for the object.
|
||||
r.FileChanges[file][objectID] = append(r.FileChanges[file][objectID], changes...)
|
||||
}
|
||||
|
||||
// Changes returns all the changes that were made in at least one update.
|
||||
func (r ResultV2) Changes() []Change {
|
||||
seen := make(map[Change]struct{})
|
||||
var result []Change
|
||||
for _, objChanges := range r.FileChanges {
|
||||
for _, changes := range objChanges {
|
||||
for _, change := range changes {
|
||||
if _, ok := seen[change]; !ok {
|
||||
seen[change] = struct{}{}
|
||||
result = append(result, change)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Objects returns ObjectChanges, regardless of which file they appear in.
|
||||
func (r ResultV2) Objects() ObjectChanges {
|
||||
result := make(ObjectChanges)
|
||||
for _, objChanges := range r.FileChanges {
|
||||
for obj, change := range objChanges {
|
||||
result[obj] = change
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -92,3 +92,80 @@ func TestUpdateResults(t *testing.T) {
|
|||
},
|
||||
}))
|
||||
}
|
||||
|
||||
func TestResultV2(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
var result ResultV2
|
||||
objectNames := []ObjectIdentifier{
|
||||
{yaml.ResourceIdentifier{
|
||||
NameMeta: yaml.NameMeta{Namespace: "ns", Name: "foo"},
|
||||
}},
|
||||
{yaml.ResourceIdentifier{
|
||||
NameMeta: yaml.NameMeta{Namespace: "ns", Name: "bar"},
|
||||
}},
|
||||
}
|
||||
|
||||
result.AddChange("foo.yaml", objectNames[0], Change{
|
||||
OldValue: "aaa",
|
||||
NewValue: "bbb",
|
||||
Setter: "foo-ns:policy:name",
|
||||
})
|
||||
result.AddChange("bar.yaml", objectNames[1], Change{
|
||||
OldValue: "cccc:v1.0",
|
||||
NewValue: "cccc:v1.2",
|
||||
Setter: "foo-ns:policy",
|
||||
})
|
||||
|
||||
result = ResultV2{
|
||||
FileChanges: map[string]ObjectChanges{
|
||||
"foo.yaml": {
|
||||
objectNames[0]: []Change{
|
||||
{
|
||||
OldValue: "aaa",
|
||||
NewValue: "bbb",
|
||||
Setter: "foo-ns:policy:name",
|
||||
},
|
||||
},
|
||||
},
|
||||
"bar.yaml": {
|
||||
objectNames[1]: []Change{
|
||||
{
|
||||
OldValue: "cccc:v1.0",
|
||||
NewValue: "cccc:v1.2",
|
||||
Setter: "foo-ns:policy",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
g.Expect(result.Changes()).To(ContainElements([]Change{
|
||||
{
|
||||
OldValue: "aaa",
|
||||
NewValue: "bbb",
|
||||
Setter: "foo-ns:policy:name",
|
||||
},
|
||||
{
|
||||
OldValue: "cccc:v1.0",
|
||||
NewValue: "cccc:v1.2",
|
||||
Setter: "foo-ns:policy",
|
||||
},
|
||||
}))
|
||||
g.Expect(result.Objects()).To(Equal(ObjectChanges{
|
||||
objectNames[0]: []Change{
|
||||
{
|
||||
OldValue: "aaa",
|
||||
NewValue: "bbb",
|
||||
Setter: "foo-ns:policy:name",
|
||||
},
|
||||
},
|
||||
objectNames[1]: []Change{
|
||||
{
|
||||
OldValue: "cccc:v1.0",
|
||||
NewValue: "cccc:v1.2",
|
||||
Setter: "foo-ns:policy",
|
||||
},
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ package update
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
|
@ -29,21 +28,19 @@ import (
|
|||
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
|
||||
"sigs.k8s.io/kustomize/kyaml/openapi"
|
||||
"sigs.k8s.io/kustomize/kyaml/sets"
|
||||
"sigs.k8s.io/kustomize/kyaml/setters2"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta1"
|
||||
"github.com/fluxcd/image-automation-controller/internal/constants"
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
const (
|
||||
// SetterShortHand is a shorthand that can be used to mark
|
||||
// setters; instead of
|
||||
// # { "$ref": "#/definitions/
|
||||
SetterShortHand = "$imagepolicy"
|
||||
// This is preserved from setters2
|
||||
K8sCliExtensionKey = "x-k8s-cli"
|
||||
)
|
||||
|
||||
func init() {
|
||||
fieldmeta.SetShortHandRef(SetterShortHand)
|
||||
fieldmeta.SetShortHandRef(constants.SetterShortHand)
|
||||
// this prevents the global schema, should it be initialised, from
|
||||
// parsing all the Kubernetes openAPI definitions, which is not
|
||||
// necessary.
|
||||
|
@ -54,6 +51,15 @@ func init() {
|
|||
// that contain an "in scope" image policy marker, and writes files it
|
||||
// updated (and only those files) back to `outpath`.
|
||||
func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []imagev1_reflect.ImagePolicy) (Result, error) {
|
||||
result, err := UpdateV2WithSetters(tracelog, inpath, outpath, policies)
|
||||
return result.ImageResult, err
|
||||
}
|
||||
|
||||
// UpdateV2WithSetters takes all YAML files from `inpath`, updates any
|
||||
// that contain an "in scope" image policy marker, and writes files it
|
||||
// updated (and only those files) back to `outpath`. It also returns the result
|
||||
// of the changes it made as ResultV2.
|
||||
func UpdateV2WithSetters(tracelog logr.Logger, inpath, outpath string, policies []imagev1_reflect.ImagePolicy) (ResultV2, error) {
|
||||
// the OpenAPI schema is a package variable in kyaml/openapi. In
|
||||
// lieu of being able to isolate invocations (per
|
||||
// https://github.com/kubernetes-sigs/kustomize/issues/3058), I
|
||||
|
@ -97,13 +103,15 @@ func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []
|
|||
Files: make(map[string]FileResult),
|
||||
}
|
||||
|
||||
var resultV2 ResultV2
|
||||
|
||||
// Compilng the result needs the file, the image ref used, and the
|
||||
// object. Each setter will supply its own name to its callback,
|
||||
// which can be used to look up the image ref; the file and object
|
||||
// we will get from `setAll` which keeps track of those as it
|
||||
// iterates.
|
||||
imageRefs := make(map[string]imageRef)
|
||||
setAllCallback := func(file, setterName string, node *yaml.RNode) {
|
||||
setAllCallback := func(file, setterName string, node *yaml.RNode, old, new string) {
|
||||
ref, ok := imageRefs[setterName]
|
||||
if !ok {
|
||||
return
|
||||
|
@ -115,6 +123,15 @@ func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []
|
|||
}
|
||||
oid := ObjectIdentifier{meta.GetIdentifier()}
|
||||
|
||||
// Record the change.
|
||||
ch := Change{
|
||||
OldValue: old,
|
||||
NewValue: new,
|
||||
Setter: setterName,
|
||||
}
|
||||
// Append the change for the file and identifier.
|
||||
resultV2.AddChange(file, oid, ch)
|
||||
|
||||
fileres, ok := result.Files[file]
|
||||
if !ok {
|
||||
fileres = FileResult{
|
||||
|
@ -122,7 +139,7 @@ func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []
|
|||
}
|
||||
result.Files[file] = fileres
|
||||
}
|
||||
objres, ok := fileres.Objects[oid]
|
||||
objres := fileres.Objects[oid]
|
||||
for _, n := range objres {
|
||||
if n == ref {
|
||||
return
|
||||
|
@ -134,7 +151,7 @@ func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []
|
|||
|
||||
defs := map[string]spec.Schema{}
|
||||
for _, policy := range policies {
|
||||
if policy.Status.LatestImage == "" {
|
||||
if policy.Status.LatestRef == nil {
|
||||
continue
|
||||
}
|
||||
// Using strict validation would mean any image that omits the
|
||||
|
@ -143,10 +160,10 @@ func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []
|
|||
// filled in. Usually this would mean the tag would end up
|
||||
// being `latest` if empty in the input; but I'm assuming here
|
||||
// that the policy won't have a tagless ref.
|
||||
image := policy.Status.LatestImage
|
||||
image := policy.Status.LatestRef.String()
|
||||
r, err := name.ParseReference(image, name.WeakValidation)
|
||||
if err != nil {
|
||||
return Result{}, fmt.Errorf("encountered invalid image ref %q: %w", policy.Status.LatestImage, err)
|
||||
return ResultV2{}, fmt.Errorf("encountered invalid image ref %q: %w", image, err)
|
||||
}
|
||||
ref := imageRef{
|
||||
Reference: r,
|
||||
|
@ -156,15 +173,13 @@ func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []
|
|||
},
|
||||
}
|
||||
|
||||
tag := ref.Identifier()
|
||||
// annoyingly, neither the library imported above, nor an
|
||||
// alternative I found, will yield the original image name;
|
||||
// this is an easy way to get it
|
||||
name := strings.TrimSuffix(image, ":"+tag)
|
||||
tag := policy.Status.LatestRef.Tag
|
||||
name := policy.Status.LatestRef.Name
|
||||
digest := policy.Status.LatestRef.Digest
|
||||
|
||||
imageSetter := fmt.Sprintf("%s:%s", policy.GetNamespace(), policy.GetName())
|
||||
tracelog.Info("adding setter", "name", imageSetter)
|
||||
defs[fieldmeta.SetterDefinitionPrefix+imageSetter] = setterSchema(imageSetter, policy.Status.LatestImage)
|
||||
defs[fieldmeta.SetterDefinitionPrefix+imageSetter] = setterSchema(imageSetter, image)
|
||||
imageRefs[imageSetter] = ref
|
||||
|
||||
tagSetter := imageSetter + ":tag"
|
||||
|
@ -172,11 +187,15 @@ func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []
|
|||
defs[fieldmeta.SetterDefinitionPrefix+tagSetter] = setterSchema(tagSetter, tag)
|
||||
imageRefs[tagSetter] = ref
|
||||
|
||||
// Context().Name() gives the image repository _as supplied_
|
||||
nameSetter := imageSetter + ":name"
|
||||
tracelog.Info("adding setter", "name", nameSetter)
|
||||
defs[fieldmeta.SetterDefinitionPrefix+nameSetter] = setterSchema(nameSetter, name)
|
||||
imageRefs[nameSetter] = ref
|
||||
|
||||
digestSetter := imageSetter + ":digest"
|
||||
tracelog.Info("adding setter", "name", digestSetter)
|
||||
defs[fieldmeta.SetterDefinitionPrefix+digestSetter] = setterSchema(digestSetter, digest)
|
||||
imageRefs[digestSetter] = ref
|
||||
}
|
||||
|
||||
settersSchema.Definitions = defs
|
||||
|
@ -184,7 +203,7 @@ func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []
|
|||
// get ready with the reader and writer
|
||||
reader := &ScreeningLocalReader{
|
||||
Path: inpath,
|
||||
Token: fmt.Sprintf("%q", SetterShortHand),
|
||||
Token: fmt.Sprintf("%q", constants.SetterShortHand),
|
||||
Trace: tracelog,
|
||||
}
|
||||
writer := &kio.LocalPackageWriter{
|
||||
|
@ -202,9 +221,12 @@ func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []
|
|||
// go!
|
||||
err := pipeline.Execute()
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
return ResultV2{}, err
|
||||
}
|
||||
return result, nil
|
||||
|
||||
// Combine the results.
|
||||
resultV2.ImageResult = result
|
||||
return resultV2, nil
|
||||
}
|
||||
|
||||
// setAll returns a kio.Filter using the supplied SetAllCallback
|
||||
|
@ -213,7 +235,7 @@ func UpdateWithSetters(tracelog logr.Logger, inpath, outpath string, policies []
|
|||
// files with changed nodes. This is based on
|
||||
// [`SetAll`](https://github.com/kubernetes-sigs/kustomize/blob/kyaml/v0.10.16/kyaml/setters2/set.go#L503
|
||||
// from kyaml/kio.
|
||||
func setAll(schema *spec.Schema, tracelog logr.Logger, callback func(file, setterName string, node *yaml.RNode)) kio.Filter {
|
||||
func setAll(schema *spec.Schema, tracelog logr.Logger, callback func(file, setterName string, node *yaml.RNode, old, new string)) kio.Filter {
|
||||
filter := &SetAllCallback{
|
||||
SettersSchema: schema,
|
||||
Trace: tracelog,
|
||||
|
@ -229,7 +251,7 @@ func setAll(schema *spec.Schema, tracelog logr.Logger, callback func(file, sette
|
|||
|
||||
filter.Callback = func(setter, oldValue, newValue string) {
|
||||
if newValue != oldValue {
|
||||
callback(path, setter, nodes[i])
|
||||
callback(path, setter, nodes[i], oldValue, newValue)
|
||||
filesToUpdate.Insert(path)
|
||||
}
|
||||
}
|
||||
|
@ -256,7 +278,7 @@ func setAll(schema *spec.Schema, tracelog logr.Logger, callback func(file, sette
|
|||
func setterSchema(name, value string) spec.Schema {
|
||||
schema := spec.StringProperty()
|
||||
schema.Extensions = map[string]interface{}{}
|
||||
schema.Extensions.Add(setters2.K8sCliExtensionKey, map[string]interface{}{
|
||||
schema.Extensions.Add(K8sCliExtensionKey, map[string]interface{}{
|
||||
"setter": map[string]string{
|
||||
"name": name,
|
||||
"value": value,
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# This is not intended to be a working kustomization
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- irrelevant.yaml
|
||||
images:
|
||||
- name: container
|
||||
newName: index.repo.fake/updated # {"$imagepolicy": "automation-ns:policy:name"}
|
||||
newTag: v1.0.1 # {"$imagepolicy": "automation-ns:policy:tag"}
|
|
@ -1,9 +0,0 @@
|
|||
# This is not intended to be a working kustomization
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- unimportant.yaml
|
||||
images:
|
||||
- name: container
|
||||
newName: index.repo.fake/updated # {"$imagepolicy": "automation-ns:policy:name"}
|
||||
newTag: v1.0.1 # {"$imagepolicy": "automation-ns:policy:tag"}
|
|
@ -0,0 +1,28 @@
|
|||
# This is not intended to be a working kustomization
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- unimportant.yaml
|
||||
images:
|
||||
- name: container
|
||||
newName: index.repo.fake/updated # {"$imagepolicy": "automation-ns:policy:name"}
|
||||
newTag: v1.0.1 # {"$imagepolicy": "automation-ns:policy:tag"}
|
||||
newDigest: sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be # {"$imagepolicy": "automation-ns:policy-with-digest:digest"}
|
||||
# Prove fix for https://github.com/fluxcd/flux2/issues/3284
|
||||
patches:
|
||||
- patch: |
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/volumeMounts
|
||||
value:
|
||||
- mountPath: /usr/share/wildfly/wildfly/standalone/configuration/appConfigMaps/WF
|
||||
name: wf-config
|
||||
- mountPath: /usr/share/wildfly/wildfly/standalone/configuration/appConfigMaps/xxx_config
|
||||
name: xxx-config
|
||||
- mountPath: /usr/share/wildfly/wildfly/standalone/log
|
||||
name: wildfly-standalone-log
|
||||
target:
|
||||
group: apps
|
||||
version: v1
|
||||
kind: Deployment
|
||||
name: sxxxxdadminservice
|
||||
image: image:v1.0.0@sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be # {"$imagepolicy": "automation-ns:policy-with-digest"}
|
|
@ -0,0 +1,9 @@
|
|||
# This is not intended to be a working kustomization
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- irrelevant.yaml
|
||||
images:
|
||||
- name: container
|
||||
newName: replaced # {"$imagepolicy": "automation-ns:policy:name"}
|
||||
newTag: v1 # {"$imagepolicy": "automation-ns:policy:tag"}
|
|
@ -1,9 +0,0 @@
|
|||
# This is not intended to be a working kustomization
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- unimportant.yaml
|
||||
images:
|
||||
- name: container
|
||||
newName: replaced # {"$imagepolicy": "automation-ns:policy:name"}
|
||||
newTag: v1 # {"$imagepolicy": "automation-ns:policy:tag"}
|
|
@ -0,0 +1,28 @@
|
|||
# This is not intended to be a working kustomization
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- unimportant.yaml
|
||||
images:
|
||||
- name: container
|
||||
newName: replaced # {"$imagepolicy": "automation-ns:policy:name"}
|
||||
newTag: v1 # {"$imagepolicy": "automation-ns:policy:tag"}
|
||||
newDigest: sha256:1234567890abcdef # {"$imagepolicy": "automation-ns:policy-with-digest:digest"}
|
||||
# Prove fix for https://github.com/fluxcd/flux2/issues/3284
|
||||
patches:
|
||||
- patch: |
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/volumeMounts
|
||||
value:
|
||||
- mountPath: /usr/share/wildfly/wildfly/standalone/configuration/appConfigMaps/WF
|
||||
name: wf-config
|
||||
- mountPath: /usr/share/wildfly/wildfly/standalone/configuration/appConfigMaps/xxx_config
|
||||
name: xxx-config
|
||||
- mountPath: /usr/share/wildfly/wildfly/standalone/log
|
||||
name: wildfly-standalone-log
|
||||
target:
|
||||
group: apps
|
||||
version: v1
|
||||
kind: Deployment
|
||||
name: sxxxxdadminservice
|
||||
image: image # {"$imagepolicy": "automation-ns:policy-with-digest"}
|
|
@ -17,8 +17,6 @@ limitations under the License.
|
|||
package update
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
|
@ -28,8 +26,9 @@ import (
|
|||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
|
||||
"github.com/fluxcd/image-automation-controller/internal/testutil"
|
||||
"github.com/fluxcd/image-automation-controller/pkg/test"
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta1"
|
||||
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
)
|
||||
|
||||
func TestUpdateWithSetters(t *testing.T) {
|
||||
|
@ -37,29 +36,35 @@ func TestUpdateWithSetters(t *testing.T) {
|
|||
|
||||
policies := []imagev1_reflect.ImagePolicy{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{ // name matches marker used in testdata/setters/{original,expected}
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "automation-ns",
|
||||
Name: "policy",
|
||||
},
|
||||
Status: imagev1_reflect.ImagePolicyStatus{
|
||||
LatestImage: "index.repo.fake/updated:v1.0.1",
|
||||
LatestRef: testutil.ImageToRef("index.repo.fake/updated:v1.0.1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{ // name matches marker used in testdata/setters/{original,expected}
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "automation-ns",
|
||||
Name: "unchanged",
|
||||
},
|
||||
Status: imagev1_reflect.ImagePolicyStatus{
|
||||
LatestImage: "image:v1.0.0",
|
||||
LatestRef: testutil.ImageToRef("image:v1.0.0"),
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "automation-ns",
|
||||
Name: "policy-with-digest",
|
||||
},
|
||||
Status: imagev1_reflect.ImagePolicyStatus{
|
||||
LatestRef: testutil.ImageToRef("image:v1.0.0@sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tmp, err := ioutil.TempDir("", "gotest")
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
tmp := t.TempDir()
|
||||
result, err := UpdateWithSetters(logr.Discard(), "testdata/setters/original", tmp, policies)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
test.ExpectMatchingDirectories(g, tmp, "testdata/setters/expected")
|
||||
|
@ -80,15 +85,30 @@ func TestUpdateWithSetters(t *testing.T) {
|
|||
Name: "foo",
|
||||
},
|
||||
}}
|
||||
|
||||
r, _ := name.ParseReference("index.repo.fake/updated:v1.0.1")
|
||||
expectedImageRef := imageRef{r, types.NamespacedName{
|
||||
Name: "policy",
|
||||
Namespace: "automation-ns",
|
||||
}}
|
||||
|
||||
r, _ = name.ParseReference("image:v1.0.0@sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be")
|
||||
expectedImageRefDigest := imageRef{r, types.NamespacedName{
|
||||
Name: "policy-with-digest",
|
||||
Namespace: "automation-ns",
|
||||
}}
|
||||
|
||||
expectedResult := Result{
|
||||
Files: map[string]FileResult{
|
||||
"kustomization.yaml": {
|
||||
"kustomization.yml": {
|
||||
Objects: map[ObjectIdentifier][]ImageRef{
|
||||
kustomizeResourceID: {
|
||||
expectedImageRef,
|
||||
expectedImageRefDigest,
|
||||
},
|
||||
},
|
||||
},
|
||||
"Kustomization": {
|
||||
Objects: map[ObjectIdentifier][]ImageRef{
|
||||
kustomizeResourceID: {
|
||||
expectedImageRef,
|
||||
|
@ -106,4 +126,65 @@ func TestUpdateWithSetters(t *testing.T) {
|
|||
}
|
||||
|
||||
g.Expect(result).To(Equal(expectedResult))
|
||||
|
||||
// Test ResultV2.
|
||||
tmp2 := t.TempDir()
|
||||
resultV2, err := UpdateV2WithSetters(logr.Discard(), "testdata/setters/original", tmp2, policies)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
test.ExpectMatchingDirectories(g, tmp2, "testdata/setters/expected")
|
||||
|
||||
expectedResultV2 := ResultV2{
|
||||
ImageResult: expectedResult,
|
||||
FileChanges: map[string]ObjectChanges{
|
||||
"kustomization.yml": {
|
||||
kustomizeResourceID: []Change{
|
||||
{
|
||||
OldValue: "replaced",
|
||||
NewValue: "index.repo.fake/updated",
|
||||
Setter: "automation-ns:policy:name",
|
||||
},
|
||||
{
|
||||
OldValue: "v1",
|
||||
NewValue: "v1.0.1",
|
||||
Setter: "automation-ns:policy:tag",
|
||||
},
|
||||
{
|
||||
OldValue: "sha256:1234567890abcdef",
|
||||
NewValue: "sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be",
|
||||
Setter: "automation-ns:policy-with-digest:digest",
|
||||
},
|
||||
{
|
||||
OldValue: "image",
|
||||
NewValue: "image:v1.0.0@sha256:6745aaad46d795c9836632e1fb62f24b7e7f4c843144da8e47a5465c411a14be",
|
||||
Setter: "automation-ns:policy-with-digest",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Kustomization": {
|
||||
kustomizeResourceID: []Change{
|
||||
{
|
||||
OldValue: "replaced",
|
||||
NewValue: "index.repo.fake/updated",
|
||||
Setter: "automation-ns:policy:name",
|
||||
},
|
||||
{
|
||||
OldValue: "v1",
|
||||
NewValue: "v1.0.1",
|
||||
Setter: "automation-ns:policy:tag",
|
||||
},
|
||||
},
|
||||
},
|
||||
"marked.yaml": {
|
||||
markedResourceID: []Change{
|
||||
{
|
||||
OldValue: "image:v1.0.0",
|
||||
NewValue: "index.repo.fake/updated:v1.0.1",
|
||||
Setter: "automation-ns:policy",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
g.Expect(resultV2).To(Equal(expectedResultV2))
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue