mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-02-20 17:48:47 +01:00
Compare commits
585 Commits
appsetdocs
...
v3.1.12
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a46152e850 | ||
|
|
28b618775d | ||
|
|
1b7cfc0c06 | ||
|
|
0262c8ff97 | ||
|
|
cc053b2eeb | ||
|
|
f9adb4e7f4 | ||
|
|
6c3f3eab5d | ||
|
|
f22ccc2192 | ||
|
|
b424210b52 | ||
|
|
955ea1b1df | ||
|
|
836d47f311 | ||
|
|
db2004f2e2 | ||
|
|
b68c964321 | ||
|
|
f4f21b3642 | ||
|
|
38c15ada45 | ||
|
|
11e7758ca9 | ||
|
|
f2f4f4579b | ||
|
|
f9ada0403d | ||
|
|
b6660a2a7a | ||
|
|
787f3ec6a2 | ||
|
|
49ee0040c4 | ||
|
|
7eca62c2d6 | ||
|
|
8665140f96 | ||
|
|
a419e477e6 | ||
|
|
e53196f9fd | ||
|
|
16ba5f9c43 | ||
|
|
1904de5065 | ||
|
|
becb020064 | ||
|
|
c63c2d8909 | ||
|
|
e20828f869 | ||
|
|
761fc27068 | ||
|
|
1a023f1ca7 | ||
|
|
5c466a4e39 | ||
|
|
b2fa7dcde6 | ||
|
|
38808d03cd | ||
|
|
41eac62eac | ||
|
|
54bab39a80 | ||
|
|
511ebd799e | ||
|
|
2e4458b91a | ||
|
|
7f92418a9c | ||
|
|
f3d59b0bb7 | ||
|
|
4081e2983a | ||
|
|
c26cd5502b | ||
|
|
96797ba846 | ||
|
|
b46a57ab82 | ||
|
|
2b3df7f5a8 | ||
|
|
4ef56634b4 | ||
|
|
cb9574597e | ||
|
|
468870f65d | ||
|
|
cfeed49105 | ||
|
|
c21141a51f | ||
|
|
0415c60af9 | ||
|
|
9a3235ef92 | ||
|
|
3320f1ed7a | ||
|
|
20dd73af34 | ||
|
|
206d57b0de | ||
|
|
c1467b81bc | ||
|
|
791b036d98 | ||
|
|
60c62a944b | ||
|
|
fe6efec8f4 | ||
|
|
6de4f7739b | ||
|
|
ed9149beea | ||
|
|
20447f7f57 | ||
|
|
7982a74600 | ||
|
|
b3ad040b2c | ||
|
|
30d8ce66e2 | ||
|
|
fa342d153e | ||
|
|
c140eb27f8 | ||
|
|
70dde2c27b | ||
|
|
eb72a0bd3b | ||
|
|
fdd099181c | ||
|
|
a0f065316b | ||
|
|
b22566d001 | ||
|
|
03c4ad854c | ||
|
|
a9a0c7bf57 | ||
|
|
1c1c17663e | ||
|
|
61d2a05fd0 | ||
|
|
64e94af771 | ||
|
|
269e213889 | ||
|
|
26c63b9283 | ||
|
|
1ad527f094 | ||
|
|
b7d39b57fc | ||
|
|
9f18ff1fcd | ||
|
|
66d06c0b23 | ||
|
|
007a5aea6e | ||
|
|
36cc2d1b86 | ||
|
|
9db5e25e03 | ||
|
|
8eaccb7ea0 | ||
|
|
fed347da29 | ||
|
|
1cbd28cb30 | ||
|
|
6fe5ec794a | ||
|
|
6142c5bf56 | ||
|
|
563d45b8db | ||
|
|
37f2793e95 | ||
|
|
7224a15502 | ||
|
|
dd675feb33 | ||
|
|
c215dbf202 | ||
|
|
75f7016d89 | ||
|
|
4a7e581080 | ||
|
|
320f46f06b | ||
|
|
d69639a073 | ||
|
|
b75f532a91 | ||
|
|
200dc1d499 | ||
|
|
3f1d9bf5a2 | ||
|
|
20f9081fb4 | ||
|
|
1d09c8c8a1 | ||
|
|
bee23628a8 | ||
|
|
f03ffb3592 | ||
|
|
d4ebcc0c15 | ||
|
|
a671cc9b23 | ||
|
|
49514c9b4c | ||
|
|
f7590fa302 | ||
|
|
8ede222650 | ||
|
|
e25123b63d | ||
|
|
d6efb9e538 | ||
|
|
9849d2743b | ||
|
|
8b2560497a | ||
|
|
2fc05c58e7 | ||
|
|
590d0cda64 | ||
|
|
fa25f278c0 | ||
|
|
127293dcc7 | ||
|
|
b39e3a8d1e | ||
|
|
531b79ad8d | ||
|
|
fb0a89864c | ||
|
|
0893d7349d | ||
|
|
a3ef471913 | ||
|
|
25fc5d2b0c | ||
|
|
c11b36d455 | ||
|
|
3ff30dfac1 | ||
|
|
1b6c8237d3 | ||
|
|
964f26901c | ||
|
|
98ca41125c | ||
|
|
2c53861edb | ||
|
|
54501c05a8 | ||
|
|
0b8d018fdf | ||
|
|
91e5f954a3 | ||
|
|
30c325d952 | ||
|
|
ede2b32aea | ||
|
|
061d5992da | ||
|
|
1203ad0660 | ||
|
|
4731c61d7a | ||
|
|
7cc360e5af | ||
|
|
df5ff1f746 | ||
|
|
6b24fcb32c | ||
|
|
114693dcc2 | ||
|
|
10fa1aa8c8 | ||
|
|
28e871e7e2 | ||
|
|
a91a6594a1 | ||
|
|
52ad029110 | ||
|
|
fd48c47cf9 | ||
|
|
b6e90ea92c | ||
|
|
c6ebc19c1f | ||
|
|
c2e93a3e95 | ||
|
|
79c75876a8 | ||
|
|
2801a11efc | ||
|
|
64198e8042 | ||
|
|
dc1d148a5d | ||
|
|
7a064000a0 | ||
|
|
96fd06165e | ||
|
|
8c6f35bf5c | ||
|
|
2ae9f43410 | ||
|
|
d14ba140d1 | ||
|
|
81ae77a4ad | ||
|
|
d33b9b98ba | ||
|
|
1df47e9aa4 | ||
|
|
44fce0e478 | ||
|
|
8f1f5c7234 | ||
|
|
6174b94d6a | ||
|
|
00ee32f7f5 | ||
|
|
f4edcf7717 | ||
|
|
6d25734852 | ||
|
|
6c299bc783 | ||
|
|
a2152a1216 | ||
|
|
d4bdabd64d | ||
|
|
6e972e436a | ||
|
|
7814790ff3 | ||
|
|
ee92cf56f2 | ||
|
|
8b567c99b4 | ||
|
|
5091f9a3f7 | ||
|
|
c7a0fa3a8c | ||
|
|
2bf3f6850e | ||
|
|
046b9e7d97 | ||
|
|
dbd8009f18 | ||
|
|
0a8a7e246f | ||
|
|
7496ede7c2 | ||
|
|
7ee59d02b6 | ||
|
|
463155b8b4 | ||
|
|
5dba0648f3 | ||
|
|
949c9eabd3 | ||
|
|
8fde8d0a4b | ||
|
|
2093179488 | ||
|
|
61b3ea1187 | ||
|
|
80a194af85 | ||
|
|
2e1e3162e5 | ||
|
|
0fa3c3d67d | ||
|
|
68c8b99270 | ||
|
|
01a882d429 | ||
|
|
edf36831ab | ||
|
|
7915002382 | ||
|
|
43ce82801d | ||
|
|
36f91a0231 | ||
|
|
47326bda6d | ||
|
|
8010b4f733 | ||
|
|
18c4d9d568 | ||
|
|
109cd6c382 | ||
|
|
e1195fd931 | ||
|
|
41a4cd3032 | ||
|
|
ee45d0fc1b | ||
|
|
d9b102bea0 | ||
|
|
fc3c929cea | ||
|
|
9472273a1b | ||
|
|
b3e72ac3e6 | ||
|
|
9050798094 | ||
|
|
23ab5fc23c | ||
|
|
30a0088c84 | ||
|
|
f532299f7c | ||
|
|
1ecc561d9e | ||
|
|
7246c0246f | ||
|
|
7bb7385e46 | ||
|
|
eb794b7db9 | ||
|
|
e691ee35b4 | ||
|
|
e89e05486d | ||
|
|
9df2fd9a67 | ||
|
|
8679549c38 | ||
|
|
ae6e35976d | ||
|
|
0e9519314b | ||
|
|
4b1bd1852f | ||
|
|
4b866292e5 | ||
|
|
5290de32b8 | ||
|
|
2c3a452fd7 | ||
|
|
ea97dec642 | ||
|
|
c0c6abedc4 | ||
|
|
b39ca155dc | ||
|
|
81ac6216a6 | ||
|
|
76f80b375c | ||
|
|
fb40acdd79 | ||
|
|
4a15925edc | ||
|
|
234c73a688 | ||
|
|
2e3f9eb1c0 | ||
|
|
5509dd8646 | ||
|
|
b9cf44b584 | ||
|
|
7fda067767 | ||
|
|
f43523a69e | ||
|
|
54a45c4f89 | ||
|
|
33959d0605 | ||
|
|
7d6604404f | ||
|
|
1be1d1c0ae | ||
|
|
ddb1021440 | ||
|
|
b8051c60a9 | ||
|
|
3bbbac4bb9 | ||
|
|
7735b743b3 | ||
|
|
ee3752df70 | ||
|
|
8ad0e22bf1 | ||
|
|
a5b4041a79 | ||
|
|
18ce5c271f | ||
|
|
10abed751e | ||
|
|
81f3843f04 | ||
|
|
65464d8b77 | ||
|
|
ad6f0afa57 | ||
|
|
6ef971330a | ||
|
|
3de278ef85 | ||
|
|
1ff739dccb | ||
|
|
2f0bde61b5 | ||
|
|
a5012404fb | ||
|
|
a2ff605212 | ||
|
|
1d44046141 | ||
|
|
d076bfa074 | ||
|
|
45f12f2676 | ||
|
|
8c23cbdf13 | ||
|
|
e5ee3e42d2 | ||
|
|
e9863708b6 | ||
|
|
c63885298c | ||
|
|
0205eafa8e | ||
|
|
b6d9d6dd33 | ||
|
|
df9ea61424 | ||
|
|
f138b35889 | ||
|
|
6dc031987f | ||
|
|
81c17cbcdd | ||
|
|
2ed3ebe807 | ||
|
|
47213a4392 | ||
|
|
cefb62292d | ||
|
|
0484f9f8a6 | ||
|
|
d9e93f43e8 | ||
|
|
fe52b4d6d9 | ||
|
|
22c11cee10 | ||
|
|
4a41a86e43 | ||
|
|
dbdc6eafdb | ||
|
|
5831c948db | ||
|
|
d2d344ba01 | ||
|
|
a823e12595 | ||
|
|
878d6f715e | ||
|
|
d69f37bb35 | ||
|
|
e6065b88e5 | ||
|
|
57b039f12c | ||
|
|
5b21f98d7c | ||
|
|
198aa8a37a | ||
|
|
9183506de8 | ||
|
|
9149021b2c | ||
|
|
722da4e70f | ||
|
|
4febded784 | ||
|
|
f9971fa370 | ||
|
|
e131d273e1 | ||
|
|
e22cd46a86 | ||
|
|
baa679d2dc | ||
|
|
96039be4e0 | ||
|
|
4905876108 | ||
|
|
17e03ff335 | ||
|
|
ce4b7a28cc | ||
|
|
18a26d624b | ||
|
|
d3223a8c9f | ||
|
|
fff55f23f1 | ||
|
|
e8f568a70d | ||
|
|
4210535a17 | ||
|
|
146a7798ed | ||
|
|
7d58ca3ef7 | ||
|
|
f4e12ad2d6 | ||
|
|
4f35f7f09a | ||
|
|
be6ba0e446 | ||
|
|
45a8c68f2a | ||
|
|
e1db296752 | ||
|
|
30ede8ac81 | ||
|
|
aefa93ff4e | ||
|
|
3e7625a255 | ||
|
|
95efc16cd8 | ||
|
|
ff7870d27c | ||
|
|
25dc9605eb | ||
|
|
a0a82a1407 | ||
|
|
309acd161c | ||
|
|
f550dc0592 | ||
|
|
90408cbafd | ||
|
|
899e9964e0 | ||
|
|
cbb76a587a | ||
|
|
17d356fe38 | ||
|
|
ec9b43f9fa | ||
|
|
f763b2a97c | ||
|
|
0707bff1c2 | ||
|
|
797f1d693b | ||
|
|
6c1cfedd14 | ||
|
|
6aa5f84631 | ||
|
|
bf488ccc4d | ||
|
|
f0d783b1e7 | ||
|
|
af3a37b053 | ||
|
|
41e7b26d03 | ||
|
|
2e4d8e58d3 | ||
|
|
0e800341bb | ||
|
|
8398380d46 | ||
|
|
32eaea7533 | ||
|
|
570b3424b0 | ||
|
|
2078f40a6f | ||
|
|
7b09aef22a | ||
|
|
b63bf8f1d4 | ||
|
|
be3efbd98f | ||
|
|
dd5a8780e1 | ||
|
|
9675487a16 | ||
|
|
db6ece9b9f | ||
|
|
02185d7056 | ||
|
|
97a38b486b | ||
|
|
76a63a1cac | ||
|
|
44294a5102 | ||
|
|
e0b278fd5d | ||
|
|
0c20652c9a | ||
|
|
299286dea1 | ||
|
|
31e8ff8759 | ||
|
|
610523b784 | ||
|
|
e28f6090d1 | ||
|
|
5a3a104793 | ||
|
|
59b9ffa094 | ||
|
|
09f874da24 | ||
|
|
f4e2c6ea3b | ||
|
|
30742f6d5d | ||
|
|
274e918fc8 | ||
|
|
2dafcba595 | ||
|
|
d284b15400 | ||
|
|
3d73f37738 | ||
|
|
bd30eea239 | ||
|
|
56c556e707 | ||
|
|
0455e840b7 | ||
|
|
e9483a6a0d | ||
|
|
992ee25bd3 | ||
|
|
d0afa89a82 | ||
|
|
bdcc705956 | ||
|
|
ff1b935905 | ||
|
|
ef07bbdea1 | ||
|
|
111da3966e | ||
|
|
6625d07859 | ||
|
|
3f3ac06fd1 | ||
|
|
bb37b10517 | ||
|
|
ceec669b0b | ||
|
|
d4a20e4136 | ||
|
|
c7fa14e3b8 | ||
|
|
6047e91d15 | ||
|
|
e578b85410 | ||
|
|
92589dea2f | ||
|
|
38e7a03c51 | ||
|
|
0b7d7df6ac | ||
|
|
a12f517ebd | ||
|
|
f31807d5da | ||
|
|
acd8844064 | ||
|
|
73f68af2a6 | ||
|
|
70fc269834 | ||
|
|
73c976003a | ||
|
|
362dc663cd | ||
|
|
410a0a54cf | ||
|
|
c37be2122b | ||
|
|
969b043503 | ||
|
|
0dfdb0a86a | ||
|
|
6cf29619ae | ||
|
|
9a738b2880 | ||
|
|
f6861aa353 | ||
|
|
11736abfb9 | ||
|
|
8950d0a46c | ||
|
|
5cd1ef9e67 | ||
|
|
53a2005b43 | ||
|
|
99faf82474 | ||
|
|
7d81d1a59e | ||
|
|
9d93eb2054 | ||
|
|
08d52f3617 | ||
|
|
d0dbafcf0d | ||
|
|
274bdcca8b | ||
|
|
8d8ca44487 | ||
|
|
ba9ad75ac7 | ||
|
|
aa7f5726e8 | ||
|
|
a7dce39444 | ||
|
|
db9c82a886 | ||
|
|
a00446565c | ||
|
|
1cd3ac511c | ||
|
|
a5626a3432 | ||
|
|
a9dff073b2 | ||
|
|
814a24e229 | ||
|
|
b2ad0122d5 | ||
|
|
89f006a351 | ||
|
|
7079a4cc7a | ||
|
|
5500a375f7 | ||
|
|
19669d2daf | ||
|
|
895dcf7fa7 | ||
|
|
ca431dc5e4 | ||
|
|
eaf83ba29d | ||
|
|
fdb035017f | ||
|
|
0ad3a2c986 | ||
|
|
f26b7cd1ac | ||
|
|
0be041a834 | ||
|
|
3a529f338d | ||
|
|
4aea93b867 | ||
|
|
0864318ac2 | ||
|
|
27ae11ac58 | ||
|
|
c8c74b5d83 | ||
|
|
d22e28a28e | ||
|
|
713f9473af | ||
|
|
04809649f8 | ||
|
|
8c3c778bee | ||
|
|
2bd2488989 | ||
|
|
86fa92d954 | ||
|
|
569f5d4ce5 | ||
|
|
4b6566a8ef | ||
|
|
bde7bcfb29 | ||
|
|
5002f8a24e | ||
|
|
23f6822cab | ||
|
|
b85fec60a5 | ||
|
|
8fd61195f0 | ||
|
|
89d1ff95cf | ||
|
|
2d2efdc48f | ||
|
|
a5b57d43a2 | ||
|
|
e97114c3cd | ||
|
|
6ce6976cf1 | ||
|
|
8c2fc74306 | ||
|
|
8e846a56f9 | ||
|
|
88ecbe2e27 | ||
|
|
a2233562df | ||
|
|
e72ebb0098 | ||
|
|
f6e15d2984 | ||
|
|
600ab18087 | ||
|
|
5e1d2b9bb7 | ||
|
|
faf0073ebc | ||
|
|
127eef95fd | ||
|
|
01e8bd1c2f | ||
|
|
1eeaab4564 | ||
|
|
923edeafa0 | ||
|
|
8cc091c3d2 | ||
|
|
580ae1db1f | ||
|
|
4d0d80731e | ||
|
|
cde5b4b7d4 | ||
|
|
8401dcead0 | ||
|
|
f54238bae4 | ||
|
|
a1fd1ba178 | ||
|
|
e4d7103508 | ||
|
|
e292cc4441 | ||
|
|
e4a8c9ee98 | ||
|
|
999dbb264e | ||
|
|
81dda34ab8 | ||
|
|
4e2d2d3459 | ||
|
|
99b8761a64 | ||
|
|
93a8014aed | ||
|
|
f7b5bef7db | ||
|
|
fc147e3e9a | ||
|
|
76d1772b5c | ||
|
|
17337de6eb | ||
|
|
a85a760ffe | ||
|
|
c5f7898382 | ||
|
|
b99e8e73c0 | ||
|
|
bc73ff9023 | ||
|
|
54d7dbff62 | ||
|
|
3a49fc87eb | ||
|
|
d016dbec4d | ||
|
|
6fb4cb56eb | ||
|
|
d0d53d3639 | ||
|
|
fa02355001 | ||
|
|
0e17da4880 | ||
|
|
bc00d08261 | ||
|
|
bd5e6af609 | ||
|
|
7a2242a6f5 | ||
|
|
765d2d36b6 | ||
|
|
2ed31a7b8c | ||
|
|
73495d0707 | ||
|
|
f63b807789 | ||
|
|
0f7da63220 | ||
|
|
8bfb4f456a | ||
|
|
5b0d76503f | ||
|
|
6bee96c375 | ||
|
|
38b4cba146 | ||
|
|
e55840a455 | ||
|
|
b3cd6fee77 | ||
|
|
c73796da90 | ||
|
|
ffe42256e9 | ||
|
|
0ec46f6857 | ||
|
|
4a987ddbf2 | ||
|
|
344b434cc5 | ||
|
|
75098e5aed | ||
|
|
d9ef10a817 | ||
|
|
06dd876038 | ||
|
|
2fbace3fe7 | ||
|
|
a1f90b5cb6 | ||
|
|
c943ad2d40 | ||
|
|
76f3e9841d | ||
|
|
f8b5b0ba54 | ||
|
|
d2da1b3f4e | ||
|
|
66d7f1b060 | ||
|
|
fe8083adf5 | ||
|
|
fe347e39f7 | ||
|
|
d171179dbd | ||
|
|
528f6ad58b | ||
|
|
77ecfe527e | ||
|
|
a628db994b | ||
|
|
881448ec76 | ||
|
|
ff9e40ef8f | ||
|
|
9c177584fa | ||
|
|
3f90f99ee8 | ||
|
|
a916289a66 | ||
|
|
6d63683bff | ||
|
|
401e835d08 | ||
|
|
432a0cdef4 | ||
|
|
b4fc122d3b | ||
|
|
98faf392a7 | ||
|
|
6340e7ebdd | ||
|
|
04186aa3e4 | ||
|
|
6d7e75d5cc | ||
|
|
545b26756d | ||
|
|
39e483ddef | ||
|
|
9f37d43672 | ||
|
|
08f34b4545 | ||
|
|
8a4ba4d2e2 | ||
|
|
f6f10c0521 | ||
|
|
dd0c300698 | ||
|
|
065e298ab4 | ||
|
|
2a50dcb6c2 | ||
|
|
a2e99a055a | ||
|
|
a445fcb4f6 | ||
|
|
20b1870d75 | ||
|
|
b0d00d521a | ||
|
|
bde63b5a5b | ||
|
|
1a25d7c52c | ||
|
|
e9ae886c3c | ||
|
|
9a8ec4aa09 | ||
|
|
9c2ffb74c9 | ||
|
|
ab337ed807 | ||
|
|
8545d214b6 | ||
|
|
bfd72b42df | ||
|
|
f044200d9e | ||
|
|
0fab3cfeec | ||
|
|
c8e1de6146 | ||
|
|
de40dc2334 | ||
|
|
9f8d68f07b | ||
|
|
fe598a831e | ||
|
|
aeb0002877 | ||
|
|
910b9518e4 | ||
|
|
2ce593b5de |
20
.github/dependabot.yml
vendored
20
.github/dependabot.yml
vendored
@@ -10,7 +10,7 @@ updates:
|
||||
groups:
|
||||
otel:
|
||||
patterns:
|
||||
- "^go.opentelemetry.io/.*"
|
||||
- "go.opentelemetry.io/*"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
@@ -22,10 +22,11 @@ updates:
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/ui-test/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
# Disabled since this code is rarely used.
|
||||
# - package-ecosystem: "npm"
|
||||
# directory: "/ui-test/"
|
||||
# schedule:
|
||||
# interval: "daily"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/"
|
||||
@@ -52,7 +53,8 @@ updates:
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/ui-test/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
# Disabled since this code is rarely used.
|
||||
# - package-ecosystem: "docker"
|
||||
# directory: "/ui-test/"
|
||||
# schedule:
|
||||
# interval: "daily"
|
||||
|
||||
2
.github/workflows/README.md
vendored
2
.github/workflows/README.md
vendored
@@ -16,7 +16,7 @@
|
||||
|
||||
## image-reuse.yaml
|
||||
|
||||
- The resuable workflow can be used to publish or build images with multiple container registries(Quay,GHCR, dockerhub), and then sign them with cosign when an image is published.
|
||||
- The reusable workflow can be used to publish or build images with multiple container registries(Quay,GHCR, dockerhub), and then sign them with cosign when an image is published.
|
||||
- A GO version `must` be specified e.g. 1.21
|
||||
- The image name for each registry *must* contain the tag. Note: multiple tags are allowed for each registry using a CSV type.
|
||||
- Multiple platforms can be specified e.g. linux/amd64,linux/arm64
|
||||
|
||||
4
.github/workflows/bump-major-version.yaml
vendored
4
.github/workflows/bump-major-version.yaml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
working-directory: /home/runner/go/src/github.com/argoproj/argo-cd
|
||||
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Add ~/go/bin to PATH
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
rsync -a --exclude=.git /home/runner/go/src/github.com/argoproj/argo-cd/ ../argo-cd
|
||||
|
||||
- name: Create pull request
|
||||
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||
with:
|
||||
commit-message: "Bump major version to ${{ steps.get-target-version.outputs.TARGET_VERSION }}"
|
||||
title: "Bump major version to ${{ steps.get-target-version.outputs.TARGET_VERSION }}"
|
||||
|
||||
83
.github/workflows/ci-build.yaml
vendored
83
.github/workflows/ci-build.yaml
vendored
@@ -14,11 +14,11 @@ on:
|
||||
env:
|
||||
# Golang version to use across CI steps
|
||||
# renovate: datasource=golang-version packageName=golang
|
||||
GOLANG_VERSION: '1.24.1'
|
||||
GOLANG_VERSION: '1.25.5'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
docs: ${{ steps.filter.outputs.docs_any_changed }}
|
||||
steps:
|
||||
- uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
- uses: tj-actions/changed-files@bab30c2299617f6615ec02a68b9a40d10bd21366 # v45.0.5
|
||||
- uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46.0.5
|
||||
id: filter
|
||||
with:
|
||||
# Any file which is not under docs/, ui/ or is not a markdown file is counted as a backend file
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Download all Go modules
|
||||
@@ -78,11 +78,11 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Restore go build cache
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
|
||||
@@ -103,16 +103,16 @@ jobs:
|
||||
- changes
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1
|
||||
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
|
||||
with:
|
||||
# renovate: datasource=go packageName=github.com/golangci/golangci-lint versioning=regex:^v(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)?$
|
||||
version: v1.64.7
|
||||
version: v2.5.0
|
||||
args: --verbose
|
||||
|
||||
test-go:
|
||||
@@ -133,7 +133,7 @@ jobs:
|
||||
- name: Create symlink in GOPATH
|
||||
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Install required packages
|
||||
@@ -153,7 +153,7 @@ jobs:
|
||||
run: |
|
||||
echo "/usr/local/bin" >> $GITHUB_PATH
|
||||
- name: Restore go build cache
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
|
||||
@@ -174,7 +174,7 @@ jobs:
|
||||
- name: Run all unit tests
|
||||
run: make test-local
|
||||
- name: Generate test results artifacts
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: test-results
|
||||
path: test-results
|
||||
@@ -197,7 +197,7 @@ jobs:
|
||||
- name: Create symlink in GOPATH
|
||||
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Install required packages
|
||||
@@ -217,7 +217,7 @@ jobs:
|
||||
run: |
|
||||
echo "/usr/local/bin" >> $GITHUB_PATH
|
||||
- name: Restore go build cache
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
|
||||
@@ -238,7 +238,7 @@ jobs:
|
||||
- name: Run all unit tests
|
||||
run: make test-race-local
|
||||
- name: Generate test results artifacts
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: race-results
|
||||
path: test-results/
|
||||
@@ -253,7 +253,7 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Create symlink in GOPATH
|
||||
@@ -305,13 +305,13 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
# renovate: datasource=node-version packageName=node versioning=node
|
||||
node-version: '22.9.0'
|
||||
- name: Restore node dependency cache
|
||||
id: cache-dependencies
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: ui/node_modules
|
||||
key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }}
|
||||
@@ -333,6 +333,15 @@ jobs:
|
||||
run: yarn lint
|
||||
working-directory: ui/
|
||||
|
||||
shellcheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
- run: |
|
||||
sudo apt-get install shellcheck
|
||||
shellcheck -e SC2086 -e SC2046 -e SC2068 -e SC2206 -e SC2048 -e SC2059 -e SC2154 -e SC2034 -e SC2016 -e SC2128 -e SC1091 -e SC2207 $(find . -type f -name '*.sh') | tee sc.log
|
||||
test ! -s sc.log
|
||||
|
||||
analyze:
|
||||
name: Process & analyze test artifacts
|
||||
if: ${{ needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true' }}
|
||||
@@ -351,7 +360,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- name: Restore node dependency cache
|
||||
id: cache-dependencies
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: ui/node_modules
|
||||
key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }}
|
||||
@@ -359,12 +368,12 @@ jobs:
|
||||
run: |
|
||||
rm -rf ui/node_modules/argo-ui/node_modules
|
||||
- name: Get e2e code coverage
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: e2e-code-coverage
|
||||
path: e2e-code-coverage
|
||||
- name: Get unit test code coverage
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: test-results
|
||||
path: test-results
|
||||
@@ -376,15 +385,15 @@ jobs:
|
||||
run: |
|
||||
go tool covdata percent -i=test-results,e2e-code-coverage/applicationset-controller,e2e-code-coverage/repo-server,e2e-code-coverage/app-controller,e2e-code-coverage/commit-server -o test-results/full-coverage.out
|
||||
- name: Upload code coverage information to codecov.io
|
||||
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
with:
|
||||
file: test-results/full-coverage.out
|
||||
files: test-results/full-coverage.out
|
||||
fail_ci_if_error: true
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
- name: Upload test results to Codecov
|
||||
if: github.ref == 'refs/heads/master' && github.event_name == 'push' && github.repository == 'argoproj/argo-cd'
|
||||
uses: codecov/test-results-action@9739113ad922ea0a9abb4b2c0f8bf6a4aa8ef820 # v1.0.1
|
||||
uses: codecov/test-results-action@47f89e9acb64b76debcd5ea40642d25a4adced9f # v1.1.1
|
||||
with:
|
||||
file: test-results/junit.xml
|
||||
fail_ci_if_error: true
|
||||
@@ -393,7 +402,7 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
uses: SonarSource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203 # v4.2.1
|
||||
uses: SonarSource/sonarqube-scan-action@2500896589ef8f7247069a56136f8dc177c27ccf # v5.2.0
|
||||
if: env.sonar_secret != ''
|
||||
test-e2e:
|
||||
name: Run end-to-end tests
|
||||
@@ -405,14 +414,14 @@ jobs:
|
||||
# latest: true means that this version mush upload the coverage report to codecov.io
|
||||
# We designate the latest version because we only collect code coverage for that version.
|
||||
k3s:
|
||||
- version: v1.32.1
|
||||
- version: v1.34.2
|
||||
latest: true
|
||||
- version: v1.33.1
|
||||
latest: false
|
||||
- version: v1.32.1
|
||||
latest: false
|
||||
- version: v1.31.0
|
||||
latest: false
|
||||
- version: v1.30.4
|
||||
latest: false
|
||||
- version: v1.29.8
|
||||
latest: false
|
||||
needs:
|
||||
- build-go
|
||||
- changes
|
||||
@@ -440,7 +449,7 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: GH actions workaround - Kill XSP4 process
|
||||
@@ -459,7 +468,7 @@ jobs:
|
||||
sudo chmod go-r $HOME/.kube/config
|
||||
kubectl version
|
||||
- name: Restore go build cache
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
|
||||
@@ -485,9 +494,9 @@ jobs:
|
||||
git config --global user.email "john.doe@example.com"
|
||||
- name: Pull Docker image required for tests
|
||||
run: |
|
||||
docker pull ghcr.io/dexidp/dex:v2.41.1
|
||||
docker pull ghcr.io/dexidp/dex:v2.43.0
|
||||
docker pull argoproj/argo-cd-ci-builder:v1.0.0
|
||||
docker pull redis:7.2.7-alpine
|
||||
docker pull redis:7.2.11-alpine
|
||||
- name: Create target directory for binaries in the build-process
|
||||
run: |
|
||||
mkdir -p dist
|
||||
@@ -517,13 +526,13 @@ jobs:
|
||||
goreman run stop-all || echo "goreman trouble"
|
||||
sleep 30
|
||||
- name: Upload e2e coverage report
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: e2e-code-coverage
|
||||
path: /tmp/coverage
|
||||
if: ${{ matrix.k3s.latest }}
|
||||
- name: Upload e2e-server logs
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: e2e-server-k8s${{ matrix.k3s.version }}.log
|
||||
path: /tmp/e2e-server.log
|
||||
|
||||
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
# Use correct go version. https://github.com/github/codeql-action/issues/1842#issuecomment-1704398087
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
|
||||
17
.github/workflows/image-reuse.yaml
vendored
17
.github/workflows/image-reuse.yaml
vendored
@@ -67,15 +67,16 @@ jobs:
|
||||
if: ${{ github.ref_type != 'tag'}}
|
||||
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: ${{ inputs.go-version }}
|
||||
cache: false
|
||||
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
|
||||
uses: sigstore/cosign-installer@fb28c2b6339dcd94da6e4cbcbc5e888961f6f8c3 # v3.9.0
|
||||
|
||||
- uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0
|
||||
- uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
||||
- uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- name: Setup tags for container image as a CSV type
|
||||
run: |
|
||||
@@ -102,7 +103,7 @@ jobs:
|
||||
echo 'EOF' >> $GITHUB_ENV
|
||||
|
||||
- name: Login to Quay.io
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ secrets.quay_username }}
|
||||
@@ -110,7 +111,7 @@ jobs:
|
||||
if: ${{ inputs.quay_image_name && inputs.push }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ secrets.ghcr_username }}
|
||||
@@ -118,7 +119,7 @@ jobs:
|
||||
if: ${{ inputs.ghcr_image_name && inputs.push }}
|
||||
|
||||
- name: Login to dockerhub Container Registry
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
username: ${{ secrets.docker_username }}
|
||||
password: ${{ secrets.docker_password }}
|
||||
@@ -141,7 +142,7 @@ jobs:
|
||||
|
||||
- name: Build and push container image
|
||||
id: image
|
||||
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 #v6.10.0
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 #v6.18.0
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ inputs.platforms }}
|
||||
|
||||
6
.github/workflows/image.yaml
vendored
6
.github/workflows/image.yaml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
with:
|
||||
# Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations)
|
||||
# renovate: datasource=golang-version packageName=golang
|
||||
go-version: 1.24.1
|
||||
go-version: 1.25.5
|
||||
platforms: ${{ needs.set-vars.outputs.platforms }}
|
||||
push: false
|
||||
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
ghcr_image_name: ghcr.io/argoproj/argo-cd/argocd:${{ needs.set-vars.outputs.image-tag }}
|
||||
# Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations)
|
||||
# renovate: datasource=golang-version packageName=golang
|
||||
go-version: 1.24.1
|
||||
go-version: 1.25.5
|
||||
platforms: ${{ needs.set-vars.outputs.platforms }}
|
||||
push: true
|
||||
secrets:
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
|
||||
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
|
||||
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||
with:
|
||||
image: ghcr.io/argoproj/argo-cd/argocd
|
||||
digest: ${{ needs.build-and-publish.outputs.image-digest }}
|
||||
|
||||
2
.github/workflows/init-release.yaml
vendored
2
.github/workflows/init-release.yaml
vendored
@@ -64,7 +64,7 @@ jobs:
|
||||
git stash pop
|
||||
|
||||
- name: Create pull request
|
||||
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||
with:
|
||||
commit-message: "Bump version to ${{ inputs.TARGET_VERSION }}"
|
||||
title: "Bump version to ${{ inputs.TARGET_VERSION }} on ${{ inputs.TARGET_BRANCH }} branch"
|
||||
|
||||
91
.github/workflows/release.yaml
vendored
91
.github/workflows/release.yaml
vendored
@@ -11,7 +11,7 @@ permissions: {}
|
||||
|
||||
env:
|
||||
# renovate: datasource=golang-version packageName=golang
|
||||
GOLANG_VERSION: '1.24.1' # Note: go-version must also be set in job argocd-image.with.go-version
|
||||
GOLANG_VERSION: '1.25.5' # Note: go-version must also be set in job argocd-image.with.go-version
|
||||
|
||||
jobs:
|
||||
argocd-image:
|
||||
@@ -25,13 +25,49 @@ jobs:
|
||||
quay_image_name: quay.io/argoproj/argocd:${{ github.ref_name }}
|
||||
# Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations)
|
||||
# renovate: datasource=golang-version packageName=golang
|
||||
go-version: 1.24.1
|
||||
go-version: 1.25.5
|
||||
platforms: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
|
||||
push: true
|
||||
secrets:
|
||||
quay_username: ${{ secrets.RELEASE_QUAY_USERNAME }}
|
||||
quay_password: ${{ secrets.RELEASE_QUAY_TOKEN }}
|
||||
|
||||
setup-variables:
|
||||
name: Setup Release Variables
|
||||
if: github.repository == 'argoproj/argo-cd'
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
is_pre_release: ${{ steps.var.outputs.is_pre_release }}
|
||||
is_latest_release: ${{ steps.var.outputs.is_latest_release }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Setup variables
|
||||
id: var
|
||||
run: |
|
||||
set -xue
|
||||
# Fetch all tag information
|
||||
git fetch --prune --tags --force
|
||||
|
||||
LATEST_RELEASE_TAG=$(git -c 'versionsort.suffix=-rc' tag --list --sort=version:refname | grep -v '-' | tail -n1)
|
||||
|
||||
PRE_RELEASE=false
|
||||
# Check if latest tag is a pre-release
|
||||
if echo ${{ github.ref_name }} | grep -E -- '-rc[0-9]+$';then
|
||||
PRE_RELEASE=true
|
||||
fi
|
||||
|
||||
IS_LATEST=false
|
||||
# Ensure latest release tag matches github.ref_name
|
||||
if [[ $LATEST_RELEASE_TAG == ${{ github.ref_name }} ]];then
|
||||
IS_LATEST=true
|
||||
fi
|
||||
echo "is_pre_release=$PRE_RELEASE" >> $GITHUB_OUTPUT
|
||||
echo "is_latest_release=$IS_LATEST" >> $GITHUB_OUTPUT
|
||||
|
||||
argocd-image-provenance:
|
||||
needs: [argocd-image]
|
||||
permissions:
|
||||
@@ -40,7 +76,7 @@ jobs:
|
||||
packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
|
||||
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
|
||||
if: github.repository == 'argoproj/argo-cd'
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||
with:
|
||||
image: quay.io/argoproj/argocd
|
||||
digest: ${{ needs.argocd-image.outputs.image-digest }}
|
||||
@@ -50,15 +86,17 @@ jobs:
|
||||
|
||||
goreleaser:
|
||||
needs:
|
||||
- setup-variables
|
||||
- argocd-image
|
||||
- argocd-image-provenance
|
||||
permissions:
|
||||
contents: write # used for uploading assets
|
||||
if: github.repository == 'argoproj/argo-cd'
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
GORELEASER_MAKE_LATEST: ${{ needs.setup-variables.outputs.is_latest_release }}
|
||||
outputs:
|
||||
hashes: ${{ steps.hash.outputs.hashes }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
@@ -70,14 +108,16 @@ jobs:
|
||||
run: git fetch --force --tags
|
||||
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
cache: false
|
||||
|
||||
- name: Set GORELEASER_PREVIOUS_TAG # Workaround, GoReleaser uses 'git-describe' to determine a previous tag. Our tags are created in release branches.
|
||||
run: |
|
||||
set -xue
|
||||
echo "GORELEASER_PREVIOUS_TAG=$(go run hack/get-previous-release/get-previous-version-for-release-notes.go ${{ github.ref_name }})" >> $GITHUB_ENV
|
||||
GORELEASER_PREVIOUS_TAG=$(go run hack/get-previous-release/get-previous-version-for-release-notes.go ${{ github.ref_name }}) || exit 1
|
||||
echo "GORELEASER_PREVIOUS_TAG=$GORELEASER_PREVIOUS_TAG" >> $GITHUB_ENV
|
||||
|
||||
- name: Set environment variables for ldflags
|
||||
id: set_ldflag
|
||||
@@ -94,7 +134,7 @@ jobs:
|
||||
tool-cache: false
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf # v6.1.0
|
||||
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
|
||||
id: run-goreleaser
|
||||
with:
|
||||
version: latest
|
||||
@@ -126,7 +166,7 @@ jobs:
|
||||
contents: write # Needed for release uploads
|
||||
if: github.repository == 'argoproj/argo-cd'
|
||||
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
|
||||
with:
|
||||
base64-subjects: '${{ needs.goreleaser.outputs.hashes }}'
|
||||
provenance-name: 'argocd-cli.intoto.jsonl'
|
||||
@@ -140,7 +180,7 @@ jobs:
|
||||
permissions:
|
||||
contents: write # Needed for release uploads
|
||||
outputs:
|
||||
hashes: ${{ steps.sbom-hash.outputs.hashes}}
|
||||
hashes: ${{ steps.sbom-hash.outputs.hashes }}
|
||||
if: github.repository == 'argoproj/argo-cd'
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
@@ -151,9 +191,10 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
cache: false
|
||||
|
||||
- name: Generate SBOM (spdx)
|
||||
id: spdx-builder
|
||||
@@ -195,7 +236,7 @@ jobs:
|
||||
echo "hashes=$(sha256sum /tmp/sbom.tar.gz | base64 -w0)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload SBOM
|
||||
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
|
||||
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -210,7 +251,7 @@ jobs:
|
||||
contents: write # Needed for release uploads
|
||||
if: github.repository == 'argoproj/argo-cd'
|
||||
# Must be referenced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
|
||||
with:
|
||||
base64-subjects: '${{ needs.generate-sbom.outputs.hashes }}'
|
||||
provenance-name: 'argocd-sbom.intoto.jsonl'
|
||||
@@ -218,6 +259,7 @@ jobs:
|
||||
|
||||
post-release:
|
||||
needs:
|
||||
- setup-variables
|
||||
- argocd-image
|
||||
- goreleaser
|
||||
- generate-sbom
|
||||
@@ -226,6 +268,8 @@ jobs:
|
||||
pull-requests: write # Needed to create PR for VERSION update.
|
||||
if: github.repository == 'argoproj/argo-cd'
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
TAG_STABLE: ${{ needs.setup-variables.outputs.is_latest_release }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
|
||||
@@ -239,27 +283,6 @@ jobs:
|
||||
git config --global user.email 'ci@argoproj.com'
|
||||
git config --global user.name 'CI'
|
||||
|
||||
- name: Check if tag is the latest version and not a pre-release
|
||||
run: |
|
||||
set -xue
|
||||
# Fetch all tag information
|
||||
git fetch --prune --tags --force
|
||||
|
||||
LATEST_TAG=$(git -c 'versionsort.suffix=-rc' tag --list --sort=version:refname | tail -n1)
|
||||
|
||||
PRE_RELEASE=false
|
||||
# Check if latest tag is a pre-release
|
||||
if echo $LATEST_TAG | grep -E -- '-rc[0-9]+$';then
|
||||
PRE_RELEASE=true
|
||||
fi
|
||||
|
||||
# Ensure latest tag matches github.ref_name & not a pre-release
|
||||
if [[ $LATEST_TAG == ${{ github.ref_name }} ]] && [[ $PRE_RELEASE != 'true' ]];then
|
||||
echo "TAG_STABLE=true" >> $GITHUB_ENV
|
||||
else
|
||||
echo "TAG_STABLE=false" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Update stable tag to latest version
|
||||
run: |
|
||||
git tag -f stable ${{ github.ref_name }}
|
||||
@@ -293,7 +316,7 @@ jobs:
|
||||
if: ${{ env.UPDATE_VERSION == 'true' }}
|
||||
|
||||
- name: Create PR to update VERSION on master branch
|
||||
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||
with:
|
||||
commit-message: Bump version in master
|
||||
title: 'chore: Bump version in master'
|
||||
|
||||
4
.github/workflows/scorecard.yaml
vendored
4
.github/workflows/scorecard.yaml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
|
||||
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -8,6 +8,7 @@ ui/dist/app/*
|
||||
!ui/dist/app/gitkeep
|
||||
site/
|
||||
*.iml
|
||||
.tilt-bin/
|
||||
# delve debug binaries
|
||||
cmd/**/debug
|
||||
debug.test
|
||||
@@ -27,3 +28,6 @@ cmd/argocd/argocd
|
||||
cmd/argocd-application-controller/argocd-application-controller
|
||||
cmd/argocd-repo-server/argocd-repo-server
|
||||
cmd/argocd-server/argocd-server
|
||||
|
||||
# ignore generated `.argocd-helm-dep-up` marker file; this should not be committed to git
|
||||
reposerver/repository/testdata/**/.argocd-helm-dep-up
|
||||
21
.gitpod.Dockerfile
vendored
21
.gitpod.Dockerfile
vendored
@@ -1,21 +0,0 @@
|
||||
FROM gitpod/workspace-full@sha256:a47a68ee7f9da10cd889ccce4661bc73f2c0d5a98d3d087e8bdfc0230b27964c
|
||||
|
||||
USER root
|
||||
|
||||
RUN curl -o /usr/local/bin/kubectl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" && \
|
||||
chmod +x /usr/local/bin/kubectl
|
||||
|
||||
RUN curl -L https://github.com/kubernetes-sigs/kubebuilder/releases/download/v2.3.1/kubebuilder_2.3.1_$(go env GOOS)_$(go env GOARCH).tar.gz | \
|
||||
tar -xz -C /tmp/ && mv /tmp/kubebuilder_2.3.1_$(go env GOOS)_$(go env GOARCH) /usr/local/kubebuilder
|
||||
|
||||
ENV GOCACHE=/go-build-cache
|
||||
|
||||
RUN apt-get install redis-server -y
|
||||
RUN go install github.com/mattn/goreman@latest
|
||||
|
||||
RUN chown -R gitpod:gitpod /go-build-cache
|
||||
|
||||
USER gitpod
|
||||
|
||||
ENV ARGOCD_REDIS_LOCAL=true
|
||||
ENV KUBECONFIG=/tmp/kubeconfig
|
||||
@@ -1,6 +0,0 @@
|
||||
image:
|
||||
file: .gitpod.Dockerfile
|
||||
|
||||
tasks:
|
||||
- init: make mod-download-local dep-ui-local && GO111MODULE=off go install github.com/mattn/goreman@latest
|
||||
command: make start-test-k8s
|
||||
375
.golangci.yaml
375
.golangci.yaml
@@ -1,156 +1,253 @@
|
||||
issues:
|
||||
exclude:
|
||||
- SA5011
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
exclude-rules:
|
||||
- path: '(.+)_test\.go'
|
||||
linters:
|
||||
- unparam
|
||||
linters:
|
||||
formatters:
|
||||
enable:
|
||||
- errcheck
|
||||
- errorlint
|
||||
- gocritic
|
||||
- gofumpt
|
||||
- goimports
|
||||
|
||||
settings:
|
||||
goimports:
|
||||
local-prefixes:
|
||||
- github.com/argoproj/argo-cd/v3
|
||||
|
||||
issues:
|
||||
max-issues-per-linter: 0
|
||||
|
||||
max-same-issues: 0
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- errorlint
|
||||
- exptostd
|
||||
- gocritic
|
||||
- gomodguard
|
||||
- gosimple
|
||||
- govet
|
||||
- importas
|
||||
- ineffassign
|
||||
- misspell
|
||||
# Disabled because of https://github.com/argoproj/argo-cd/issues/21705
|
||||
# - nolintlint
|
||||
- perfsprint
|
||||
- revive
|
||||
- staticcheck
|
||||
- testifylint
|
||||
- thelper
|
||||
- tparallel
|
||||
- unparam
|
||||
- unused
|
||||
- usestdlibvars
|
||||
- usetesting
|
||||
- whitespace
|
||||
linters-settings:
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- appendAssign
|
||||
- assignOp # Keep it disabled for readability
|
||||
- exitAfterDefer
|
||||
- mapKey
|
||||
- typeSwitchVar
|
||||
goimports:
|
||||
local-prefixes: github.com/argoproj/argo-cd/v3
|
||||
gomodguard:
|
||||
blocked:
|
||||
modules:
|
||||
- github.com/golang-jwt/jwt/v4:
|
||||
recommendations:
|
||||
- github.com/golang-jwt/jwt/v5
|
||||
- github.com/imdario/mergo:
|
||||
recommendations:
|
||||
- dario.cat/mergo
|
||||
reason: '`github.com/imdario/mergo` has been renamed.'
|
||||
- github.com/pkg/errors:
|
||||
recommendations:
|
||||
- errors
|
||||
importas:
|
||||
alias:
|
||||
- alias: jwtgo
|
||||
pkg: github.com/golang-jwt/jwt/v5
|
||||
- alias: appsv1
|
||||
pkg: k8s.io/api/apps/v1
|
||||
- alias: corev1
|
||||
pkg: k8s.io/api/core/v1
|
||||
- alias: rbacv1
|
||||
pkg: k8s.io/api/rbac/v1
|
||||
- alias: apierrors
|
||||
pkg: k8s.io/apimachinery/pkg/api/errors
|
||||
- alias: apiextensionsv1
|
||||
pkg: k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1
|
||||
- alias: metav1
|
||||
pkg: k8s.io/apimachinery/pkg/apis/meta/v1
|
||||
- alias: informersv1
|
||||
pkg: k8s.io/client-go/informers/core/v1
|
||||
- alias: stderrors
|
||||
pkg: errors
|
||||
nolintlint:
|
||||
require-specific: true
|
||||
perfsprint:
|
||||
# Optimizes even if it requires an int or uint type cast.
|
||||
int-conversion: true
|
||||
# Optimizes into `err.Error()` even if it is only equivalent for non-nil errors.
|
||||
err-error: true
|
||||
# Optimizes `fmt.Errorf`.
|
||||
errorf: true
|
||||
# Optimizes `fmt.Sprintf` with only one argument.
|
||||
sprintf1: true
|
||||
# Optimizes into strings concatenation.
|
||||
strconcat: true
|
||||
revive:
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md
|
||||
|
||||
exclusions:
|
||||
rules:
|
||||
- name: bool-literal-in-expr
|
||||
- name: blank-imports
|
||||
disabled: true
|
||||
- name: context-as-argument
|
||||
arguments:
|
||||
- allowTypesBefore: '*testing.T,testing.TB'
|
||||
- name: context-keys-type
|
||||
disabled: true
|
||||
- name: dot-imports
|
||||
disabled: true
|
||||
- name: duplicated-imports
|
||||
- name: early-return
|
||||
arguments:
|
||||
- 'preserveScope'
|
||||
- name: empty-block
|
||||
disabled: true
|
||||
- name: error-naming
|
||||
disabled: true
|
||||
- name: error-return
|
||||
- name: error-strings
|
||||
disabled: true
|
||||
- name: errorf
|
||||
- name: identical-branches
|
||||
- name: if-return
|
||||
- name: increment-decrement
|
||||
- name: indent-error-flow
|
||||
arguments:
|
||||
- 'preserveScope'
|
||||
- name: modifies-parameter
|
||||
- name: optimize-operands-order
|
||||
- name: range
|
||||
- name: receiver-naming
|
||||
- name: redefines-builtin-id
|
||||
disabled: true
|
||||
- name: redundant-import-alias
|
||||
- name: superfluous-else
|
||||
arguments:
|
||||
- 'preserveScope'
|
||||
- name: time-equal
|
||||
- name: time-naming
|
||||
disabled: true
|
||||
- name: unexported-return
|
||||
disabled: true
|
||||
- name: unnecessary-stmt
|
||||
- name: unreachable-code
|
||||
- name: unused-parameter
|
||||
- name: use-any
|
||||
- name: useless-break
|
||||
- name: var-declaration
|
||||
- name: var-naming
|
||||
arguments:
|
||||
- ["ID"]
|
||||
- ["VM"]
|
||||
- - skipPackageNameChecks: true
|
||||
upperCaseConst: true
|
||||
testifylint:
|
||||
enable-all: true
|
||||
disable:
|
||||
- go-require
|
||||
usetesting:
|
||||
os-mkdir-temp: false
|
||||
run:
|
||||
timeout: 50m
|
||||
- linters:
|
||||
- unparam
|
||||
path: (.+)_test\.go
|
||||
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
|
||||
warn-unused: true
|
||||
|
||||
settings:
|
||||
gocritic:
|
||||
enable-all: true
|
||||
# Most of these should probably be enabled one-by-one.
|
||||
disabled-checks:
|
||||
- appendAssign
|
||||
- appendCombine # Leave disabled, multi-line assigns can be more readable.
|
||||
- assignOp # Leave disabled, assign operations can be more confusing than helpful.
|
||||
- commentedOutCode
|
||||
- deferInLoop
|
||||
- exitAfterDefer
|
||||
- exposedSyncMutex
|
||||
- hugeParam
|
||||
- importShadow
|
||||
- paramTypeCombine # Leave disabled, there are too many failures to be worth fixing.
|
||||
- rangeValCopy
|
||||
- tooManyResultsChecker
|
||||
- unnamedResult
|
||||
- whyNoLint
|
||||
|
||||
gomodguard:
|
||||
blocked:
|
||||
modules:
|
||||
- github.com/golang-jwt/jwt/v4:
|
||||
recommendations:
|
||||
- github.com/golang-jwt/jwt/v5
|
||||
|
||||
- github.com/imdario/mergo:
|
||||
recommendations:
|
||||
- dario.cat/mergo
|
||||
reason: '`github.com/imdario/mergo` has been renamed.'
|
||||
|
||||
- github.com/pkg/errors:
|
||||
recommendations:
|
||||
- errors
|
||||
|
||||
govet:
|
||||
disable:
|
||||
- fieldalignment
|
||||
- shadow
|
||||
enable-all: true
|
||||
|
||||
importas:
|
||||
alias:
|
||||
- pkg: github.com/golang-jwt/jwt/v5
|
||||
alias: jwtgo
|
||||
|
||||
- pkg: k8s.io/api/apps/v1
|
||||
alias: appsv1
|
||||
|
||||
- pkg: k8s.io/api/core/v1
|
||||
alias: corev1
|
||||
|
||||
- pkg: k8s.io/api/rbac/v1
|
||||
alias: rbacv1
|
||||
|
||||
- pkg: k8s.io/apimachinery/pkg/api/errors
|
||||
alias: apierrors
|
||||
|
||||
- pkg: k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1
|
||||
alias: apiextensionsv1
|
||||
|
||||
- pkg: k8s.io/apimachinery/pkg/apis/meta/v1
|
||||
alias: metav1
|
||||
|
||||
- pkg: k8s.io/client-go/informers/core/v1
|
||||
alias: informersv1
|
||||
|
||||
- pkg: errors
|
||||
alias: stderrors
|
||||
|
||||
- pkg: github.com/argoproj/argo-cd/v3/util/io
|
||||
alias: utilio
|
||||
|
||||
nolintlint:
|
||||
require-specific: true
|
||||
|
||||
perfsprint:
|
||||
# Optimizes even if it requires an int or uint type cast.
|
||||
int-conversion: true
|
||||
# Optimizes into `err.Error()` even if it is only equivalent for non-nil errors.
|
||||
err-error: true
|
||||
# Optimizes `fmt.Errorf`.
|
||||
errorf: true
|
||||
# Optimizes `fmt.Sprintf` with only one argument.
|
||||
sprintf1: true
|
||||
# Optimizes into strings concatenation.
|
||||
strconcat: true
|
||||
|
||||
revive:
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md
|
||||
rules:
|
||||
- name: bool-literal-in-expr
|
||||
|
||||
- name: blank-imports
|
||||
disabled: true
|
||||
|
||||
- name: context-as-argument
|
||||
arguments:
|
||||
- allowTypesBefore: '*testing.T,testing.TB'
|
||||
|
||||
- name: context-keys-type
|
||||
disabled: true
|
||||
|
||||
- name: dot-imports
|
||||
disabled: true
|
||||
|
||||
- name: duplicated-imports
|
||||
|
||||
- name: early-return
|
||||
arguments:
|
||||
- preserveScope
|
||||
|
||||
- name: empty-block
|
||||
disabled: true
|
||||
|
||||
- name: error-naming
|
||||
disabled: true
|
||||
|
||||
- name: error-return
|
||||
|
||||
- name: error-strings
|
||||
disabled: true
|
||||
|
||||
- name: errorf
|
||||
|
||||
- name: identical-branches
|
||||
|
||||
- name: if-return
|
||||
|
||||
- name: increment-decrement
|
||||
|
||||
- name: indent-error-flow
|
||||
arguments:
|
||||
- preserveScope
|
||||
|
||||
- name: modifies-parameter
|
||||
|
||||
- name: optimize-operands-order
|
||||
|
||||
- name: range
|
||||
|
||||
- name: receiver-naming
|
||||
|
||||
- name: redefines-builtin-id
|
||||
disabled: true
|
||||
|
||||
- name: redundant-import-alias
|
||||
|
||||
- name: superfluous-else
|
||||
arguments:
|
||||
- preserveScope
|
||||
|
||||
- name: time-equal
|
||||
|
||||
- name: time-naming
|
||||
disabled: true
|
||||
|
||||
- name: unexported-return
|
||||
disabled: true
|
||||
|
||||
- name: unnecessary-stmt
|
||||
|
||||
- name: unreachable-code
|
||||
|
||||
- name: unused-parameter
|
||||
|
||||
- name: use-any
|
||||
|
||||
- name: useless-break
|
||||
|
||||
- name: var-declaration
|
||||
|
||||
- name: var-naming
|
||||
arguments:
|
||||
- - ID
|
||||
- - VM
|
||||
- - skipPackageNameChecks: true
|
||||
upperCaseConst: true
|
||||
|
||||
staticcheck:
|
||||
checks:
|
||||
- all
|
||||
- -SA5011
|
||||
- -ST1003
|
||||
- -ST1016
|
||||
|
||||
testifylint:
|
||||
enable-all: true
|
||||
|
||||
disable:
|
||||
- go-require
|
||||
|
||||
unused:
|
||||
field-writes-are-uses: false
|
||||
exported-fields-are-used: false
|
||||
|
||||
usetesting:
|
||||
os-mkdir-temp: false
|
||||
|
||||
output:
|
||||
show-stats: false
|
||||
|
||||
version: "2"
|
||||
|
||||
@@ -24,8 +24,8 @@ builds:
|
||||
- -extldflags="-static"
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
@@ -45,17 +45,18 @@ builds:
|
||||
|
||||
archives:
|
||||
- id: argocd-archive
|
||||
builds:
|
||||
- argocd-cli
|
||||
ids:
|
||||
- argocd-cli
|
||||
name_template: |-
|
||||
{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}
|
||||
format: binary
|
||||
formats: [binary]
|
||||
|
||||
checksum:
|
||||
name_template: 'cli_checksums.txt'
|
||||
algorithm: sha256
|
||||
|
||||
release:
|
||||
make_latest: '{{ .Env.GORELEASER_MAKE_LATEST }}'
|
||||
prerelease: auto
|
||||
draft: false
|
||||
header: |
|
||||
@@ -80,26 +81,27 @@ release:
|
||||
All Argo CD container images are signed by cosign. A Provenance is generated for container images and CLI binaries which meet the SLSA Level 3 specifications. See the [documentation](https://argo-cd.readthedocs.io/en/stable/operator-manual/signed-release-assets) on how to verify.
|
||||
|
||||
## Release Notes Blog Post
|
||||
For a detailed breakdown of the key changes and improvements in this release, check out the [official blog post](https://blog.argoproj.io/argo-cd-v2-14-release-candidate-57a664791e2a)
|
||||
For a detailed breakdown of the key changes and improvements in this release, check out the [official blog post](https://blog.argoproj.io/announcing-argo-cd-v3-1-f4389bc783c8)
|
||||
|
||||
## Upgrading
|
||||
|
||||
If upgrading from a different minor version, be sure to read the [upgrading](https://argo-cd.readthedocs.io/en/stable/operator-manual/upgrading/overview/) documentation.
|
||||
footer: |
|
||||
**Full Changelog**: https://github.com/argoproj/argo-cd/compare/{{ .PreviousTag }}...{{ .Tag }}
|
||||
|
||||
|
||||
<a href="https://argoproj.github.io/cd/"><img src="https://raw.githubusercontent.com/argoproj/argo-site/master/content/pages/cd/gitops-cd.png" width="25%" ></a>
|
||||
|
||||
|
||||
snapshot: #### To be removed for PR
|
||||
name_template: "2.6.0"
|
||||
version_template: '2.6.0'
|
||||
|
||||
changelog:
|
||||
use:
|
||||
github
|
||||
use: github
|
||||
sort: asc
|
||||
abbrev: 0
|
||||
groups: # Regex use RE2 syntax as defined here: https://github.com/google/re2/wiki/Syntax.
|
||||
- title: 'Breaking Changes'
|
||||
regexp: '^.*?(\([[:word:]]+\))??!:.+$'
|
||||
order: 0
|
||||
- title: 'Features'
|
||||
regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
|
||||
order: 100
|
||||
@@ -119,7 +121,4 @@ changelog:
|
||||
- '^test:'
|
||||
- '^.*?Bump(\([[:word:]]+\))?.+$'
|
||||
- '^.*?\[Bot\](\([[:word:]]+\))?.+$'
|
||||
|
||||
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
|
||||
|
||||
131
.mockery.yaml
131
.mockery.yaml
@@ -1,82 +1,85 @@
|
||||
# global config
|
||||
filename: "{{.InterfaceName}}.go"
|
||||
dir: "{{.InterfaceDir}}/mocks"
|
||||
outpkg: "mocks"
|
||||
mockname: "{{.InterfaceName}}"
|
||||
with-expecter: false
|
||||
# individual interface config
|
||||
dir: '{{.InterfaceDir}}/mocks'
|
||||
structname: '{{.InterfaceName}}'
|
||||
filename: '{{.InterfaceName}}.go'
|
||||
pkgname: mocks
|
||||
|
||||
template-data:
|
||||
unroll-variadic: true
|
||||
|
||||
packages:
|
||||
github.com/argoproj/argo-cd/v3/applicationset/generators:
|
||||
interfaces:
|
||||
Generator:
|
||||
Generator: {}
|
||||
github.com/argoproj/argo-cd/v3/applicationset/services:
|
||||
interfaces:
|
||||
Repos:
|
||||
Repos: {}
|
||||
github.com/argoproj/argo-cd/v3/applicationset/services/scm_provider:
|
||||
config:
|
||||
dir: "applicationset/services/scm_provider/aws_codecommit/mocks"
|
||||
dir: applicationset/services/scm_provider/aws_codecommit/mocks
|
||||
interfaces:
|
||||
AWSCodeCommitClient:
|
||||
AWSTaggingClient:
|
||||
github.com/microsoft/azure-devops-go-api/azuredevops/git:
|
||||
config:
|
||||
dir: "applicationset/services/scm_provider/azure_devops/git/mocks"
|
||||
interfaces:
|
||||
Client:
|
||||
AWSCodeCommitClient: {}
|
||||
AWSTaggingClient: {}
|
||||
github.com/argoproj/argo-cd/v3/applicationset/utils:
|
||||
interfaces:
|
||||
Renderer:
|
||||
github.com/argoproj/argo-cd/v3/commitserver/commit:
|
||||
interfaces:
|
||||
RepoClientFactory:
|
||||
Renderer: {}
|
||||
github.com/argoproj/argo-cd/v3/commitserver/apiclient:
|
||||
interfaces:
|
||||
CommitServiceClient:
|
||||
Clientset:
|
||||
Clientset: {}
|
||||
CommitServiceClient: {}
|
||||
github.com/argoproj/argo-cd/v3/commitserver/commit:
|
||||
interfaces:
|
||||
RepoClientFactory: {}
|
||||
github.com/argoproj/argo-cd/v3/controller/cache:
|
||||
interfaces:
|
||||
LiveStateCache:
|
||||
github.com/argoproj/argo-cd/v3/reposerver/apiclient:
|
||||
interfaces:
|
||||
RepoServerServiceClient:
|
||||
RepoServerService_GenerateManifestWithFilesClient:
|
||||
github.com/argoproj/argo-cd/v3/server/application:
|
||||
interfaces:
|
||||
Broadcaster:
|
||||
github.com/argoproj/argo-cd/v3/server/extension:
|
||||
interfaces:
|
||||
ApplicationGetter:
|
||||
ExtensionMetricsRegistry:
|
||||
ProjectGetter:
|
||||
RbacEnforcer:
|
||||
SettingsGetter:
|
||||
UserGetter:
|
||||
github.com/argoproj/argo-cd/v3/util/db:
|
||||
interfaces:
|
||||
ArgoDB:
|
||||
github.com/argoproj/argo-cd/v3/util/git:
|
||||
interfaces:
|
||||
Client:
|
||||
github.com/argoproj/argo-cd/v3/util/helm:
|
||||
interfaces:
|
||||
Client:
|
||||
github.com/argoproj/argo-cd/v3/util/io:
|
||||
interfaces:
|
||||
TempPaths:
|
||||
github.com/argoproj/argo-cd/v3/util/notification/argocd:
|
||||
interfaces:
|
||||
Service:
|
||||
github.com/argoproj/argo-cd/v3/util/workloadidentity:
|
||||
interfaces:
|
||||
TokenProvider:
|
||||
# These mocks are not currently used, but they are part of the public API of this package.
|
||||
github.com/argoproj/argo-cd/v3/pkg/apiclient/session:
|
||||
interfaces:
|
||||
SessionServiceServer:
|
||||
SessionServiceClient:
|
||||
LiveStateCache: {}
|
||||
github.com/argoproj/argo-cd/v3/pkg/apiclient/cluster:
|
||||
interfaces:
|
||||
ClusterServiceServer:
|
||||
ClusterServiceServer: {}
|
||||
github.com/argoproj/argo-cd/v3/pkg/apiclient/session:
|
||||
interfaces:
|
||||
SessionServiceClient: {}
|
||||
SessionServiceServer: {}
|
||||
github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned/typed/application/v1alpha1:
|
||||
interfaces:
|
||||
AppProjectInterface:
|
||||
AppProjectInterface: {}
|
||||
github.com/argoproj/argo-cd/v3/reposerver/apiclient:
|
||||
interfaces:
|
||||
RepoServerService_GenerateManifestWithFilesClient: {}
|
||||
RepoServerServiceClient: {}
|
||||
github.com/argoproj/argo-cd/v3/server/application:
|
||||
interfaces:
|
||||
Broadcaster: {}
|
||||
github.com/argoproj/argo-cd/v3/server/extension:
|
||||
interfaces:
|
||||
ApplicationGetter: {}
|
||||
ExtensionMetricsRegistry: {}
|
||||
ProjectGetter: {}
|
||||
RbacEnforcer: {}
|
||||
SettingsGetter: {}
|
||||
UserGetter: {}
|
||||
github.com/argoproj/argo-cd/v3/util/db:
|
||||
interfaces:
|
||||
ArgoDB: {}
|
||||
github.com/argoproj/argo-cd/v3/util/git:
|
||||
interfaces:
|
||||
Client: {}
|
||||
github.com/argoproj/argo-cd/v3/util/helm:
|
||||
interfaces:
|
||||
Client: {}
|
||||
github.com/argoproj/argo-cd/v3/util/oci:
|
||||
interfaces:
|
||||
Client: {}
|
||||
github.com/argoproj/argo-cd/v3/util/io:
|
||||
interfaces:
|
||||
TempPaths: {}
|
||||
github.com/argoproj/argo-cd/v3/util/notification/argocd:
|
||||
interfaces:
|
||||
Service: {}
|
||||
github.com/argoproj/argo-cd/v3/util/workloadidentity:
|
||||
interfaces:
|
||||
TokenProvider: {}
|
||||
github.com/microsoft/azure-devops-go-api/azuredevops/v7/git:
|
||||
config:
|
||||
dir: applicationset/services/scm_provider/azure_devops/git/mocks
|
||||
interfaces:
|
||||
Client: {}
|
||||
|
||||
34
Dockerfile
34
Dockerfile
@@ -4,7 +4,9 @@ ARG BASE_IMAGE=docker.io/library/ubuntu:24.04@sha256:80dd3c3b9c6cecb9f1667e9290b
|
||||
# Initial stage which pulls prepares build dependencies and CLI tooling we need for our final image
|
||||
# Also used as the image in CI jobs so needs all dependencies
|
||||
####################################################################################################
|
||||
FROM docker.io/library/golang:1.24.1@sha256:c5adecdb7b3f8c5ca3c88648a861882849cc8b02fed68ece31e25de88ad13418 AS builder
|
||||
FROM docker.io/library/golang:1.25.5@sha256:6cc2338c038bc20f96ab32848da2b5c0641bb9bb5363f2c33e9b7c8838f9a208 AS builder
|
||||
|
||||
WORKDIR /tmp
|
||||
|
||||
RUN echo 'deb http://archive.debian.org/debian buster-backports main' >> /etc/apt/sources.list
|
||||
|
||||
@@ -23,8 +25,6 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
WORKDIR /tmp
|
||||
|
||||
COPY hack/install.sh hack/tool-versions.sh ./
|
||||
COPY hack/installers installers
|
||||
|
||||
@@ -40,8 +40,8 @@ LABEL org.opencontainers.image.source="https://github.com/argoproj/argo-cd"
|
||||
|
||||
USER root
|
||||
|
||||
ENV ARGOCD_USER_ID=999
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV ARGOCD_USER_ID=999 \
|
||||
DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN groupadd -g $ARGOCD_USER_ID argocd && \
|
||||
useradd -r -u $ARGOCD_USER_ID -g argocd argocd && \
|
||||
@@ -55,11 +55,13 @@ RUN groupadd -g $ARGOCD_USER_ID argocd && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
COPY hack/gpg-wrapper.sh /usr/local/bin/gpg-wrapper.sh
|
||||
COPY hack/git-verify-wrapper.sh /usr/local/bin/git-verify-wrapper.sh
|
||||
COPY hack/gpg-wrapper.sh \
|
||||
hack/git-verify-wrapper.sh \
|
||||
entrypoint.sh \
|
||||
/usr/local/bin/
|
||||
COPY --from=builder /usr/local/bin/helm /usr/local/bin/helm
|
||||
COPY --from=builder /usr/local/bin/kustomize /usr/local/bin/kustomize
|
||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
|
||||
# keep uid_entrypoint.sh for backward compatibility
|
||||
RUN ln -s /usr/local/bin/entrypoint.sh /usr/local/bin/uid_entrypoint.sh
|
||||
|
||||
@@ -101,7 +103,7 @@ RUN HOST_ARCH=$TARGETARCH NODE_ENV='production' NODE_ONLINE_ENV='online' NODE_OP
|
||||
####################################################################################################
|
||||
# Argo CD Build stage which performs the actual build of Argo CD binaries
|
||||
####################################################################################################
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.24.1@sha256:c5adecdb7b3f8c5ca3c88648a861882849cc8b02fed68ece31e25de88ad13418 AS argocd-build
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.25.5@sha256:6cc2338c038bc20f96ab32848da2b5c0641bb9bb5363f2c33e9b7c8838f9a208 AS argocd-build
|
||||
|
||||
WORKDIR /go/src/github.com/argoproj/argo-cd
|
||||
|
||||
@@ -111,13 +113,13 @@ RUN go mod download
|
||||
# Perform the build
|
||||
COPY . .
|
||||
COPY --from=argocd-ui /src/dist/app /go/src/github.com/argoproj/argo-cd/ui/dist/app
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG TARGETOS \
|
||||
TARGETARCH
|
||||
# These build args are optional; if not specified the defaults will be taken from the Makefile
|
||||
ARG GIT_TAG
|
||||
ARG BUILD_DATE
|
||||
ARG GIT_TREE_STATE
|
||||
ARG GIT_COMMIT
|
||||
ARG GIT_TAG \
|
||||
BUILD_DATE \
|
||||
GIT_TREE_STATE \
|
||||
GIT_COMMIT
|
||||
RUN GIT_COMMIT=$GIT_COMMIT \
|
||||
GIT_TREE_STATE=$GIT_TREE_STATE \
|
||||
GIT_TAG=$GIT_TAG \
|
||||
@@ -130,6 +132,7 @@ RUN GIT_COMMIT=$GIT_COMMIT \
|
||||
# Final image
|
||||
####################################################################################################
|
||||
FROM argocd-base
|
||||
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||
COPY --from=argocd-build /go/src/github.com/argoproj/argo-cd/dist/argocd* /usr/local/bin/
|
||||
|
||||
USER root
|
||||
@@ -144,4 +147,3 @@ RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-server && \
|
||||
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-commit-server
|
||||
|
||||
USER $ARGOCD_USER_ID
|
||||
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||
|
||||
62
Dockerfile.tilt
Normal file
62
Dockerfile.tilt
Normal file
@@ -0,0 +1,62 @@
|
||||
FROM docker.io/library/golang:1.24.1@sha256:c5adecdb7b3f8c5ca3c88648a861882849cc8b02fed68ece31e25de88ad13418
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN echo 'deb http://archive.debian.org/debian buster-backports main' >> /etc/apt/sources.list
|
||||
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
curl \
|
||||
openssh-server \
|
||||
nginx \
|
||||
unzip \
|
||||
fcgiwrap \
|
||||
git \
|
||||
git-lfs \
|
||||
make \
|
||||
wget \
|
||||
gcc \
|
||||
sudo \
|
||||
zip \
|
||||
tini \
|
||||
gpg \
|
||||
tzdata \
|
||||
connect-proxy
|
||||
|
||||
RUN go install github.com/go-delve/delve/cmd/dlv@latest
|
||||
|
||||
COPY hack/install.sh hack/tool-versions.sh ./
|
||||
COPY hack/installers installers
|
||||
|
||||
RUN ./install.sh helm && \
|
||||
INSTALL_PATH=/usr/local/bin ./install.sh kustomize
|
||||
|
||||
COPY hack/gpg-wrapper.sh \
|
||||
hack/git-verify-wrapper.sh \
|
||||
entrypoint.sh \
|
||||
/usr/local/bin/
|
||||
|
||||
# support for mounting configuration from a configmap
|
||||
WORKDIR /app/config/ssh
|
||||
RUN touch ssh_known_hosts && \
|
||||
ln -s /app/config/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts
|
||||
|
||||
WORKDIR /app/config
|
||||
RUN mkdir -p tls && \
|
||||
mkdir -p gpg/source && \
|
||||
mkdir -p gpg/keys
|
||||
|
||||
COPY .tilt-bin/argocd_linux /usr/local/bin/argocd
|
||||
|
||||
RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-server && \
|
||||
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-repo-server && \
|
||||
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-application-controller && \
|
||||
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-dex && \
|
||||
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-notifications && \
|
||||
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-applicationset-controller && \
|
||||
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-commit-server
|
||||
|
||||
# directory for Tilt restart file
|
||||
RUN mkdir -p /tilt
|
||||
|
||||
# overridden by Tiltfile
|
||||
ENTRYPOINT ["/usr/bin/tini", "-s", "--", "dlv", "exec", "--continue", "--accept-multiclient", "--headless", "--listen=:2345", "--api-version=2"]
|
||||
9
Dockerfile.ui.tilt
Normal file
9
Dockerfile.ui.tilt
Normal file
@@ -0,0 +1,9 @@
|
||||
FROM node:20
|
||||
|
||||
WORKDIR /app/ui
|
||||
|
||||
COPY ui /app/ui
|
||||
|
||||
RUN yarn install
|
||||
|
||||
ENTRYPOINT ["yarn", "start"]
|
||||
19
Makefile
19
Makefile
@@ -3,7 +3,16 @@ CURRENT_DIR=$(shell pwd)
|
||||
DIST_DIR=${CURRENT_DIR}/dist
|
||||
CLI_NAME=argocd
|
||||
BIN_NAME=argocd
|
||||
CGO_FLAG=0
|
||||
|
||||
UNAME_S:=$(shell uname)
|
||||
IS_DARWIN:=$(if $(filter Darwin, $(UNAME_S)),true,false)
|
||||
|
||||
# When using OSX/Darwin, you might need to enable CGO for local builds
|
||||
DEFAULT_CGO_FLAG:=0
|
||||
ifeq ($(IS_DARWIN),true)
|
||||
DEFAULT_CGO_FLAG:=1
|
||||
endif
|
||||
CGO_FLAG?=${DEFAULT_CGO_FLAG}
|
||||
|
||||
GEN_RESOURCES_CLI_NAME=argocd-resources-gen
|
||||
|
||||
@@ -147,7 +156,11 @@ PATH:=$(PATH):$(PWD)/hack
|
||||
DOCKER_PUSH?=false
|
||||
IMAGE_NAMESPACE?=
|
||||
# perform static compilation
|
||||
STATIC_BUILD?=true
|
||||
DEFAULT_STATIC_BUILD:=true
|
||||
ifeq ($(IS_DARWIN),true)
|
||||
DEFAULT_STATIC_BUILD:=false
|
||||
endif
|
||||
STATIC_BUILD?=${DEFAULT_STATIC_BUILD}
|
||||
# build development images
|
||||
DEV_IMAGE?=false
|
||||
ARGOCD_GPG_ENABLED?=true
|
||||
@@ -486,6 +499,7 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
|
||||
ARGOCD_APPLICATIONSET_CONTROLLER_ALLOWED_SCM_PROVIDERS=http://127.0.0.1:8341,http://127.0.0.1:8342,http://127.0.0.1:8343,http://127.0.0.1:8344 \
|
||||
ARGOCD_E2E_TEST=true \
|
||||
ARGOCD_HYDRATOR_ENABLED=true \
|
||||
ARGOCD_CLUSTER_CACHE_EVENTS_PROCESSING_INTERVAL=1ms \
|
||||
goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START}
|
||||
ls -lrt /tmp/coverage
|
||||
|
||||
@@ -584,6 +598,7 @@ install-test-tools-local:
|
||||
./hack/install.sh kustomize
|
||||
./hack/install.sh helm
|
||||
./hack/install.sh gotestsum
|
||||
./hack/install.sh oras
|
||||
|
||||
# Installs all tools required for running codegen (Linux packages)
|
||||
.PHONY: install-codegen-tools-local
|
||||
|
||||
1
Procfile
1
Procfile
@@ -8,6 +8,7 @@ commit-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go
|
||||
ui: sh -c 'cd ui && ${ARGOCD_E2E_YARN_CMD:-yarn} start'
|
||||
git-server: test/fixture/testrepos/start-git.sh
|
||||
helm-registry: test/fixture/testrepos/start-helm-registry.sh
|
||||
oci-registry: test/fixture/testrepos/start-authenticated-helm-registry.sh
|
||||
dev-mounter: [[ "$ARGOCD_E2E_TEST" != "true" ]] && go run hack/dev-mounter/main.go --configmap argocd-ssh-known-hosts-cm=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} --configmap argocd-tls-certs-cm=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} --configmap argocd-gpg-keys-cm=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source}
|
||||
applicationset-controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/applicationset-controller} FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-applicationset-controller $COMMAND --loglevel debug --metrics-addr localhost:12345 --probe-addr localhost:12346 --argocd-repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
|
||||
notification: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/notification} FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_BINARY_NAME=argocd-notifications $COMMAND --loglevel debug --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''} --self-service-notification-enabled=${ARGOCD_NOTIFICATION_CONTROLLER_SELF_SERVICE_NOTIFICATION_ENABLED:-'false'}"
|
||||
@@ -3,9 +3,9 @@ header:
|
||||
expiration-date: '2024-10-31T00:00:00.000Z' # One year from initial release.
|
||||
last-updated: '2023-10-27'
|
||||
last-reviewed: '2023-10-27'
|
||||
commit-hash: 74a367d10e7110209610ba3ec225539ebe5f7522
|
||||
commit-hash: 226a670fe6b3c6769ff6d18e6839298a58e4577d
|
||||
project-url: https://github.com/argoproj/argo-cd
|
||||
project-release: v2.14.0
|
||||
project-release: v3.1.0
|
||||
changelog: https://github.com/argoproj/argo-cd/releases
|
||||
license: https://github.com/argoproj/argo-cd/blob/master/LICENSE
|
||||
project-lifecycle:
|
||||
|
||||
282
Tiltfile
Normal file
282
Tiltfile
Normal file
@@ -0,0 +1,282 @@
|
||||
load('ext://restart_process', 'docker_build_with_restart')
|
||||
load('ext://uibutton', 'cmd_button', 'location')
|
||||
|
||||
# add ui button in web ui to run make codegen-local (top nav)
|
||||
cmd_button(
|
||||
'make codegen-local',
|
||||
argv=['sh', '-c', 'make codegen-local'],
|
||||
location=location.NAV,
|
||||
icon_name='terminal',
|
||||
text='make codegen-local',
|
||||
)
|
||||
|
||||
# add ui button in web ui to run make codegen-local (top nav)
|
||||
cmd_button(
|
||||
'make cli-local',
|
||||
argv=['sh', '-c', 'make cli-local'],
|
||||
location=location.NAV,
|
||||
icon_name='terminal',
|
||||
text='make cli-local',
|
||||
)
|
||||
|
||||
# detect cluster architecture for build
|
||||
cluster_version = decode_yaml(local('kubectl version -o yaml'))
|
||||
platform = cluster_version['serverVersion']['platform']
|
||||
arch = platform.split('/')[1]
|
||||
|
||||
# build the argocd binary on code changes
|
||||
code_deps = [
|
||||
'applicationset',
|
||||
'cmd',
|
||||
'cmpserver',
|
||||
'commitserver',
|
||||
'common',
|
||||
'controller',
|
||||
'notification-controller',
|
||||
'pkg',
|
||||
'reposerver',
|
||||
'server',
|
||||
'util',
|
||||
'go.mod',
|
||||
'go.sum',
|
||||
]
|
||||
local_resource(
|
||||
'build',
|
||||
'CGO_ENABLED=0 GOOS=linux GOARCH=' + arch + ' go build -gcflags="all=-N -l" -mod=readonly -o .tilt-bin/argocd_linux cmd/main.go',
|
||||
deps = code_deps,
|
||||
allow_parallel=True,
|
||||
)
|
||||
|
||||
# deploy the argocd manifests
|
||||
k8s_yaml(kustomize('manifests/dev-tilt'))
|
||||
|
||||
# build dev image
|
||||
docker_build_with_restart(
|
||||
'argocd',
|
||||
context='.',
|
||||
dockerfile='Dockerfile.tilt',
|
||||
entrypoint=[
|
||||
"/usr/bin/tini",
|
||||
"-s",
|
||||
"--",
|
||||
"dlv",
|
||||
"exec",
|
||||
"--continue",
|
||||
"--accept-multiclient",
|
||||
"--headless",
|
||||
"--listen=:2345",
|
||||
"--api-version=2"
|
||||
],
|
||||
platform=platform,
|
||||
live_update=[
|
||||
sync('.tilt-bin/argocd_linux_amd64', '/usr/local/bin/argocd'),
|
||||
],
|
||||
only=[
|
||||
'.tilt-bin',
|
||||
'hack',
|
||||
'entrypoint.sh',
|
||||
],
|
||||
restart_file='/tilt/.restart-proc'
|
||||
)
|
||||
|
||||
# build image for argocd-cli jobs
|
||||
docker_build(
|
||||
'argocd-job',
|
||||
context='.',
|
||||
dockerfile='Dockerfile.tilt',
|
||||
platform=platform,
|
||||
only=[
|
||||
'.tilt-bin',
|
||||
'hack',
|
||||
'entrypoint.sh',
|
||||
]
|
||||
)
|
||||
|
||||
# track argocd-server resources and port forward
|
||||
k8s_resource(
|
||||
workload='argocd-server',
|
||||
objects=[
|
||||
'argocd-server:serviceaccount',
|
||||
'argocd-server:role',
|
||||
'argocd-server:rolebinding',
|
||||
'argocd-cm:configmap',
|
||||
'argocd-cmd-params-cm:configmap',
|
||||
'argocd-gpg-keys-cm:configmap',
|
||||
'argocd-rbac-cm:configmap',
|
||||
'argocd-ssh-known-hosts-cm:configmap',
|
||||
'argocd-tls-certs-cm:configmap',
|
||||
'argocd-secret:secret',
|
||||
'argocd-server-network-policy:networkpolicy',
|
||||
'argocd-server:clusterrolebinding',
|
||||
'argocd-server:clusterrole',
|
||||
],
|
||||
port_forwards=[
|
||||
'8080:8080',
|
||||
'9345:2345',
|
||||
'8083:8083'
|
||||
],
|
||||
)
|
||||
|
||||
# track crds
|
||||
k8s_resource(
|
||||
new_name='cluster-resources',
|
||||
objects=[
|
||||
'applications.argoproj.io:customresourcedefinition',
|
||||
'applicationsets.argoproj.io:customresourcedefinition',
|
||||
'appprojects.argoproj.io:customresourcedefinition',
|
||||
'argocd:namespace'
|
||||
]
|
||||
)
|
||||
|
||||
# track argocd-repo-server resources and port forward
|
||||
k8s_resource(
|
||||
workload='argocd-repo-server',
|
||||
objects=[
|
||||
'argocd-repo-server:serviceaccount',
|
||||
'argocd-repo-server-network-policy:networkpolicy',
|
||||
],
|
||||
port_forwards=[
|
||||
'8081:8081',
|
||||
'9346:2345',
|
||||
'8084:8084'
|
||||
],
|
||||
)
|
||||
|
||||
# track argocd-redis resources and port forward
|
||||
k8s_resource(
|
||||
workload='argocd-redis',
|
||||
objects=[
|
||||
'argocd-redis:serviceaccount',
|
||||
'argocd-redis:role',
|
||||
'argocd-redis:rolebinding',
|
||||
'argocd-redis-network-policy:networkpolicy',
|
||||
],
|
||||
port_forwards=[
|
||||
'6379:6379',
|
||||
],
|
||||
)
|
||||
|
||||
# track argocd-applicationset-controller resources
|
||||
k8s_resource(
|
||||
workload='argocd-applicationset-controller',
|
||||
objects=[
|
||||
'argocd-applicationset-controller:serviceaccount',
|
||||
'argocd-applicationset-controller-network-policy:networkpolicy',
|
||||
'argocd-applicationset-controller:role',
|
||||
'argocd-applicationset-controller:rolebinding',
|
||||
'argocd-applicationset-controller:clusterrolebinding',
|
||||
'argocd-applicationset-controller:clusterrole',
|
||||
],
|
||||
port_forwards=[
|
||||
'9347:2345',
|
||||
'8085:8080',
|
||||
'7000:7000'
|
||||
],
|
||||
)
|
||||
|
||||
# track argocd-application-controller resources
|
||||
k8s_resource(
|
||||
workload='argocd-application-controller',
|
||||
objects=[
|
||||
'argocd-application-controller:serviceaccount',
|
||||
'argocd-application-controller-network-policy:networkpolicy',
|
||||
'argocd-application-controller:role',
|
||||
'argocd-application-controller:rolebinding',
|
||||
'argocd-application-controller:clusterrolebinding',
|
||||
'argocd-application-controller:clusterrole',
|
||||
],
|
||||
port_forwards=[
|
||||
'9348:2345',
|
||||
'8086:8082',
|
||||
],
|
||||
)
|
||||
|
||||
# track argocd-notifications-controller resources
|
||||
k8s_resource(
|
||||
workload='argocd-notifications-controller',
|
||||
objects=[
|
||||
'argocd-notifications-controller:serviceaccount',
|
||||
'argocd-notifications-controller-network-policy:networkpolicy',
|
||||
'argocd-notifications-controller:role',
|
||||
'argocd-notifications-controller:rolebinding',
|
||||
'argocd-notifications-cm:configmap',
|
||||
'argocd-notifications-secret:secret',
|
||||
],
|
||||
port_forwards=[
|
||||
'9349:2345',
|
||||
'8087:9001',
|
||||
],
|
||||
)
|
||||
|
||||
# track argocd-dex-server resources
|
||||
k8s_resource(
|
||||
workload='argocd-dex-server',
|
||||
objects=[
|
||||
'argocd-dex-server:serviceaccount',
|
||||
'argocd-dex-server-network-policy:networkpolicy',
|
||||
'argocd-dex-server:role',
|
||||
'argocd-dex-server:rolebinding',
|
||||
],
|
||||
)
|
||||
|
||||
# track argocd-commit-server resources
|
||||
k8s_resource(
|
||||
workload='argocd-commit-server',
|
||||
objects=[
|
||||
'argocd-commit-server:serviceaccount',
|
||||
'argocd-commit-server-network-policy:networkpolicy',
|
||||
],
|
||||
port_forwards=[
|
||||
'9350:2345',
|
||||
'8088:8087',
|
||||
'8089:8086',
|
||||
],
|
||||
)
|
||||
|
||||
# docker for ui
|
||||
docker_build(
|
||||
'argocd-ui',
|
||||
context='.',
|
||||
dockerfile='Dockerfile.ui.tilt',
|
||||
entrypoint=['sh', '-c', 'cd /app/ui && yarn start'],
|
||||
only=['ui'],
|
||||
live_update=[
|
||||
sync('ui', '/app/ui'),
|
||||
run('sh -c "cd /app/ui && yarn install"', trigger=['/app/ui/package.json', '/app/ui/yarn.lock']),
|
||||
],
|
||||
)
|
||||
|
||||
# track argocd-ui resources and port forward
|
||||
k8s_resource(
|
||||
workload='argocd-ui',
|
||||
port_forwards=[
|
||||
'4000:4000',
|
||||
],
|
||||
)
|
||||
|
||||
# linting
|
||||
local_resource(
|
||||
'lint',
|
||||
'make lint-local',
|
||||
deps = code_deps,
|
||||
allow_parallel=True,
|
||||
)
|
||||
|
||||
local_resource(
|
||||
'lint-ui',
|
||||
'make lint-ui-local',
|
||||
deps = [
|
||||
'ui',
|
||||
],
|
||||
allow_parallel=True,
|
||||
)
|
||||
|
||||
local_resource(
|
||||
'vendor',
|
||||
'go mod vendor',
|
||||
deps = [
|
||||
'go.mod',
|
||||
'go.sum',
|
||||
],
|
||||
)
|
||||
|
||||
19
USERS.md
19
USERS.md
@@ -30,6 +30,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Ant Group](https://www.antgroup.com/)
|
||||
1. [AppDirect](https://www.appdirect.com)
|
||||
1. [Arctiq Inc.](https://www.arctiq.ca)
|
||||
1. [Artemis Health by Nomi Health](https://www.artemishealth.com/)
|
||||
1. [Arturia](https://www.arturia.com)
|
||||
1. [ARZ Allgemeines Rechenzentrum GmbH](https://www.arz.at/)
|
||||
1. [Augury](https://www.augury.com/)
|
||||
@@ -45,6 +46,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Bedag Informatik AG](https://www.bedag.ch/)
|
||||
1. [Beleza Na Web](https://www.belezanaweb.com.br/)
|
||||
1. [Believable Bots](https://believablebots.io)
|
||||
1. [Bayer AG](https://bayer.com)
|
||||
1. [BigPanda](https://bigpanda.io)
|
||||
1. [BioBox Analytics](https://biobox.io)
|
||||
1. [BMW Group](https://www.bmwgroup.com/)
|
||||
@@ -67,6 +69,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Chainnodes](https://chainnodes.org)
|
||||
1. [Chargetrip](https://chargetrip.com)
|
||||
1. [Chime](https://www.chime.com)
|
||||
1. [Chronicle Labs](https://chroniclelabs.org)
|
||||
1. [Cisco ET&I](https://eti.cisco.com/)
|
||||
1. [Cloud Posse](https://www.cloudposse.com/)
|
||||
1. [Cloud Scale](https://cloudscaleinc.com/)
|
||||
@@ -92,12 +95,15 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Daydream](https://daydream.ing)
|
||||
1. [Deloitte](https://www.deloitte.com/)
|
||||
1. [Deutsche Telekom AG](https://telekom.com)
|
||||
1. [Deutsche Bank AG](https://www.deutsche-bank.de/)
|
||||
1. [Devopsi - Poland Software/DevOps Consulting](https://devopsi.pl/)
|
||||
1. [Devtron Labs](https://github.com/devtron-labs/devtron)
|
||||
1. [DigitalEd](https://www.digitaled.com)
|
||||
1. [DigitalOcean](https://www.digitalocean.com)
|
||||
1. [Divar](https://divar.ir)
|
||||
1. [Divistant](https://divistant.com)
|
||||
1. [Dott](https://ridedott.com)
|
||||
1. [Doubble](https://www.doubble.app)
|
||||
1. [Doximity](https://www.doximity.com/)
|
||||
1. [EDF Renewables](https://www.edf-re.com/)
|
||||
1. [edX](https://edx.org)
|
||||
@@ -109,6 +115,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Energisme](https://energisme.com/)
|
||||
1. [enigmo](https://enigmo.co.jp/)
|
||||
1. [Envoy](https://envoy.com/)
|
||||
1. [eSave](https://esave.es/)
|
||||
1. [Factorial](https://factorialhr.com/)
|
||||
1. [Farfetch](https://www.farfetch.com)
|
||||
1. [Faro](https://www.faro.com/)
|
||||
@@ -156,6 +163,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [IABAI](https://www.iab.ai)
|
||||
1. [IBM](https://www.ibm.com/)
|
||||
1. [Ibotta](https://home.ibotta.com)
|
||||
1. [Icelandair](https://www.icelandair.com)
|
||||
1. [IFS](https://www.ifs.com)
|
||||
1. [IITS-Consulting](https://iits-consulting.de)
|
||||
1. [IllumiDesk](https://www.illumidesk.com)
|
||||
@@ -259,6 +267,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Patreon](https://www.patreon.com/)
|
||||
1. [PayIt](https://payitgov.com/)
|
||||
1. [PayPay](https://paypay.ne.jp/)
|
||||
1. [Paystack](https://paystack.com/)
|
||||
1. [Peloton Interactive](https://www.onepeloton.com/)
|
||||
1. [Percona](https://percona.com/)
|
||||
1. [PGS](https://www.pgs.com)
|
||||
@@ -305,6 +314,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Saloodo! GmbH](https://www.saloodo.com)
|
||||
1. [Sap Labs](http://sap.com)
|
||||
1. [Sauce Labs](https://saucelabs.com/)
|
||||
1. [Schneider Electric](https://www.se.com)
|
||||
1. [Schwarz IT](https://jobs.schwarz/it-mission)
|
||||
1. [SCRM Lidl International Hub](https://scrm.lidl)
|
||||
1. [SEEK](https://seek.com.au)
|
||||
@@ -314,7 +324,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [SI Analytics](https://si-analytics.ai)
|
||||
1. [Sidewalk Entertainment](https://sidewalkplay.com/)
|
||||
1. [Skit](https://skit.ai/)
|
||||
1. [Skribble](https://skribble.com)
|
||||
1. [Skribble](https://skribble.com)
|
||||
1. [Skyscanner](https://www.skyscanner.net/)
|
||||
1. [Smart Pension](https://www.smartpension.co.uk/)
|
||||
1. [Smilee.io](https://smilee.io)
|
||||
@@ -335,11 +345,13 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Sumo Logic](https://sumologic.com/)
|
||||
1. [Sutpc](http://www.sutpc.com/)
|
||||
1. [Swiss Post](https://github.com/swisspost)
|
||||
1. [Swissblock Technologies](https://swissblock.net/)
|
||||
1. [Swisscom](https://www.swisscom.ch)
|
||||
1. [Swissquote](https://github.com/swissquote)
|
||||
1. [Syncier](https://syncier.com/)
|
||||
1. [Synergy](https://synergy.net.au)
|
||||
1. [Syself](https://syself.com)
|
||||
1. [T-ROC Global](https://trocglobal.com/)
|
||||
1. [TableCheck](https://tablecheck.com/)
|
||||
1. [Tailor Brands](https://www.tailorbrands.com)
|
||||
1. [Tamkeen Technologies](https://tamkeentech.sa/)
|
||||
@@ -381,6 +393,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Volvo Cars](https://www.volvocars.com/)
|
||||
1. [Voyager Digital](https://www.investvoyager.com/)
|
||||
1. [VSHN - The DevOps Company](https://vshn.ch/)
|
||||
1. [Wakacje.pl](https://www.wakacje.pl/)
|
||||
1. [Walkbase](https://www.walkbase.com/)
|
||||
1. [Webstores](https://www.webstores.nl)
|
||||
1. [Wehkamp](https://www.wehkamp.nl/)
|
||||
@@ -396,8 +409,8 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Yieldlab](https://www.yieldlab.de/)
|
||||
1. [Youverify](https://youverify.co/)
|
||||
1. [Yubo](https://www.yubo.live/)
|
||||
1. [Yuno](https://y.uno/)
|
||||
1. [ZDF](https://www.zdf.de/)
|
||||
1. [Zimpler](https://www.zimpler.com/)
|
||||
1. [ZipRecuiter](https://www.ziprecruiter.com/)
|
||||
1. [ZipRecruiter](https://www.ziprecruiter.com/)
|
||||
1. [ZOZO](https://corp.zozo.com/)
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/status"
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/utils"
|
||||
"github.com/argoproj/argo-cd/v3/common"
|
||||
applog "github.com/argoproj/argo-cd/v3/util/app/log"
|
||||
"github.com/argoproj/argo-cd/v3/util/db"
|
||||
|
||||
argov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
@@ -71,6 +72,7 @@ const (
|
||||
var defaultPreservedAnnotations = []string{
|
||||
NotifiedAnnotationKey,
|
||||
argov1alpha1.AnnotationKeyRefresh,
|
||||
argov1alpha1.AnnotationKeyHydrate,
|
||||
}
|
||||
|
||||
// ApplicationSetReconciler reconciles a ApplicationSet object
|
||||
@@ -91,6 +93,7 @@ type ApplicationSetReconciler struct {
|
||||
GlobalPreservedAnnotations []string
|
||||
GlobalPreservedLabels []string
|
||||
Metrics *metrics.ApplicationsetMetrics
|
||||
MaxResourcesStatusCount int
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=argoproj.io,resources=applicationsets,verbs=get;list;watch;create;update;patch;delete
|
||||
@@ -127,8 +130,8 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
|
||||
}()
|
||||
|
||||
// Do not attempt to further reconcile the ApplicationSet if it is being deleted.
|
||||
if applicationSetInfo.ObjectMeta.DeletionTimestamp != nil {
|
||||
appsetName := applicationSetInfo.ObjectMeta.Name
|
||||
if applicationSetInfo.DeletionTimestamp != nil {
|
||||
appsetName := applicationSetInfo.Name
|
||||
logCtx.Debugf("DeletionTimestamp is set on %s", appsetName)
|
||||
deleteAllowed := utils.DefaultPolicy(applicationSetInfo.Spec.SyncPolicy, r.Policy, r.EnablePolicyOverride).AllowDelete()
|
||||
if !deleteAllowed {
|
||||
@@ -210,16 +213,16 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
|
||||
appSyncMap := map[string]bool{}
|
||||
|
||||
if r.EnableProgressiveSyncs {
|
||||
if applicationSetInfo.Spec.Strategy == nil && len(applicationSetInfo.Status.ApplicationStatus) > 0 {
|
||||
// If appset used progressive sync but stopped, clean up the progressive sync application statuses
|
||||
if !isRollingSyncStrategy(&applicationSetInfo) && len(applicationSetInfo.Status.ApplicationStatus) > 0 {
|
||||
// If an appset was previously syncing with a `RollingSync` strategy but it has switched to the default strategy, clean up the progressive sync application statuses
|
||||
logCtx.Infof("Removing %v unnecessary AppStatus entries from ApplicationSet %v", len(applicationSetInfo.Status.ApplicationStatus), applicationSetInfo.Name)
|
||||
|
||||
err := r.setAppSetApplicationStatus(ctx, logCtx, &applicationSetInfo, []argov1alpha1.ApplicationSetApplicationStatus{})
|
||||
if err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("failed to clear previous AppSet application statuses for %v: %w", applicationSetInfo.Name, err)
|
||||
}
|
||||
} else if applicationSetInfo.Spec.Strategy != nil {
|
||||
// appset uses progressive sync
|
||||
} else if isRollingSyncStrategy(&applicationSetInfo) {
|
||||
// The appset uses progressive sync with `RollingSync` strategy
|
||||
for _, app := range currentApplications {
|
||||
appMap[app.Name] = app
|
||||
}
|
||||
@@ -229,6 +232,16 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
|
||||
return ctrl.Result{}, fmt.Errorf("failed to perform progressive sync reconciliation for application set: %w", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Progressive Sync is disabled, clear any existing applicationStatus to prevent stale data
|
||||
if len(applicationSetInfo.Status.ApplicationStatus) > 0 {
|
||||
logCtx.Infof("Progressive Sync disabled, removing %v AppStatus entries from ApplicationSet %v", len(applicationSetInfo.Status.ApplicationStatus), applicationSetInfo.Name)
|
||||
|
||||
err := r.setAppSetApplicationStatus(ctx, logCtx, &applicationSetInfo, []argov1alpha1.ApplicationSetApplicationStatus{})
|
||||
if err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("failed to clear AppSet application statuses when Progressive Sync is disabled for %v: %w", applicationSetInfo.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var validApps []argov1alpha1.Application
|
||||
@@ -314,7 +327,7 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
|
||||
|
||||
if applicationSetInfo.RefreshRequired() {
|
||||
delete(applicationSetInfo.Annotations, common.AnnotationApplicationSetRefresh)
|
||||
err := r.Client.Update(ctx, &applicationSetInfo)
|
||||
err := r.Update(ctx, &applicationSetInfo)
|
||||
if err != nil {
|
||||
logCtx.Warnf("error occurred while updating ApplicationSet: %v", err)
|
||||
_ = r.setApplicationSetStatusCondition(ctx,
|
||||
@@ -488,7 +501,7 @@ func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Con
|
||||
namesSet[app.Name] = true
|
||||
|
||||
appProject := &argov1alpha1.AppProject{}
|
||||
err := r.Client.Get(ctx, types.NamespacedName{Name: app.Spec.Project, Namespace: r.ArgoCDNamespace}, appProject)
|
||||
err := r.Get(ctx, types.NamespacedName{Name: app.Spec.Project, Namespace: r.ArgoCDNamespace}, appProject)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
errorsByIndex[i] = fmt.Errorf("application references project %s which does not exist", app.Spec.Project)
|
||||
@@ -553,7 +566,7 @@ func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager, enableProg
|
||||
}
|
||||
|
||||
appOwnsHandler := getApplicationOwnsHandler(enableProgressiveSyncs)
|
||||
appSetOwnsHandler := getApplicationSetOwnsHandler()
|
||||
appSetOwnsHandler := getApplicationSetOwnsHandler(enableProgressiveSyncs)
|
||||
|
||||
return ctrl.NewControllerManagedBy(mgr).WithOptions(controller.Options{
|
||||
MaxConcurrentReconciles: maxConcurrentReconciliations,
|
||||
@@ -563,8 +576,9 @@ func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager, enableProg
|
||||
Watches(
|
||||
&corev1.Secret{},
|
||||
&clusterSecretEventHandler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: log.WithField("type", "createSecretEventHandler"),
|
||||
Client: mgr.GetClient(),
|
||||
Log: log.WithField("type", "createSecretEventHandler"),
|
||||
ApplicationSetNamespaces: r.ApplicationSetNamespaces,
|
||||
}).
|
||||
Complete(r)
|
||||
}
|
||||
@@ -577,7 +591,7 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
|
||||
var firstError error
|
||||
// Creates or updates the application in appList
|
||||
for _, generatedApp := range desiredApplications {
|
||||
appLog := logCtx.WithFields(log.Fields{"app": generatedApp.QualifiedName()})
|
||||
appLog := logCtx.WithFields(applog.GetAppLogFields(&generatedApp))
|
||||
|
||||
// Normalize to avoid fighting with the application controller.
|
||||
generatedApp.Spec = *argoutil.NormalizeApplicationSpec(&generatedApp.Spec)
|
||||
@@ -624,7 +638,7 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
|
||||
preservedAnnotations = append(preservedAnnotations, defaultPreservedAnnotations...)
|
||||
|
||||
for _, key := range preservedAnnotations {
|
||||
if state, exists := found.ObjectMeta.Annotations[key]; exists {
|
||||
if state, exists := found.Annotations[key]; exists {
|
||||
if generatedApp.Annotations == nil {
|
||||
generatedApp.Annotations = map[string]string{}
|
||||
}
|
||||
@@ -633,7 +647,7 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
|
||||
}
|
||||
|
||||
for _, key := range preservedLabels {
|
||||
if state, exists := found.ObjectMeta.Labels[key]; exists {
|
||||
if state, exists := found.Labels[key]; exists {
|
||||
if generatedApp.Labels == nil {
|
||||
generatedApp.Labels = map[string]string{}
|
||||
}
|
||||
@@ -643,7 +657,7 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
|
||||
|
||||
// Preserve post-delete finalizers:
|
||||
// https://github.com/argoproj/argo-cd/issues/17181
|
||||
for _, finalizer := range found.ObjectMeta.Finalizers {
|
||||
for _, finalizer := range found.Finalizers {
|
||||
if strings.HasPrefix(finalizer, argov1alpha1.PostDeleteFinalizerName) {
|
||||
if generatedApp.Finalizers == nil {
|
||||
generatedApp.Finalizers = []string{}
|
||||
@@ -652,10 +666,10 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
|
||||
}
|
||||
}
|
||||
|
||||
found.ObjectMeta.Annotations = generatedApp.Annotations
|
||||
found.Annotations = generatedApp.Annotations
|
||||
|
||||
found.ObjectMeta.Finalizers = generatedApp.Finalizers
|
||||
found.ObjectMeta.Labels = generatedApp.Labels
|
||||
found.Finalizers = generatedApp.Finalizers
|
||||
found.Labels = generatedApp.Labels
|
||||
|
||||
return controllerutil.SetControllerReference(&applicationSet, found, r.Scheme)
|
||||
})
|
||||
@@ -709,7 +723,7 @@ func (r *ApplicationSetReconciler) createInCluster(ctx context.Context, logCtx *
|
||||
|
||||
func (r *ApplicationSetReconciler) getCurrentApplications(ctx context.Context, applicationSet argov1alpha1.ApplicationSet) ([]argov1alpha1.Application, error) {
|
||||
var current argov1alpha1.ApplicationList
|
||||
err := r.Client.List(ctx, ¤t, client.MatchingFields{".metadata.controller": applicationSet.Name}, client.InNamespace(applicationSet.Namespace))
|
||||
err := r.List(ctx, ¤t, client.MatchingFields{".metadata.controller": applicationSet.Name}, client.InNamespace(applicationSet.Namespace))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving applications: %w", err)
|
||||
}
|
||||
@@ -740,7 +754,7 @@ func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, logCtx *
|
||||
// Delete apps that are not in m[string]bool
|
||||
var firstError error
|
||||
for _, app := range current {
|
||||
logCtx = logCtx.WithField("app", app.QualifiedName())
|
||||
logCtx = logCtx.WithFields(applog.GetAppLogFields(&app))
|
||||
_, exists := m[app.Name]
|
||||
|
||||
if !exists {
|
||||
@@ -754,7 +768,7 @@ func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, logCtx *
|
||||
continue
|
||||
}
|
||||
|
||||
err = r.Client.Delete(ctx, &app)
|
||||
err = r.Delete(ctx, &app)
|
||||
if err != nil {
|
||||
logCtx.WithError(err).Error("failed to delete Application")
|
||||
if firstError != nil {
|
||||
@@ -823,7 +837,7 @@ func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx conte
|
||||
if log.IsLevelEnabled(log.DebugLevel) {
|
||||
utils.LogPatch(appLog, patch, updated)
|
||||
}
|
||||
if err := r.Client.Patch(ctx, updated, patch); err != nil {
|
||||
if err := r.Patch(ctx, updated, patch); err != nil {
|
||||
return fmt.Errorf("error updating finalizers: %w", err)
|
||||
}
|
||||
// Application must have updated list of finalizers
|
||||
@@ -845,7 +859,7 @@ func (r *ApplicationSetReconciler) removeOwnerReferencesOnDeleteAppSet(ctx conte
|
||||
|
||||
for _, app := range applications {
|
||||
app.SetOwnerReferences([]metav1.OwnerReference{})
|
||||
err := r.Client.Update(ctx, &app)
|
||||
err := r.Update(ctx, &app)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating application: %w", err)
|
||||
}
|
||||
@@ -1000,8 +1014,14 @@ func appSyncEnabledForNextStep(appset *argov1alpha1.ApplicationSet, app argov1al
|
||||
return true
|
||||
}
|
||||
|
||||
func isRollingSyncStrategy(appset *argov1alpha1.ApplicationSet) bool {
|
||||
// It's only RollingSync if the type specifically sets it
|
||||
return appset.Spec.Strategy != nil && appset.Spec.Strategy.Type == "RollingSync" && appset.Spec.Strategy.RollingSync != nil
|
||||
}
|
||||
|
||||
func progressiveSyncsRollingSyncStrategyEnabled(appset *argov1alpha1.ApplicationSet) bool {
|
||||
return appset.Spec.Strategy != nil && appset.Spec.Strategy.RollingSync != nil && appset.Spec.Strategy.Type == "RollingSync" && len(appset.Spec.Strategy.RollingSync.Steps) > 0
|
||||
// ProgressiveSync is enabled if the strategy is set to `RollingSync` + steps slice is not empty
|
||||
return isRollingSyncStrategy(appset) && len(appset.Spec.Strategy.RollingSync.Steps) > 0
|
||||
}
|
||||
|
||||
func isApplicationHealthy(app argov1alpha1.Application) bool {
|
||||
@@ -1303,6 +1323,11 @@ func (r *ApplicationSetReconciler) updateResourcesStatus(ctx context.Context, lo
|
||||
sort.Slice(statuses, func(i, j int) bool {
|
||||
return statuses[i].Name < statuses[j].Name
|
||||
})
|
||||
|
||||
if r.MaxResourcesStatusCount > 0 && len(statuses) > r.MaxResourcesStatusCount {
|
||||
logCtx.Warnf("Truncating ApplicationSet %s resource status from %d to max allowed %d entries", appset.Name, len(statuses), r.MaxResourcesStatusCount)
|
||||
statuses = statuses[:r.MaxResourcesStatusCount]
|
||||
}
|
||||
appset.Status.Resources = statuses
|
||||
// DefaultRetry will retry 5 times with a backoff factor of 1, jitter of 0.1 and a duration of 10ms
|
||||
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
@@ -1398,7 +1423,7 @@ func (r *ApplicationSetReconciler) syncValidApplications(logCtx *log.Entry, appl
|
||||
pruneEnabled := false
|
||||
|
||||
// ensure that Applications generated with RollingSync do not have an automated sync policy, since the AppSet controller will handle triggering the sync operation instead
|
||||
if validApps[i].Spec.SyncPolicy != nil && validApps[i].Spec.SyncPolicy.Automated != nil {
|
||||
if validApps[i].Spec.SyncPolicy != nil && validApps[i].Spec.SyncPolicy.IsAutomatedSyncEnabled() {
|
||||
pruneEnabled = validApps[i].Spec.SyncPolicy.Automated.Prune
|
||||
validApps[i].Spec.SyncPolicy.Automated = nil
|
||||
}
|
||||
@@ -1434,6 +1459,10 @@ func syncApplication(application argov1alpha1.Application, prune bool) argov1alp
|
||||
},
|
||||
},
|
||||
Sync: &argov1alpha1.SyncOperation{},
|
||||
// Set a retry limit of 5, aligning with the default in Argo CD's appcontroller auto-sync behavior.
|
||||
// This provides consistency for retry behavior across controllers.
|
||||
// See: https://github.com/argoproj/argo-cd/blob/af9ebac0bb35dc16eb034c1cefaf7c92d1029927/controller/appcontroller.go#L2126
|
||||
Retry: argov1alpha1.RetryStrategy{Limit: 5},
|
||||
}
|
||||
|
||||
if application.Spec.SyncPolicy != nil {
|
||||
@@ -1456,23 +1485,23 @@ func getApplicationOwnsHandler(enableProgressiveSyncs bool) predicate.Funcs {
|
||||
// if we are the owner and there is a create event, we most likely created it and do not need to
|
||||
// re-reconcile
|
||||
if log.IsLevelEnabled(log.DebugLevel) {
|
||||
var appName string
|
||||
logFields := log.Fields{"app": ""}
|
||||
app, isApp := e.Object.(*argov1alpha1.Application)
|
||||
if isApp {
|
||||
appName = app.QualifiedName()
|
||||
logFields = applog.GetAppLogFields(app)
|
||||
}
|
||||
log.WithField("app", appName).Debugln("received create event from owning an application")
|
||||
log.WithFields(logFields).Debugln("received create event from owning an application")
|
||||
}
|
||||
return false
|
||||
},
|
||||
DeleteFunc: func(e event.DeleteEvent) bool {
|
||||
if log.IsLevelEnabled(log.DebugLevel) {
|
||||
var appName string
|
||||
logFields := log.Fields{"app": ""}
|
||||
app, isApp := e.Object.(*argov1alpha1.Application)
|
||||
if isApp {
|
||||
appName = app.QualifiedName()
|
||||
logFields = applog.GetAppLogFields(app)
|
||||
}
|
||||
log.WithField("app", appName).Debugln("received delete event from owning an application")
|
||||
log.WithFields(logFields).Debugln("received delete event from owning an application")
|
||||
}
|
||||
return true
|
||||
},
|
||||
@@ -1481,7 +1510,7 @@ func getApplicationOwnsHandler(enableProgressiveSyncs bool) predicate.Funcs {
|
||||
if !isApp {
|
||||
return false
|
||||
}
|
||||
logCtx := log.WithField("app", appOld.QualifiedName())
|
||||
logCtx := log.WithFields(applog.GetAppLogFields(appOld))
|
||||
logCtx.Debugln("received update event from owning an application")
|
||||
appNew, isApp := e.ObjectNew.(*argov1alpha1.Application)
|
||||
if !isApp {
|
||||
@@ -1493,12 +1522,12 @@ func getApplicationOwnsHandler(enableProgressiveSyncs bool) predicate.Funcs {
|
||||
},
|
||||
GenericFunc: func(e event.GenericEvent) bool {
|
||||
if log.IsLevelEnabled(log.DebugLevel) {
|
||||
var appName string
|
||||
logFields := log.Fields{}
|
||||
app, isApp := e.Object.(*argov1alpha1.Application)
|
||||
if isApp {
|
||||
appName = app.QualifiedName()
|
||||
logFields = applog.GetAppLogFields(app)
|
||||
}
|
||||
log.WithField("app", appName).Debugln("received generic event from owning an application")
|
||||
log.WithFields(logFields).Debugln("received generic event from owning an application")
|
||||
}
|
||||
return true
|
||||
},
|
||||
@@ -1521,9 +1550,9 @@ func shouldRequeueForApplication(appOld *argov1alpha1.Application, appNew *argov
|
||||
// https://pkg.go.dev/reflect#DeepEqual
|
||||
// ApplicationDestination has an unexported field so we can just use the == for comparison
|
||||
if !cmp.Equal(appOld.Spec, appNew.Spec, cmpopts.EquateEmpty(), cmpopts.EquateComparable(argov1alpha1.ApplicationDestination{})) ||
|
||||
!cmp.Equal(appOld.ObjectMeta.GetAnnotations(), appNew.ObjectMeta.GetAnnotations(), cmpopts.EquateEmpty()) ||
|
||||
!cmp.Equal(appOld.ObjectMeta.GetLabels(), appNew.ObjectMeta.GetLabels(), cmpopts.EquateEmpty()) ||
|
||||
!cmp.Equal(appOld.ObjectMeta.GetFinalizers(), appNew.ObjectMeta.GetFinalizers(), cmpopts.EquateEmpty()) {
|
||||
!cmp.Equal(appOld.GetAnnotations(), appNew.GetAnnotations(), cmpopts.EquateEmpty()) ||
|
||||
!cmp.Equal(appOld.GetLabels(), appNew.GetLabels(), cmpopts.EquateEmpty()) ||
|
||||
!cmp.Equal(appOld.GetFinalizers(), appNew.GetFinalizers(), cmpopts.EquateEmpty()) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1544,7 +1573,7 @@ func shouldRequeueForApplication(appOld *argov1alpha1.Application, appNew *argov
|
||||
return false
|
||||
}
|
||||
|
||||
func getApplicationSetOwnsHandler() predicate.Funcs {
|
||||
func getApplicationSetOwnsHandler(enableProgressiveSyncs bool) predicate.Funcs {
|
||||
return predicate.Funcs{
|
||||
CreateFunc: func(e event.CreateEvent) bool {
|
||||
appSet, isApp := e.Object.(*argov1alpha1.ApplicationSet)
|
||||
@@ -1573,7 +1602,7 @@ func getApplicationSetOwnsHandler() predicate.Funcs {
|
||||
if !isAppSet {
|
||||
return false
|
||||
}
|
||||
requeue := shouldRequeueForApplicationSet(appSetOld, appSetNew)
|
||||
requeue := shouldRequeueForApplicationSet(appSetOld, appSetNew, enableProgressiveSyncs)
|
||||
log.WithField("applicationset", appSetNew.QualifiedName()).
|
||||
WithField("requeue", requeue).Debugln("received update event")
|
||||
return requeue
|
||||
@@ -1591,24 +1620,33 @@ func getApplicationSetOwnsHandler() predicate.Funcs {
|
||||
}
|
||||
|
||||
// shouldRequeueForApplicationSet determines when we need to requeue an applicationset
|
||||
func shouldRequeueForApplicationSet(appSetOld, appSetNew *argov1alpha1.ApplicationSet) bool {
|
||||
func shouldRequeueForApplicationSet(appSetOld, appSetNew *argov1alpha1.ApplicationSet, enableProgressiveSyncs bool) bool {
|
||||
if appSetOld == nil || appSetNew == nil {
|
||||
return false
|
||||
}
|
||||
// only compare the applicationset spec, annotations, labels and finalizers, specifically avoiding
|
||||
|
||||
// Requeue if any ApplicationStatus.Status changed for Progressive sync strategy
|
||||
if enableProgressiveSyncs {
|
||||
if !cmp.Equal(appSetOld.Status.ApplicationStatus, appSetNew.Status.ApplicationStatus, cmpopts.EquateEmpty()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// only compare the applicationset spec, annotations, labels and finalizers, deletionTimestamp, specifically avoiding
|
||||
// the status field. status is owned by the applicationset controller,
|
||||
// and we do not need to requeue when it does bookkeeping
|
||||
// NB: the ApplicationDestination comes from the ApplicationSpec being embedded
|
||||
// in the ApplicationSetTemplate from the generators
|
||||
if !cmp.Equal(appSetOld.Spec, appSetNew.Spec, cmpopts.EquateEmpty(), cmpopts.EquateComparable(argov1alpha1.ApplicationDestination{})) ||
|
||||
!cmp.Equal(appSetOld.ObjectMeta.GetLabels(), appSetNew.ObjectMeta.GetLabels(), cmpopts.EquateEmpty()) ||
|
||||
!cmp.Equal(appSetOld.ObjectMeta.GetFinalizers(), appSetNew.ObjectMeta.GetFinalizers(), cmpopts.EquateEmpty()) {
|
||||
!cmp.Equal(appSetOld.GetLabels(), appSetNew.GetLabels(), cmpopts.EquateEmpty()) ||
|
||||
!cmp.Equal(appSetOld.GetFinalizers(), appSetNew.GetFinalizers(), cmpopts.EquateEmpty()) ||
|
||||
!cmp.Equal(appSetOld.DeletionTimestamp, appSetNew.DeletionTimestamp, cmpopts.EquateEmpty()) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Requeue only when the refresh annotation is newly added to the ApplicationSet.
|
||||
// Changes to other annotations made simultaneously might be missed, but such cases are rare.
|
||||
if !cmp.Equal(appSetOld.ObjectMeta.GetAnnotations(), appSetNew.ObjectMeta.GetAnnotations(), cmpopts.EquateEmpty()) {
|
||||
if !cmp.Equal(appSetOld.GetAnnotations(), appSetNew.GetAnnotations(), cmpopts.EquateEmpty()) {
|
||||
_, oldHasRefreshAnnotation := appSetOld.Annotations[common.AnnotationApplicationSetRefresh]
|
||||
_, newHasRefreshAnnotation := appSetNew.Annotations[common.AnnotationApplicationSetRefresh]
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/utils"
|
||||
"github.com/argoproj/argo-cd/v3/common"
|
||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
@@ -22,8 +23,9 @@ import (
|
||||
// requeue any related ApplicationSets.
|
||||
type clusterSecretEventHandler struct {
|
||||
// handler.EnqueueRequestForOwner
|
||||
Log log.FieldLogger
|
||||
Client client.Client
|
||||
Log log.FieldLogger
|
||||
Client client.Client
|
||||
ApplicationSetNamespaces []string
|
||||
}
|
||||
|
||||
func (h *clusterSecretEventHandler) Create(ctx context.Context, e event.CreateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
|
||||
@@ -68,6 +70,10 @@ func (h *clusterSecretEventHandler) queueRelatedAppGenerators(ctx context.Contex
|
||||
|
||||
h.Log.WithField("count", len(appSetList.Items)).Info("listed ApplicationSets")
|
||||
for _, appSet := range appSetList.Items {
|
||||
if !utils.IsNamespaceAllowed(h.ApplicationSetNamespaces, appSet.GetNamespace()) {
|
||||
// Ignore it as not part of the allowed list of namespaces in which to watch Appsets
|
||||
continue
|
||||
}
|
||||
foundClusterGenerator := false
|
||||
for _, generator := range appSet.Spec.Generators {
|
||||
if generator.Clusters != nil {
|
||||
|
||||
@@ -20,6 +20,15 @@ import (
|
||||
argov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
|
||||
type mockAddRateLimitingInterface struct {
|
||||
addedItems []reconcile.Request
|
||||
}
|
||||
|
||||
// Add checks the type, and adds it to the internal list of received additions
|
||||
func (obj *mockAddRateLimitingInterface) Add(item reconcile.Request) {
|
||||
obj.addedItems = append(obj.addedItems, item)
|
||||
}
|
||||
|
||||
func TestClusterEventHandler(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
err := argov1alpha1.AddToScheme(scheme)
|
||||
@@ -128,7 +137,7 @@ func TestClusterEventHandler(t *testing.T) {
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-app-set",
|
||||
Namespace: "another-namespace",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: argov1alpha1.ApplicationSetSpec{
|
||||
Generators: []argov1alpha1.ApplicationSetGenerator{
|
||||
@@ -162,9 +171,37 @@ func TestClusterEventHandler(t *testing.T) {
|
||||
},
|
||||
},
|
||||
expectedRequests: []reconcile.Request{
|
||||
{NamespacedName: types.NamespacedName{Namespace: "another-namespace", Name: "my-app-set"}},
|
||||
{NamespacedName: types.NamespacedName{Namespace: "argocd", Name: "my-app-set"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "cluster generators in other namespaces should not match",
|
||||
items: []argov1alpha1.ApplicationSet{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-app-set",
|
||||
Namespace: "my-namespace-not-allowed",
|
||||
},
|
||||
Spec: argov1alpha1.ApplicationSetSpec{
|
||||
Generators: []argov1alpha1.ApplicationSetGenerator{
|
||||
{
|
||||
Clusters: &argov1alpha1.ClusterGenerator{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
secret: corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "argocd",
|
||||
Name: "my-secret",
|
||||
Labels: map[string]string{
|
||||
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedRequests: []reconcile.Request{},
|
||||
},
|
||||
{
|
||||
name: "non-argo cd secret should not match",
|
||||
items: []argov1alpha1.ApplicationSet{
|
||||
@@ -543,8 +580,9 @@ func TestClusterEventHandler(t *testing.T) {
|
||||
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithLists(&appSetList).Build()
|
||||
|
||||
handler := &clusterSecretEventHandler{
|
||||
Client: fakeClient,
|
||||
Log: log.WithField("type", "createSecretEventHandler"),
|
||||
Client: fakeClient,
|
||||
Log: log.WithField("type", "createSecretEventHandler"),
|
||||
ApplicationSetNamespaces: []string{"argocd"},
|
||||
}
|
||||
|
||||
mockAddRateLimitingInterface := mockAddRateLimitingInterface{}
|
||||
@@ -556,15 +594,6 @@ func TestClusterEventHandler(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Add checks the type, and adds it to the internal list of received additions
|
||||
func (obj *mockAddRateLimitingInterface) Add(item reconcile.Request) {
|
||||
obj.addedItems = append(obj.addedItems, item)
|
||||
}
|
||||
|
||||
type mockAddRateLimitingInterface struct {
|
||||
addedItems []reconcile.Request
|
||||
}
|
||||
|
||||
func TestNestedGeneratorHasClusterGenerator_NestedClusterGenerator(t *testing.T) {
|
||||
nested := argov1alpha1.ApplicationSetNestedGenerator{
|
||||
Clusters: &argov1alpha1.ClusterGenerator{},
|
||||
|
||||
@@ -56,7 +56,7 @@ func TestRequeueAfter(t *testing.T) {
|
||||
},
|
||||
}
|
||||
fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, duckType)
|
||||
scmConfig := generators.NewSCMConfig("", []string{""}, true, nil, true)
|
||||
scmConfig := generators.NewSCMConfig("", []string{""}, true, true, nil, true)
|
||||
terminalGenerators := map[string]generators.Generator{
|
||||
"List": generators.NewListGenerator(),
|
||||
"Clusters": generators.NewClusterGenerator(ctx, k8sClient, appClientset, "argocd"),
|
||||
@@ -129,7 +129,7 @@ func TestRequeueAfter(t *testing.T) {
|
||||
}},
|
||||
},
|
||||
},
|
||||
}, ""}, want: generators.DefaultRequeueAfterSeconds, wantErr: assert.NoError},
|
||||
}, ""}, want: generators.DefaultRequeueAfter, wantErr: assert.NoError},
|
||||
{name: "ClusterMatrixNested", args: args{&argov1alpha1.ApplicationSet{
|
||||
Spec: argov1alpha1.ApplicationSetSpec{
|
||||
Generators: []argov1alpha1.ApplicationSetGenerator{
|
||||
@@ -144,7 +144,7 @@ func TestRequeueAfter(t *testing.T) {
|
||||
}},
|
||||
},
|
||||
},
|
||||
}, ""}, want: generators.DefaultRequeueAfterSeconds, wantErr: assert.NoError},
|
||||
}, ""}, want: generators.DefaultRequeueAfter, wantErr: assert.NoError},
|
||||
{name: "ListGenerator", args: args{appset: &argov1alpha1.ApplicationSet{
|
||||
Spec: argov1alpha1.ApplicationSetSpec{
|
||||
Generators: []argov1alpha1.ApplicationSetGenerator{{List: &argov1alpha1.ListGenerator{}}},
|
||||
@@ -154,7 +154,7 @@ func TestRequeueAfter(t *testing.T) {
|
||||
Spec: argov1alpha1.ApplicationSetSpec{
|
||||
Generators: []argov1alpha1.ApplicationSetGenerator{{ClusterDecisionResource: &argov1alpha1.DuckTypeGenerator{}}},
|
||||
},
|
||||
}}, want: generators.DefaultRequeueAfterSeconds, wantErr: assert.NoError},
|
||||
}}, want: generators.DefaultRequeueAfter, wantErr: assert.NoError},
|
||||
{name: "OverrideRequeueDuck", args: args{
|
||||
appset: &argov1alpha1.ApplicationSet{
|
||||
Spec: argov1alpha1.ApplicationSetSpec{
|
||||
|
||||
@@ -27,7 +27,7 @@ func Test_ApplyTemplatePatch(t *testing.T) {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-cluster-guestbook",
|
||||
Namespace: "namespace",
|
||||
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
|
||||
Finalizers: []string{appv1.ResourcesFinalizerName},
|
||||
},
|
||||
Spec: appv1.ApplicationSpec{
|
||||
Project: "default",
|
||||
@@ -72,7 +72,7 @@ func Test_ApplyTemplatePatch(t *testing.T) {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-cluster-guestbook",
|
||||
Namespace: "namespace",
|
||||
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
|
||||
Finalizers: []string{appv1.ResourcesFinalizerName},
|
||||
Annotations: map[string]string{
|
||||
"annotation-some-key": "annotation-some-value",
|
||||
},
|
||||
@@ -112,7 +112,7 @@ func Test_ApplyTemplatePatch(t *testing.T) {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-cluster-guestbook",
|
||||
Namespace: "namespace",
|
||||
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
|
||||
Finalizers: []string{appv1.ResourcesFinalizerName},
|
||||
},
|
||||
Spec: appv1.ApplicationSpec{
|
||||
Project: "default",
|
||||
@@ -148,7 +148,7 @@ spec:
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-cluster-guestbook",
|
||||
Namespace: "namespace",
|
||||
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
|
||||
Finalizers: []string{appv1.ResourcesFinalizerName},
|
||||
Annotations: map[string]string{
|
||||
"annotation-some-key": "annotation-some-value",
|
||||
},
|
||||
|
||||
@@ -341,10 +341,10 @@ func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) {
|
||||
renderer,
|
||||
nil,
|
||||
)
|
||||
assert.EqualValues(t, cases.expectedApp[0].ObjectMeta.Name, gotApp[0].ObjectMeta.Name)
|
||||
assert.EqualValues(t, cases.expectedApp[0].Spec.Source.TargetRevision, gotApp[0].Spec.Source.TargetRevision)
|
||||
assert.EqualValues(t, cases.expectedApp[0].Spec.Destination.Namespace, gotApp[0].Spec.Destination.Namespace)
|
||||
assert.True(t, maps.Equal(cases.expectedApp[0].ObjectMeta.Labels, gotApp[0].ObjectMeta.Labels))
|
||||
assert.Equal(t, cases.expectedApp[0].Name, gotApp[0].Name)
|
||||
assert.Equal(t, cases.expectedApp[0].Spec.Source.TargetRevision, gotApp[0].Spec.Source.TargetRevision)
|
||||
assert.Equal(t, cases.expectedApp[0].Spec.Destination.Namespace, gotApp[0].Spec.Destination.Namespace)
|
||||
assert.True(t, maps.Equal(cases.expectedApp[0].Labels, gotApp[0].Labels))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ spec:
|
||||
app: guestbook-ui
|
||||
spec:
|
||||
containers:
|
||||
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
|
||||
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
|
||||
name: guestbook-ui
|
||||
ports:
|
||||
- containerPort: 80
|
||||
|
||||
@@ -14,7 +14,7 @@ spec:
|
||||
app: guestbook-ui
|
||||
spec:
|
||||
containers:
|
||||
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
|
||||
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
|
||||
name: guestbook-ui
|
||||
ports:
|
||||
- containerPort: 80
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: gcr.io/heptio-images/ks-guestbook-demo
|
||||
repository: quay.io/argoprojlabs/argocd-e2e-container
|
||||
tag: 0.1
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ spec:
|
||||
app: guestbook-ui
|
||||
spec:
|
||||
containers:
|
||||
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
|
||||
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
|
||||
name: guestbook-ui
|
||||
ports:
|
||||
- containerPort: 80
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: ApplicationSet
|
||||
metadata:
|
||||
name: guestbook
|
||||
spec:
|
||||
generators:
|
||||
- git:
|
||||
repoURL: https://github.com/argoproj/argo-cd.git
|
||||
revision: HEAD
|
||||
files:
|
||||
- path: "applicationset/examples/git-generator-files-discovery/cluster-config/**/config.json"
|
||||
- path: "applicationset/examples/git-generator-files-discovery/cluster-config/*/dev/config.json"
|
||||
exclude: true
|
||||
template:
|
||||
metadata:
|
||||
name: '{{cluster.name}}-guestbook'
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/argoproj/argo-cd.git
|
||||
targetRevision: HEAD
|
||||
path: "applicationset/examples/git-generator-files-discovery/apps/guestbook"
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
#server: '{{cluster.address}}'
|
||||
namespace: guestbook
|
||||
@@ -0,0 +1,27 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: ApplicationSet
|
||||
metadata:
|
||||
name: guestbook
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- git:
|
||||
repoURL: https://github.com/argoproj/argo-cd.git
|
||||
revision: HEAD
|
||||
files:
|
||||
- path: "applicationset/examples/git-generator-files-discovery/cluster-config/**/config.json"
|
||||
- path: "applicationset/examples/git-generator-files-discovery/cluster-config/*/dev/config.json"
|
||||
exclude: true
|
||||
template:
|
||||
metadata:
|
||||
name: '{{.cluster.name}}-guestbook'
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/argoproj/argo-cd.git
|
||||
targetRevision: HEAD
|
||||
path: "applicationset/examples/git-generator-files-discovery/apps/guestbook"
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: guestbook
|
||||
@@ -14,7 +14,7 @@ spec:
|
||||
app: guestbook-ui
|
||||
spec:
|
||||
containers:
|
||||
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
|
||||
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
|
||||
name: guestbook-ui
|
||||
ports:
|
||||
- containerPort: 80
|
||||
|
||||
@@ -14,7 +14,7 @@ spec:
|
||||
app: guestbook-ui
|
||||
spec:
|
||||
containers:
|
||||
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
|
||||
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
|
||||
name: guestbook-ui
|
||||
ports:
|
||||
- containerPort: 80
|
||||
|
||||
@@ -14,7 +14,7 @@ spec:
|
||||
app: guestbook-ui
|
||||
spec:
|
||||
containers:
|
||||
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
|
||||
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
|
||||
name: guestbook-ui
|
||||
ports:
|
||||
- containerPort: 80
|
||||
|
||||
@@ -14,7 +14,7 @@ spec:
|
||||
app: guestbook-ui
|
||||
spec:
|
||||
containers:
|
||||
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
|
||||
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
|
||||
name: guestbook-ui
|
||||
ports:
|
||||
- containerPort: 80
|
||||
|
||||
@@ -7,8 +7,6 @@ import (
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/util/settings"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
@@ -27,21 +25,17 @@ type ClusterGenerator struct {
|
||||
ctx context.Context
|
||||
clientset kubernetes.Interface
|
||||
// namespace is the Argo CD namespace
|
||||
namespace string
|
||||
settingsManager *settings.SettingsManager
|
||||
namespace string
|
||||
}
|
||||
|
||||
var render = &utils.Render{}
|
||||
|
||||
func NewClusterGenerator(ctx context.Context, c client.Client, clientset kubernetes.Interface, namespace string) Generator {
|
||||
settingsManager := settings.NewSettingsManager(ctx, clientset, namespace)
|
||||
|
||||
g := &ClusterGenerator{
|
||||
Client: c,
|
||||
ctx: ctx,
|
||||
clientset: clientset,
|
||||
namespace: namespace,
|
||||
settingsManager: settingsManager,
|
||||
Client: c,
|
||||
ctx: ctx,
|
||||
clientset: clientset,
|
||||
namespace: namespace,
|
||||
}
|
||||
return g
|
||||
}
|
||||
@@ -59,11 +53,11 @@ func (g *ClusterGenerator) GetTemplate(appSetGenerator *argoappsetv1alpha1.Appli
|
||||
func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator, appSet *argoappsetv1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) {
|
||||
logCtx := log.WithField("applicationset", appSet.GetName()).WithField("namespace", appSet.GetNamespace())
|
||||
if appSetGenerator == nil {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
return nil, ErrEmptyAppSetGenerator
|
||||
}
|
||||
|
||||
if appSetGenerator.Clusters == nil {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
return nil, ErrEmptyAppSetGenerator
|
||||
}
|
||||
|
||||
// Do not include the local cluster in the cluster parameters IF there is a non-empty selector
|
||||
@@ -139,20 +133,20 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
|
||||
if appSet.Spec.GoTemplate {
|
||||
meta := map[string]any{}
|
||||
|
||||
if len(cluster.ObjectMeta.Annotations) > 0 {
|
||||
meta["annotations"] = cluster.ObjectMeta.Annotations
|
||||
if len(cluster.Annotations) > 0 {
|
||||
meta["annotations"] = cluster.Annotations
|
||||
}
|
||||
if len(cluster.ObjectMeta.Labels) > 0 {
|
||||
meta["labels"] = cluster.ObjectMeta.Labels
|
||||
if len(cluster.Labels) > 0 {
|
||||
meta["labels"] = cluster.Labels
|
||||
}
|
||||
|
||||
params["metadata"] = meta
|
||||
} else {
|
||||
for key, value := range cluster.ObjectMeta.Annotations {
|
||||
for key, value := range cluster.Annotations {
|
||||
params["metadata.annotations."+key] = value
|
||||
}
|
||||
|
||||
for key, value := range cluster.ObjectMeta.Labels {
|
||||
for key, value := range cluster.Labels {
|
||||
params["metadata.labels."+key] = value
|
||||
}
|
||||
}
|
||||
@@ -188,7 +182,7 @@ func (g *ClusterGenerator) getSecretsByClusterName(log *log.Entry, appSetGenerat
|
||||
return nil, fmt.Errorf("error converting label selector: %w", err)
|
||||
}
|
||||
|
||||
if err := g.Client.List(context.Background(), clusterSecretList, client.MatchingLabelsSelector{Selector: secretSelector}); err != nil {
|
||||
if err := g.List(context.Background(), clusterSecretList, client.MatchingLabelsSelector{Selector: secretSelector}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("clusters matching labels: %d", len(clusterSecretList.Items))
|
||||
|
||||
@@ -10,9 +10,8 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/util/settings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
@@ -26,22 +25,18 @@ var _ Generator = (*DuckTypeGenerator)(nil)
|
||||
|
||||
// DuckTypeGenerator generates Applications for some or all clusters registered with ArgoCD.
|
||||
type DuckTypeGenerator struct {
|
||||
ctx context.Context
|
||||
dynClient dynamic.Interface
|
||||
clientset kubernetes.Interface
|
||||
namespace string // namespace is the Argo CD namespace
|
||||
settingsManager *settings.SettingsManager
|
||||
ctx context.Context
|
||||
dynClient dynamic.Interface
|
||||
clientset kubernetes.Interface
|
||||
namespace string // namespace is the Argo CD namespace
|
||||
}
|
||||
|
||||
func NewDuckTypeGenerator(ctx context.Context, dynClient dynamic.Interface, clientset kubernetes.Interface, namespace string) Generator {
|
||||
settingsManager := settings.NewSettingsManager(ctx, clientset, namespace)
|
||||
|
||||
g := &DuckTypeGenerator{
|
||||
ctx: ctx,
|
||||
dynClient: dynClient,
|
||||
clientset: clientset,
|
||||
namespace: namespace,
|
||||
settingsManager: settingsManager,
|
||||
ctx: ctx,
|
||||
dynClient: dynClient,
|
||||
clientset: clientset,
|
||||
namespace: namespace,
|
||||
}
|
||||
return g
|
||||
}
|
||||
@@ -62,12 +57,12 @@ func (g *DuckTypeGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Appl
|
||||
|
||||
func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) {
|
||||
if appSetGenerator == nil {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
return nil, ErrEmptyAppSetGenerator
|
||||
}
|
||||
|
||||
// Not likely to happen
|
||||
if appSetGenerator.ClusterDecisionResource == nil {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
return nil, ErrEmptyAppSetGenerator
|
||||
}
|
||||
|
||||
// ListCluster from Argo CD's util/db package will include the local cluster in the list of clusters
|
||||
@@ -97,13 +92,13 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
|
||||
// Validate the fields
|
||||
if kind == "" || versionIdx < 1 {
|
||||
log.Warningf("kind=%v, resourceName=%v, versionIdx=%v", kind, resourceName, versionIdx)
|
||||
return nil, errors.New("There is a problem with the apiVersion, kind or resourceName provided")
|
||||
return nil, errors.New("there is a problem with the apiVersion, kind or resourceName provided")
|
||||
}
|
||||
|
||||
if (resourceName == "" && labelSelector.MatchLabels == nil && labelSelector.MatchExpressions == nil) ||
|
||||
(resourceName != "" && (labelSelector.MatchExpressions != nil || labelSelector.MatchLabels != nil)) {
|
||||
log.Warningf("You must choose either resourceName=%v, labelSelector.matchLabels=%v or labelSelect.matchExpressions=%v", resourceName, labelSelector.MatchLabels, labelSelector.MatchExpressions)
|
||||
return nil, errors.New("There is a problem with the definition of the ClusterDecisionResource generator")
|
||||
return nil, errors.New("there is a problem with the definition of the ClusterDecisionResource generator")
|
||||
}
|
||||
|
||||
// Split up the apiVersion
|
||||
@@ -136,18 +131,51 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
|
||||
|
||||
// Override the duck type in the status of the resource
|
||||
statusListKey := "clusters"
|
||||
|
||||
matchKey := cm.Data["matchKey"]
|
||||
|
||||
if cm.Data["statusListKey"] != "" {
|
||||
statusListKey = cm.Data["statusListKey"]
|
||||
}
|
||||
|
||||
matchKey := cm.Data["matchKey"]
|
||||
if matchKey == "" {
|
||||
log.WithField("matchKey", matchKey).Warning("matchKey not found in " + cm.Name)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
clusterDecisions := buildClusterDecisions(duckResources, statusListKey)
|
||||
if len(clusterDecisions) == 0 {
|
||||
log.Warningf("clusterDecisionResource status.%s missing", statusListKey)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
res := []map[string]any{}
|
||||
for _, clusterDecision := range clusterDecisions {
|
||||
cluster := findCluster(clustersFromArgoCD, clusterDecision, matchKey, statusListKey)
|
||||
// if no cluster is found, move to the next cluster
|
||||
if cluster == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// generated instance of cluster params
|
||||
params := map[string]any{
|
||||
"name": cluster.Name,
|
||||
"server": cluster.Server,
|
||||
}
|
||||
|
||||
for key, value := range clusterDecision.(map[string]any) {
|
||||
params[key] = value.(string)
|
||||
}
|
||||
|
||||
for key, value := range appSetGenerator.ClusterDecisionResource.Values {
|
||||
collectParams(appSet, params, key, value)
|
||||
}
|
||||
|
||||
res = append(res, params)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func buildClusterDecisions(duckResources *unstructured.UnstructuredList, statusListKey string) []any {
|
||||
clusterDecisions := []any{}
|
||||
|
||||
// Build the decision slice
|
||||
@@ -164,60 +192,38 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
|
||||
clusterDecisions = append(clusterDecisions, duckResource.Object["status"].(map[string]any)[statusListKey].([]any)...)
|
||||
}
|
||||
log.Infof("Number of decisions found: %v", len(clusterDecisions))
|
||||
|
||||
if len(clusterDecisions) == 0 {
|
||||
log.Warningf("clusterDecisionResource status.%s missing", statusListKey)
|
||||
return nil, nil
|
||||
}
|
||||
for _, cluster := range clusterDecisions {
|
||||
// generated instance of cluster params
|
||||
params := map[string]any{}
|
||||
|
||||
log.Infof("cluster: %v", cluster)
|
||||
matchValue := cluster.(map[string]any)[matchKey]
|
||||
if matchValue == nil || matchValue.(string) == "" {
|
||||
log.Warningf("matchKey=%v not found in \"%v\" list: %v\n", matchKey, statusListKey, cluster.(map[string]any))
|
||||
continue
|
||||
}
|
||||
|
||||
strMatchValue := matchValue.(string)
|
||||
log.WithField(matchKey, strMatchValue).Debug("validate against ArgoCD")
|
||||
|
||||
found := false
|
||||
|
||||
for _, argoCluster := range clustersFromArgoCD {
|
||||
if argoCluster.Name == strMatchValue {
|
||||
log.WithField(matchKey, argoCluster.Name).Info("matched cluster in ArgoCD")
|
||||
params["name"] = argoCluster.Name
|
||||
params["server"] = argoCluster.Server
|
||||
|
||||
found = true
|
||||
break // Stop looking
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
log.WithField(matchKey, strMatchValue).Warning("unmatched cluster in ArgoCD")
|
||||
continue
|
||||
}
|
||||
|
||||
for key, value := range cluster.(map[string]any) {
|
||||
params[key] = value.(string)
|
||||
}
|
||||
|
||||
for key, value := range appSetGenerator.ClusterDecisionResource.Values {
|
||||
if appSet.Spec.GoTemplate {
|
||||
if params["values"] == nil {
|
||||
params["values"] = map[string]string{}
|
||||
}
|
||||
params["values"].(map[string]string)[key] = value
|
||||
} else {
|
||||
params["values."+key] = value
|
||||
}
|
||||
}
|
||||
|
||||
res = append(res, params)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
return clusterDecisions
|
||||
}
|
||||
|
||||
func findCluster(clustersFromArgoCD []utils.ClusterSpecifier, cluster any, matchKey string, statusListKey string) *utils.ClusterSpecifier {
|
||||
log.Infof("cluster: %v", cluster)
|
||||
matchValue := cluster.(map[string]any)[matchKey]
|
||||
if matchValue == nil || matchValue.(string) == "" {
|
||||
log.Warningf("matchKey=%v not found in \"%v\" list: %v\n", matchKey, statusListKey, cluster.(map[string]any))
|
||||
return nil // no match
|
||||
}
|
||||
|
||||
strMatchValue := matchValue.(string)
|
||||
log.WithField(matchKey, strMatchValue).Debug("validate against ArgoCD")
|
||||
|
||||
for _, argoCluster := range clustersFromArgoCD {
|
||||
if argoCluster.Name == strMatchValue {
|
||||
log.WithField(matchKey, argoCluster.Name).Info("matched cluster in ArgoCD")
|
||||
return &argoCluster
|
||||
}
|
||||
}
|
||||
|
||||
log.WithField(matchKey, strMatchValue).Warning("unmatched cluster in ArgoCD")
|
||||
return nil
|
||||
}
|
||||
|
||||
func collectParams(appSet *argoprojiov1alpha1.ApplicationSet, params map[string]any, key string, value string) {
|
||||
if appSet.Spec.GoTemplate {
|
||||
if params["values"] == nil {
|
||||
params["values"] = map[string]string{}
|
||||
}
|
||||
params["values"].(map[string]string)[key] = value
|
||||
} else {
|
||||
params["values."+key] = value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
|
||||
resource: duckType,
|
||||
values: nil,
|
||||
expected: []map[string]any{},
|
||||
expectedError: errors.New("There is a problem with the definition of the ClusterDecisionResource generator"),
|
||||
expectedError: errors.New("there is a problem with the definition of the ClusterDecisionResource generator"),
|
||||
},
|
||||
/*** This does not work with the FAKE runtime client, fieldSelectors are broken.
|
||||
{
|
||||
@@ -270,7 +270,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
|
||||
resource: duckType,
|
||||
values: nil,
|
||||
expected: nil,
|
||||
expectedError: errors.New("There is a problem with the definition of the ClusterDecisionResource generator"),
|
||||
expectedError: errors.New("there is a problem with the definition of the ClusterDecisionResource generator"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -455,7 +455,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
|
||||
resource: duckType,
|
||||
values: nil,
|
||||
expected: []map[string]any{},
|
||||
expectedError: errors.New("There is a problem with the definition of the ClusterDecisionResource generator"),
|
||||
expectedError: errors.New("there is a problem with the definition of the ClusterDecisionResource generator"),
|
||||
},
|
||||
/*** This does not work with the FAKE runtime client, fieldSelectors are broken.
|
||||
{
|
||||
@@ -566,7 +566,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
|
||||
resource: duckType,
|
||||
values: nil,
|
||||
expected: nil,
|
||||
expectedError: errors.New("There is a problem with the definition of the ClusterDecisionResource generator"),
|
||||
expectedError: errors.New("there is a problem with the definition of the ClusterDecisionResource generator"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -28,22 +28,27 @@ type GitGenerator struct {
|
||||
namespace string
|
||||
}
|
||||
|
||||
func NewGitGenerator(repos services.Repos, namespace string) Generator {
|
||||
// NewGitGenerator creates a new instance of Git Generator
|
||||
func NewGitGenerator(repos services.Repos, controllerNamespace string) Generator {
|
||||
g := &GitGenerator{
|
||||
repos: repos,
|
||||
namespace: namespace,
|
||||
namespace: controllerNamespace,
|
||||
}
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
// GetTemplate returns the ApplicationSetTemplate associated with the Git generator
|
||||
// from the provided ApplicationSetGenerator. This template defines how each
|
||||
// generated Argo CD Application should be rendered.
|
||||
func (g *GitGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
|
||||
return &appSetGenerator.Git.Template
|
||||
}
|
||||
|
||||
// GetRequeueAfter returns the duration after which the Git generator should be
|
||||
// requeued for reconciliation. If RequeueAfterSeconds is set in the generator spec,
|
||||
// it uses that value. Otherwise, it falls back to a default requeue interval (3 minutes).
|
||||
func (g *GitGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
|
||||
// Return a requeue default of 3 minutes, if no default is specified.
|
||||
|
||||
if appSetGenerator.Git.RequeueAfterSeconds != nil {
|
||||
return time.Duration(*appSetGenerator.Git.RequeueAfterSeconds) * time.Second
|
||||
}
|
||||
@@ -51,13 +56,15 @@ func (g *GitGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Appli
|
||||
return getDefaultRequeueAfter()
|
||||
}
|
||||
|
||||
// GenerateParams generates a list of parameter maps for the ApplicationSet by evaluating the Git generator's configuration.
|
||||
// It supports both directory-based and file-based Git generators.
|
||||
func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]any, error) {
|
||||
if appSetGenerator == nil {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
return nil, ErrEmptyAppSetGenerator
|
||||
}
|
||||
|
||||
if appSetGenerator.Git == nil {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
return nil, ErrEmptyAppSetGenerator
|
||||
}
|
||||
|
||||
noRevisionCache := appSet.RefreshRequired()
|
||||
@@ -67,14 +74,15 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
|
||||
// When the project field is templated, the contents of the git repo are required to run the git generator and get the templated value,
|
||||
// but git generator cannot be called without verifying the commit signature.
|
||||
// In this case, we skip the signature verification.
|
||||
// If the project is templated, we skip the commit verification
|
||||
if !strings.Contains(appSet.Spec.Template.Spec.Project, "{{") {
|
||||
project := appSet.Spec.Template.Spec.Project
|
||||
appProject := &argoprojiov1alpha1.AppProject{}
|
||||
namespace := g.namespace
|
||||
if namespace == "" {
|
||||
namespace = appSet.Namespace
|
||||
controllerNamespace := g.namespace
|
||||
if controllerNamespace == "" {
|
||||
controllerNamespace = appSet.Namespace
|
||||
}
|
||||
if err := client.Get(context.TODO(), types.NamespacedName{Name: project, Namespace: namespace}, appProject); err != nil {
|
||||
if err := client.Get(context.TODO(), types.NamespacedName{Name: project, Namespace: controllerNamespace}, appProject); err != nil {
|
||||
return nil, fmt.Errorf("error getting project %s: %w", project, err)
|
||||
}
|
||||
// we need to verify the signature on the Git revision if GPG is enabled
|
||||
@@ -93,7 +101,7 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
|
||||
case len(appSetGenerator.Git.Files) != 0:
|
||||
res, err = g.generateParamsForGitFiles(appSetGenerator, noRevisionCache, verifyCommit, appSet.Spec.GoTemplate, project, appSet.Spec.GoTemplateOptions)
|
||||
default:
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
return nil, ErrEmptyAppSetGenerator
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error generating params from git: %w", err)
|
||||
@@ -102,8 +110,10 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// generateParamsForGitDirectories generates parameters for an ApplicationSet using a directory-based Git generator.
|
||||
// It fetches all directories from the given Git repository and revision, optionally using a revision cache and verifying commits.
|
||||
// It then filters the directories based on the generator's configuration and renders parameters for the resulting applications
|
||||
func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit, useGoTemplate bool, project string, goTemplateOptions []string) ([]map[string]any, error) {
|
||||
// Directories, not files
|
||||
allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, project, noRevisionCache, verifyCommit)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting directories from repo: %w", err)
|
||||
@@ -127,41 +137,88 @@ func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoproj
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// generateParamsForGitFiles generates parameters for an ApplicationSet using a file-based Git generator.
|
||||
// It retrieves and processes specified files from the Git repository, supporting both YAML and JSON formats,
|
||||
// and returns a list of parameter maps extracted from the content.
|
||||
func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit, useGoTemplate bool, project string, goTemplateOptions []string) ([]map[string]any, error) {
|
||||
// Get all files that match the requested path string, removing duplicates
|
||||
allFiles := make(map[string][]byte)
|
||||
for _, requestedPath := range appSetGenerator.Git.Files {
|
||||
files, err := g.repos.GetFiles(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, project, requestedPath.Path, noRevisionCache, verifyCommit)
|
||||
// fileContentMap maps absolute file paths to their byte content
|
||||
fileContentMap := make(map[string][]byte)
|
||||
var includePatterns []string
|
||||
var excludePatterns []string
|
||||
|
||||
for _, req := range appSetGenerator.Git.Files {
|
||||
if req.Exclude {
|
||||
excludePatterns = append(excludePatterns, req.Path)
|
||||
} else {
|
||||
includePatterns = append(includePatterns, req.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch all files from include patterns
|
||||
for _, includePattern := range includePatterns {
|
||||
retrievedFiles, err := g.repos.GetFiles(
|
||||
context.TODO(),
|
||||
appSetGenerator.Git.RepoURL,
|
||||
appSetGenerator.Git.Revision,
|
||||
project,
|
||||
includePattern,
|
||||
noRevisionCache,
|
||||
verifyCommit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for filePath, content := range files {
|
||||
allFiles[filePath] = content
|
||||
for absPath, content := range retrievedFiles {
|
||||
fileContentMap[absPath] = content
|
||||
}
|
||||
}
|
||||
|
||||
// Extract the unduplicated map into a list, and sort by path to ensure a deterministic
|
||||
// processing order in the subsequent step
|
||||
allPaths := []string{}
|
||||
for path := range allFiles {
|
||||
allPaths = append(allPaths, path)
|
||||
}
|
||||
sort.Strings(allPaths)
|
||||
|
||||
// Generate params from each path, and return
|
||||
res := []map[string]any{}
|
||||
for _, path := range allPaths {
|
||||
// A JSON / YAML file path can contain multiple sets of parameters (ie it is an array)
|
||||
paramsArray, err := g.generateParamsFromGitFile(path, allFiles[path], appSetGenerator.Git.Values, useGoTemplate, goTemplateOptions, appSetGenerator.Git.PathParamPrefix)
|
||||
// Now remove files matching any exclude pattern
|
||||
for _, excludePattern := range excludePatterns {
|
||||
matchingFiles, err := g.repos.GetFiles(
|
||||
context.TODO(),
|
||||
appSetGenerator.Git.RepoURL,
|
||||
appSetGenerator.Git.Revision,
|
||||
project,
|
||||
excludePattern,
|
||||
noRevisionCache,
|
||||
verifyCommit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to process file '%s': %w", path, err)
|
||||
return nil, err
|
||||
}
|
||||
for absPath := range matchingFiles {
|
||||
// if the file doesn't exist already and you try to delete it from the map
|
||||
// the operation is a no-op. It’s safe and doesn't return an error or panic.
|
||||
// Hence, we can simply try to delete the file from the path without checking
|
||||
// if that file already exists in the map.
|
||||
delete(fileContentMap, absPath)
|
||||
}
|
||||
|
||||
res = append(res, paramsArray...)
|
||||
}
|
||||
return res, nil
|
||||
|
||||
// Get a sorted list of file paths to ensure deterministic processing order
|
||||
var filePaths []string
|
||||
for path := range fileContentMap {
|
||||
filePaths = append(filePaths, path)
|
||||
}
|
||||
sort.Strings(filePaths)
|
||||
|
||||
var allParams []map[string]any
|
||||
for _, filePath := range filePaths {
|
||||
// A JSON / YAML file path can contain multiple sets of parameters (ie it is an array)
|
||||
paramsFromFileArray, err := g.generateParamsFromGitFile(filePath, fileContentMap[filePath], appSetGenerator.Git.Values, useGoTemplate, goTemplateOptions, appSetGenerator.Git.PathParamPrefix)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to process file '%s': %w", filePath, err)
|
||||
}
|
||||
allParams = append(allParams, paramsFromFileArray...)
|
||||
}
|
||||
|
||||
return allParams, nil
|
||||
}
|
||||
|
||||
// generateParamsFromGitFile parses the content of a Git-tracked file and generates a slice of parameter maps.
|
||||
// The file can contain a single YAML/JSON object or an array of such objects. Depending on the useGoTemplate flag,
|
||||
// it either preserves structure for Go templating or flattens the objects for use as plain key-value parameters.
|
||||
func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []byte, values map[string]string, useGoTemplate bool, goTemplateOptions []string, pathParamPrefix string) ([]map[string]any, error) {
|
||||
objectsFound := []map[string]any{}
|
||||
|
||||
@@ -221,7 +278,7 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
|
||||
params[pathParamName+".basenameNormalized"] = utils.SanitizeName(path.Base(params[pathParamName].(string)))
|
||||
params[pathParamName+".filenameNormalized"] = utils.SanitizeName(path.Base(params[pathParamName+".filename"].(string)))
|
||||
for k, v := range strings.Split(params[pathParamName].(string), "/") {
|
||||
if len(v) > 0 {
|
||||
if v != "" {
|
||||
params[pathParamName+"["+strconv.Itoa(k)+"]"] = v
|
||||
}
|
||||
}
|
||||
@@ -238,8 +295,10 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// filterApps filters the list of all application paths based on inclusion and exclusion rules
|
||||
// defined in GitDirectoryGeneratorItems. Each item can either include or exclude matching paths.
|
||||
func (g *GitGenerator) filterApps(directories []argoprojiov1alpha1.GitDirectoryGeneratorItem, allPaths []string) []string {
|
||||
res := []string{}
|
||||
var res []string
|
||||
for _, appPath := range allPaths {
|
||||
appInclude := false
|
||||
appExclude := false
|
||||
@@ -266,6 +325,9 @@ func (g *GitGenerator) filterApps(directories []argoprojiov1alpha1.GitDirectoryG
|
||||
return res
|
||||
}
|
||||
|
||||
// generateParamsFromApps generates a list of parameter maps based on the given app paths.
|
||||
// Each app path is converted into a parameter object with path metadata (basename, segments, etc.).
|
||||
// It supports both Go templates and flat key-value parameters.
|
||||
func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool, goTemplateOptions []string) ([]map[string]any, error) {
|
||||
res := make([]map[string]any, len(requestedApps))
|
||||
for i, a := range requestedApps {
|
||||
@@ -291,7 +353,7 @@ func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGene
|
||||
params[pathParamName+".basename"] = path.Base(a)
|
||||
params[pathParamName+".basenameNormalized"] = utils.SanitizeName(path.Base(a))
|
||||
for k, v := range strings.Split(params[pathParamName].(string), "/") {
|
||||
if len(v) > 0 {
|
||||
if v != "" {
|
||||
params[pathParamName+"["+strconv.Itoa(k)+"]"] = v
|
||||
}
|
||||
}
|
||||
@@ -308,6 +370,7 @@ func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGene
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// resolveProjectName resolves a project name whether templated or not
|
||||
func resolveProjectName(project string) string {
|
||||
if strings.Contains(project, "{{") {
|
||||
return ""
|
||||
|
||||
@@ -2,7 +2,6 @@ package generators
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -181,6 +180,8 @@ foo:
|
||||
}
|
||||
|
||||
func TestGitGenerateParamsFromDirectories(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
directories []v1alpha1.GitDirectoryGeneratorItem
|
||||
@@ -363,6 +364,8 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
directories []v1alpha1.GitDirectoryGeneratorItem
|
||||
@@ -664,6 +667,8 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGitGenerateParamsFromFiles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
// files is the list of paths/globs to match
|
||||
@@ -1014,7 +1019,928 @@ cluster:
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
|
||||
|
||||
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, client)
|
||||
fmt.Println(got, err)
|
||||
|
||||
if testCaseCopy.expectedError != nil {
|
||||
require.EqualError(t, err, testCaseCopy.expectedError.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.ElementsMatch(t, testCaseCopy.expected, got)
|
||||
}
|
||||
|
||||
argoCDServiceMock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGitGeneratorParamsFromFilesWithExcludeOptionWithNewGlobbing tests the params values generated by git file generator
|
||||
// when exclude option is set to true. It gives the result files based on new globbing pattern - doublestar package
|
||||
func TestGitGeneratorParamsFromFilesWithExcludeOptionWithNewGlobbing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
// files is the list of paths/globs to match
|
||||
files []v1alpha1.GitFileGeneratorItem
|
||||
// includePattern contains a list of file patterns that needs to be included
|
||||
includePattern []string
|
||||
// excludePattern contains a list of file patterns that needs to be excluded
|
||||
excludePattern []string
|
||||
// includeFiles is a map with key as absolute path to file and value as the content in bytes that satisfies the includePattern
|
||||
includeFiles map[string][]byte
|
||||
// excludeFiles is a map with key as absolute path to file and value as the content in bytes that satisfies the excludePattern
|
||||
// This means all the files should be excluded
|
||||
excludeFiles map[string][]byte
|
||||
// noMatchFiles contains all the files that neither match include pattern nor exclude pattern
|
||||
// Instead of keeping those files in the excludeFiles map, it is better to keep those files separately
|
||||
// in a separate field like 'noMatchFiles' to avoid confusion.
|
||||
noMatchFiles map[string][]byte
|
||||
// if repoPathsError is non-nil, the call to GetPaths(...) will return this error value
|
||||
repoPathsError error
|
||||
values map[string]string
|
||||
expected []map[string]any
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "filter files according to file-path with exclude",
|
||||
files: []v1alpha1.GitFileGeneratorItem{
|
||||
{
|
||||
Path: "**/config.json",
|
||||
},
|
||||
{
|
||||
Path: "p1/**/config.json",
|
||||
Exclude: true,
|
||||
},
|
||||
},
|
||||
includePattern: []string{"**/config.json"},
|
||||
excludePattern: []string{"p1/**/config.json"},
|
||||
includeFiles: map[string][]byte{
|
||||
"cluster-config/production/config.json": []byte(`{
|
||||
"cluster": {
|
||||
"owner": "john.doe@example.com",
|
||||
"name": "production",
|
||||
"address": "https://kubernetes.default.svc"
|
||||
},
|
||||
"key1": "val1",
|
||||
"key2": {
|
||||
"key2_1": "val2_1",
|
||||
"key2_2": {
|
||||
"key2_2_1": "val2_2_1"
|
||||
}
|
||||
},
|
||||
"key3": 123
|
||||
}
|
||||
`),
|
||||
"p1/config.json": []byte(`{
|
||||
"database": {
|
||||
"admin": "db.admin@example.com",
|
||||
"name": "user-data",
|
||||
"host": "db.internal.local",
|
||||
"settings": {
|
||||
"replicas": 3,
|
||||
"backup": "daily"
|
||||
}
|
||||
}
|
||||
}
|
||||
`),
|
||||
"p1/p2/config.json": []byte(``),
|
||||
},
|
||||
excludeFiles: map[string][]byte{
|
||||
"p1/config.json": []byte(`{
|
||||
"database": {
|
||||
"admin": "db.admin@example.com",
|
||||
"name": "user-data",
|
||||
"host": "db.internal.local",
|
||||
"settings": {
|
||||
"replicas": 3,
|
||||
"backup": "daily"
|
||||
}
|
||||
}
|
||||
}
|
||||
`),
|
||||
"p1/p2/config.json": []byte(``),
|
||||
},
|
||||
repoPathsError: nil,
|
||||
expected: []map[string]any{
|
||||
{
|
||||
"cluster.owner": "john.doe@example.com",
|
||||
"cluster.name": "production",
|
||||
"cluster.address": "https://kubernetes.default.svc",
|
||||
"key1": "val1",
|
||||
"key2.key2_1": "val2_1",
|
||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
||||
"key3": "123",
|
||||
"path": "cluster-config/production",
|
||||
"path.basename": "production",
|
||||
"path[0]": "cluster-config",
|
||||
"path[1]": "production",
|
||||
"path.basenameNormalized": "production",
|
||||
"path.filename": "config.json",
|
||||
"path.filenameNormalized": "config.json",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter files according to multiple file-paths with exclude",
|
||||
files: []v1alpha1.GitFileGeneratorItem{
|
||||
{Path: "**/config.json"},
|
||||
{Path: "p1/app2/config.json", Exclude: true},
|
||||
{Path: "p1/app3/config.json", Exclude: true},
|
||||
},
|
||||
includePattern: []string{"**/config.json"},
|
||||
excludePattern: []string{"p1/app2/config.json", "p1/app3/config.json"},
|
||||
includeFiles: map[string][]byte{
|
||||
"p1/config.json": []byte(`{
|
||||
"cluster": {
|
||||
"owner": "john.doe@example.com",
|
||||
"name": "production",
|
||||
"address": "https://kubernetes.default.svc",
|
||||
"inner": {
|
||||
"one" : "two"
|
||||
}
|
||||
}
|
||||
}`),
|
||||
"p1/app2/config.json": []byte(`{}`),
|
||||
"p1/app3/config.json": []byte(`{}`),
|
||||
},
|
||||
excludeFiles: map[string][]byte{
|
||||
"p1/app2/config.json": []byte(`{}`),
|
||||
"p1/app3/config.json": []byte(`{}`),
|
||||
},
|
||||
repoPathsError: nil,
|
||||
expected: []map[string]any{
|
||||
{
|
||||
"cluster.owner": "john.doe@example.com",
|
||||
"cluster.name": "production",
|
||||
"cluster.address": "https://kubernetes.default.svc",
|
||||
"cluster.inner.one": "two",
|
||||
"path": "p1",
|
||||
"path.basename": "p1",
|
||||
"path[0]": "p1",
|
||||
"path.basenameNormalized": "p1",
|
||||
"path.filename": "config.json",
|
||||
"path.filenameNormalized": "config.json",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "docs example test case to filter files according to multiple file-paths with exclude",
|
||||
files: []v1alpha1.GitFileGeneratorItem{{Path: "cluster-config/**/config.json"}, {Path: "cluster-config/*/dev/config.json", Exclude: true}},
|
||||
includePattern: []string{"cluster-config/**/config.json"},
|
||||
excludePattern: []string{"cluster-config/*/dev/config.json"},
|
||||
includeFiles: map[string][]byte{
|
||||
"cluster-config/engineering/prod/config.json": []byte(`
|
||||
cluster:
|
||||
owner: john.doe@example.com
|
||||
name: production
|
||||
address: https://kubernetes.default.svc
|
||||
`),
|
||||
"cluster-config/engineering/dev/config.json": []byte(`
|
||||
cluster:
|
||||
owner: foo.bar@example.com
|
||||
name: staging
|
||||
address: https://kubernetes.default.svc
|
||||
`),
|
||||
},
|
||||
excludeFiles: map[string][]byte{
|
||||
"cluster-config/engineering/dev/config.json": []byte(`
|
||||
cluster:
|
||||
owner: foo.bar@example.com
|
||||
name: staging
|
||||
address: https://kubernetes.default.svc
|
||||
`),
|
||||
},
|
||||
repoPathsError: nil,
|
||||
expected: []map[string]any{
|
||||
{
|
||||
"cluster.owner": "john.doe@example.com",
|
||||
"cluster.name": "production",
|
||||
"cluster.address": "https://kubernetes.default.svc",
|
||||
"path": "cluster-config/engineering/prod",
|
||||
"path.basename": "prod",
|
||||
"path[0]": "cluster-config",
|
||||
"path[1]": "engineering",
|
||||
"path[2]": "prod",
|
||||
"path.basenameNormalized": "prod",
|
||||
"path.filename": "config.json",
|
||||
"path.filenameNormalized": "config.json",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "testcase to verify new globbing pattern without any exclude",
|
||||
files: []v1alpha1.GitFileGeneratorItem{{Path: "some-path/*.yaml"}},
|
||||
includePattern: []string{"some-path/*.yaml"},
|
||||
excludePattern: nil,
|
||||
includeFiles: map[string][]byte{
|
||||
"some-path/values.yaml": []byte(`
|
||||
cluster:
|
||||
owner: john.doe@example.com
|
||||
name: production
|
||||
address: https://kubernetes.default.svc
|
||||
`),
|
||||
},
|
||||
excludeFiles: map[string][]byte{},
|
||||
noMatchFiles: map[string][]byte{
|
||||
"some-path/staging/values.yaml": []byte(`
|
||||
cluster:
|
||||
owner: foo.bar@example.com
|
||||
name: staging
|
||||
address: https://kubernetes.default.svc
|
||||
`),
|
||||
},
|
||||
repoPathsError: nil,
|
||||
expected: []map[string]any{
|
||||
{
|
||||
"cluster.owner": "john.doe@example.com",
|
||||
"cluster.name": "production",
|
||||
"cluster.address": "https://kubernetes.default.svc",
|
||||
"path": "some-path",
|
||||
"path.basename": "some-path",
|
||||
"path[0]": "some-path",
|
||||
"path.basenameNormalized": "some-path",
|
||||
"path.filename": "values.yaml",
|
||||
"path.filenameNormalized": "values.yaml",
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "test to verify the solution for Git File Generator Problem", // https://github.com/argoproj/argo-cd/blob/master/docs/operator-manual/applicationset/Generators-Git-File-Globbing.md#git-file-generator-globbing
|
||||
files: []v1alpha1.GitFileGeneratorItem{{Path: "cluster-charts/*/*/values.yaml"}, {Path: "cluster-charts/*/values.yaml", Exclude: true}},
|
||||
includePattern: []string{"cluster-charts/*/*/values.yaml"},
|
||||
excludePattern: []string{"cluster-charts/*/values.yaml"},
|
||||
includeFiles: map[string][]byte{
|
||||
"cluster-charts/cluster1/mychart/values.yaml": []byte(`
|
||||
env: staging
|
||||
`),
|
||||
"cluster-charts/cluster1/myotherchart/values.yaml": []byte(`
|
||||
env: prod
|
||||
`),
|
||||
},
|
||||
excludeFiles: map[string][]byte{
|
||||
"cluster-charts/cluster2/values.yaml": []byte(`
|
||||
env: dev
|
||||
`),
|
||||
},
|
||||
noMatchFiles: map[string][]byte{
|
||||
"cluster-charts/cluster1/mychart/charts/mysubchart/values.yaml": []byte(`
|
||||
env: testing
|
||||
`),
|
||||
},
|
||||
repoPathsError: nil,
|
||||
expected: []map[string]any{
|
||||
{
|
||||
"env": "staging",
|
||||
"path": "cluster-charts/cluster1/mychart",
|
||||
"path.filenameNormalized": "values.yaml",
|
||||
"path[0]": "cluster-charts",
|
||||
"path[1]": "cluster1",
|
||||
"path[2]": "mychart",
|
||||
"path.basename": "mychart",
|
||||
"path.filename": "values.yaml",
|
||||
"path.basenameNormalized": "mychart",
|
||||
},
|
||||
{
|
||||
"env": "prod",
|
||||
"path": "cluster-charts/cluster1/myotherchart",
|
||||
"path.filenameNormalized": "values.yaml",
|
||||
"path[0]": "cluster-charts",
|
||||
"path[1]": "cluster1",
|
||||
"path[2]": "myotherchart",
|
||||
"path.basename": "myotherchart",
|
||||
"path.filename": "values.yaml",
|
||||
"path.basenameNormalized": "myotherchart",
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
for _, testCase := range cases {
|
||||
testCaseCopy := testCase
|
||||
|
||||
t.Run(testCaseCopy.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
argoCDServiceMock := mocks.Repos{}
|
||||
|
||||
// IMPORTANT: we try to get the files from the repo server that matches the patterns
|
||||
// If we find those files also satisfy the exclude pattern, we remove them from map
|
||||
// This is generally done by the g.repos.GetFiles() function.
|
||||
// With the below mock setup, we make sure that if the GetFiles() function gets called
|
||||
// for a include or exclude pattern, it should always return the includeFiles or excludeFiles.
|
||||
for _, pattern := range testCaseCopy.excludePattern {
|
||||
argoCDServiceMock.
|
||||
On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, pattern, mock.Anything, mock.Anything).
|
||||
Return(testCaseCopy.excludeFiles, testCaseCopy.repoPathsError)
|
||||
}
|
||||
|
||||
for _, pattern := range testCaseCopy.includePattern {
|
||||
argoCDServiceMock.
|
||||
On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, pattern, mock.Anything, mock.Anything).
|
||||
Return(testCaseCopy.includeFiles, testCaseCopy.repoPathsError)
|
||||
}
|
||||
|
||||
gitGenerator := NewGitGenerator(&argoCDServiceMock, "")
|
||||
applicationSetInfo := v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "set",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Generators: []v1alpha1.ApplicationSetGenerator{{
|
||||
Git: &v1alpha1.GitGenerator{
|
||||
RepoURL: "RepoURL",
|
||||
Revision: "Revision",
|
||||
Files: testCaseCopy.files,
|
||||
Values: testCaseCopy.values,
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
err := v1alpha1.AddToScheme(scheme)
|
||||
require.NoError(t, err)
|
||||
appProject := v1alpha1.AppProject{}
|
||||
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
|
||||
|
||||
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, client)
|
||||
|
||||
if testCaseCopy.expectedError != nil {
|
||||
require.EqualError(t, err, testCaseCopy.expectedError.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.ElementsMatch(t, testCaseCopy.expected, got)
|
||||
}
|
||||
|
||||
argoCDServiceMock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGitGeneratorParamsFromFilesWithExcludeOptionWithOldGlobbing tests the params values generated by git file generator
|
||||
// // when exclude option is set to true. It gives the result files based on old globbing pattern - git ls-files
|
||||
func TestGitGeneratorParamsFromFilesWithExcludeOptionWithOldGlobbing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
// files is the list of paths/globs to match
|
||||
files []v1alpha1.GitFileGeneratorItem
|
||||
// includePattern contains a list of file patterns that needs to be included
|
||||
includePattern []string
|
||||
// excludePattern contains a list of file patterns that needs to be excluded
|
||||
excludePattern []string
|
||||
// includeFiles is a map with key as absolute path to file and value as the content in bytes that satisfies the includePattern
|
||||
includeFiles map[string][]byte
|
||||
// excludeFiles is a map with key as absolute path to file and value as the content in bytes that satisfies the excludePattern
|
||||
// This means all the files should be excluded
|
||||
excludeFiles map[string][]byte
|
||||
// noMatchFiles contains all the files that neither match include pattern nor exclude pattern
|
||||
// Instead of keeping those files in the excludeFiles map, it is better to keep those files separately
|
||||
// in a separate field like 'noMatchFiles' to avoid confusion.
|
||||
noMatchFiles map[string][]byte
|
||||
// if repoPathsError is non-nil, the call to GetPaths(...) will return this error value
|
||||
repoPathsError error
|
||||
values map[string]string
|
||||
expected []map[string]any
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "filter files according to file-path with exclude",
|
||||
files: []v1alpha1.GitFileGeneratorItem{
|
||||
{
|
||||
Path: "**/config.json",
|
||||
},
|
||||
{
|
||||
Path: "p1/**/config.json",
|
||||
Exclude: true,
|
||||
},
|
||||
},
|
||||
includePattern: []string{"**/config.json"},
|
||||
excludePattern: []string{"p1/**/config.json"},
|
||||
includeFiles: map[string][]byte{
|
||||
"cluster-config/production/config.json": []byte(`{
|
||||
"cluster": {
|
||||
"owner": "john.doe@example.com",
|
||||
"name": "production",
|
||||
"address": "https://kubernetes.default.svc"
|
||||
},
|
||||
"key1": "val1",
|
||||
"key2": {
|
||||
"key2_1": "val2_1",
|
||||
"key2_2": {
|
||||
"key2_2_1": "val2_2_1"
|
||||
}
|
||||
},
|
||||
"key3": 123
|
||||
}
|
||||
`),
|
||||
"p1/config.json": []byte(`{
|
||||
"database": {
|
||||
"admin": "db.admin@example.com",
|
||||
"name": "user-data",
|
||||
"host": "db.internal.local",
|
||||
"settings": {
|
||||
"replicas": 3,
|
||||
"backup": "daily"
|
||||
}
|
||||
}
|
||||
}
|
||||
`),
|
||||
"p1/p2/config.json": []byte(``),
|
||||
},
|
||||
excludeFiles: map[string][]byte{
|
||||
"p1/config.json": []byte(`{
|
||||
"database": {
|
||||
"admin": "db.admin@example.com",
|
||||
"name": "user-data",
|
||||
"host": "db.internal.local",
|
||||
"settings": {
|
||||
"replicas": 3,
|
||||
"backup": "daily"
|
||||
}
|
||||
}
|
||||
}
|
||||
`),
|
||||
"p1/p2/config.json": []byte(``),
|
||||
},
|
||||
repoPathsError: nil,
|
||||
expected: []map[string]any{
|
||||
{
|
||||
"cluster.owner": "john.doe@example.com",
|
||||
"cluster.name": "production",
|
||||
"cluster.address": "https://kubernetes.default.svc",
|
||||
"key1": "val1",
|
||||
"key2.key2_1": "val2_1",
|
||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
||||
"key3": "123",
|
||||
"path": "cluster-config/production",
|
||||
"path.basename": "production",
|
||||
"path[0]": "cluster-config",
|
||||
"path[1]": "production",
|
||||
"path.basenameNormalized": "production",
|
||||
"path.filename": "config.json",
|
||||
"path.filenameNormalized": "config.json",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter files according to multiple file-paths with exclude",
|
||||
files: []v1alpha1.GitFileGeneratorItem{
|
||||
{Path: "**/config.json"},
|
||||
{Path: "p1/app2/config.json", Exclude: true},
|
||||
{Path: "p1/app3/config.json", Exclude: true},
|
||||
},
|
||||
includePattern: []string{"**/config.json"},
|
||||
excludePattern: []string{"p1/app2/config.json", "p1/app3/config.json"},
|
||||
includeFiles: map[string][]byte{
|
||||
"p1/config.json": []byte(`{
|
||||
"cluster": {
|
||||
"owner": "john.doe@example.com",
|
||||
"name": "production",
|
||||
"address": "https://kubernetes.default.svc",
|
||||
"inner": {
|
||||
"one" : "two"
|
||||
}
|
||||
}
|
||||
}`),
|
||||
"p1/app2/config.json": []byte(`{}`),
|
||||
"p1/app3/config.json": []byte(`{}`),
|
||||
},
|
||||
excludeFiles: map[string][]byte{
|
||||
"p1/app2/config.json": []byte(`{}`),
|
||||
"p1/app3/config.json": []byte(`{}`),
|
||||
},
|
||||
repoPathsError: nil,
|
||||
expected: []map[string]any{
|
||||
{
|
||||
"cluster.owner": "john.doe@example.com",
|
||||
"cluster.name": "production",
|
||||
"cluster.address": "https://kubernetes.default.svc",
|
||||
"cluster.inner.one": "two",
|
||||
"path": "p1",
|
||||
"path.basename": "p1",
|
||||
"path[0]": "p1",
|
||||
"path.basenameNormalized": "p1",
|
||||
"path.filename": "config.json",
|
||||
"path.filenameNormalized": "config.json",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "docs example test case to filter files according to multiple file-paths with exclude",
|
||||
files: []v1alpha1.GitFileGeneratorItem{{Path: "cluster-config/**/config.json"}, {Path: "cluster-config/*/dev/config.json", Exclude: true}},
|
||||
includePattern: []string{"cluster-config/**/config.json"},
|
||||
excludePattern: []string{"cluster-config/*/dev/config.json"},
|
||||
includeFiles: map[string][]byte{
|
||||
"cluster-config/engineering/prod/config.json": []byte(`
|
||||
cluster:
|
||||
owner: john.doe@example.com
|
||||
name: production
|
||||
address: https://kubernetes.default.svc
|
||||
`),
|
||||
"cluster-config/engineering/dev/config.json": []byte(`
|
||||
cluster:
|
||||
owner: foo.bar@example.com
|
||||
name: staging
|
||||
address: https://kubernetes.default.svc
|
||||
`),
|
||||
},
|
||||
excludeFiles: map[string][]byte{
|
||||
"cluster-config/engineering/dev/config.json": []byte(`
|
||||
cluster:
|
||||
owner: foo.bar@example.com
|
||||
name: staging
|
||||
address: https://kubernetes.default.svc
|
||||
`),
|
||||
},
|
||||
repoPathsError: nil,
|
||||
expected: []map[string]any{
|
||||
{
|
||||
"cluster.owner": "john.doe@example.com",
|
||||
"cluster.name": "production",
|
||||
"cluster.address": "https://kubernetes.default.svc",
|
||||
"path": "cluster-config/engineering/prod",
|
||||
"path.basename": "prod",
|
||||
"path[0]": "cluster-config",
|
||||
"path[1]": "engineering",
|
||||
"path[2]": "prod",
|
||||
"path.basenameNormalized": "prod",
|
||||
"path.filename": "config.json",
|
||||
"path.filenameNormalized": "config.json",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "testcase to verify new globbing pattern without any exclude",
|
||||
files: []v1alpha1.GitFileGeneratorItem{{Path: "some-path/*.yaml"}},
|
||||
includePattern: []string{"some-path/*.yaml"},
|
||||
excludePattern: nil,
|
||||
includeFiles: map[string][]byte{
|
||||
"some-path/values.yaml": []byte(`
|
||||
cluster:
|
||||
owner: john.doe@example.com
|
||||
name: production
|
||||
address: https://kubernetes.default.svc
|
||||
`),
|
||||
"some-path/staging/values.yaml": []byte(`
|
||||
cluster:
|
||||
owner: foo.bar@example.com
|
||||
name: staging
|
||||
address: https://kubernetes.default.svc
|
||||
`),
|
||||
},
|
||||
excludeFiles: map[string][]byte{},
|
||||
repoPathsError: nil,
|
||||
expected: []map[string]any{
|
||||
{
|
||||
"cluster.owner": "john.doe@example.com",
|
||||
"cluster.name": "production",
|
||||
"cluster.address": "https://kubernetes.default.svc",
|
||||
"path": "some-path",
|
||||
"path.basename": "some-path",
|
||||
"path[0]": "some-path",
|
||||
"path.basenameNormalized": "some-path",
|
||||
"path.filename": "values.yaml",
|
||||
"path.filenameNormalized": "values.yaml",
|
||||
},
|
||||
{
|
||||
"cluster.owner": "foo.bar@example.com",
|
||||
"cluster.name": "staging",
|
||||
"cluster.address": "https://kubernetes.default.svc",
|
||||
"path": "some-path/staging",
|
||||
"path.basename": "staging",
|
||||
"path[0]": "some-path",
|
||||
"path[1]": "staging",
|
||||
"path.basenameNormalized": "staging",
|
||||
"path.filename": "values.yaml",
|
||||
"path.filenameNormalized": "values.yaml",
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "test to verify the solution for Git File Generator Problem",
|
||||
files: []v1alpha1.GitFileGeneratorItem{{Path: "cluster-charts/*/*/values.yaml"}, {Path: "cluster-charts/*/values.yaml", Exclude: true}},
|
||||
includePattern: []string{"cluster-charts/*/*/values.yaml"},
|
||||
excludePattern: []string{"cluster-charts/*/values.yaml"},
|
||||
includeFiles: map[string][]byte{
|
||||
"cluster-charts/cluster1/mychart/values.yaml": []byte(`
|
||||
env: staging
|
||||
`),
|
||||
"cluster-charts/cluster1/myotherchart/values.yaml": []byte(`
|
||||
env: prod
|
||||
`),
|
||||
"cluster-charts/cluster1/mychart/charts/mysubchart/values.yaml": []byte(``),
|
||||
},
|
||||
excludeFiles: map[string][]byte{
|
||||
"cluster-charts/cluster2/values.yaml": []byte(`
|
||||
env: dev
|
||||
`),
|
||||
"cluster-charts/cluster1/mychart/values.yaml": []byte(`
|
||||
env: staging
|
||||
`),
|
||||
"cluster-charts/cluster1/myotherchart/values.yaml": []byte(`
|
||||
env: prod
|
||||
`),
|
||||
"cluster-charts/cluster1/mychart/charts/mysubchart/values.yaml": []byte(``),
|
||||
},
|
||||
noMatchFiles: map[string][]byte{
|
||||
"cluster-charts/cluster1/mychart/charts/mysubchart/values.yaml": []byte(`
|
||||
env: testing
|
||||
`),
|
||||
},
|
||||
repoPathsError: nil,
|
||||
expected: []map[string]any{},
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
for _, testCase := range cases {
|
||||
testCaseCopy := testCase
|
||||
|
||||
t.Run(testCaseCopy.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
argoCDServiceMock := mocks.Repos{}
|
||||
|
||||
// IMPORTANT: we try to get the files from the repo server that matches the patterns
|
||||
// If we find those files also satisfy the exclude pattern, we remove them from map
|
||||
// This is generally done by the g.repos.GetFiles() function.
|
||||
// With the below mock setup, we make sure that if the GetFiles() function gets called
|
||||
// for a include or exclude pattern, it should always return the includeFiles or excludeFiles.
|
||||
for _, pattern := range testCaseCopy.excludePattern {
|
||||
argoCDServiceMock.
|
||||
On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, pattern, mock.Anything, mock.Anything).
|
||||
Return(testCaseCopy.excludeFiles, testCaseCopy.repoPathsError)
|
||||
}
|
||||
|
||||
for _, pattern := range testCaseCopy.includePattern {
|
||||
argoCDServiceMock.
|
||||
On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, pattern, mock.Anything, mock.Anything).
|
||||
Return(testCaseCopy.includeFiles, testCaseCopy.repoPathsError)
|
||||
}
|
||||
|
||||
gitGenerator := NewGitGenerator(&argoCDServiceMock, "")
|
||||
applicationSetInfo := v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "set",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Generators: []v1alpha1.ApplicationSetGenerator{{
|
||||
Git: &v1alpha1.GitGenerator{
|
||||
RepoURL: "RepoURL",
|
||||
Revision: "Revision",
|
||||
Files: testCaseCopy.files,
|
||||
Values: testCaseCopy.values,
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
err := v1alpha1.AddToScheme(scheme)
|
||||
require.NoError(t, err)
|
||||
appProject := v1alpha1.AppProject{}
|
||||
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
|
||||
|
||||
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, client)
|
||||
|
||||
if testCaseCopy.expectedError != nil {
|
||||
require.EqualError(t, err, testCaseCopy.expectedError.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.ElementsMatch(t, testCaseCopy.expected, got)
|
||||
}
|
||||
|
||||
argoCDServiceMock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGitGeneratorParamsFromFilesWithExcludeOption tests the params values generated by git file generator
|
||||
// when exclude option is set to true. It gives the result files based on new globbing pattern - doublestar package
|
||||
func TestGitGeneratorParamsFromFilesWithExcludeOptionGoTemplate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
// files is the list of paths/globs to match
|
||||
files []v1alpha1.GitFileGeneratorItem
|
||||
// includePattern contains a list of file patterns that needs to be included
|
||||
includePattern []string
|
||||
// excludePattern contains a list of file patterns that needs to be excluded
|
||||
excludePattern []string
|
||||
// includeFiles is a map with key as absolute path to file and value as the content in bytes that satisfies the includePattern
|
||||
includeFiles map[string][]byte
|
||||
// excludeFiles is a map with key as absolute path to file and value as the content in bytes that satisfies the excludePattern
|
||||
// This means all the files should be excluded
|
||||
excludeFiles map[string][]byte
|
||||
// if repoPathsError is non-nil, the call to GetPaths(...) will return this error value
|
||||
repoPathsError error
|
||||
values map[string]string
|
||||
expected []map[string]any
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "filter files according to file-path with exclude",
|
||||
files: []v1alpha1.GitFileGeneratorItem{
|
||||
{
|
||||
Path: "**/config.json",
|
||||
},
|
||||
{
|
||||
Path: "p1/**/config.json",
|
||||
Exclude: true,
|
||||
},
|
||||
},
|
||||
includePattern: []string{"**/config.json"},
|
||||
excludePattern: []string{"p1/**/config.json"},
|
||||
includeFiles: map[string][]byte{
|
||||
"cluster-config/production/config.json": []byte(`{
|
||||
"cluster": {
|
||||
"owner": "john.doe@example.com",
|
||||
"name": "production",
|
||||
"address": "https://kubernetes.default.svc"
|
||||
},
|
||||
"key1": "val1",
|
||||
"key2": {
|
||||
"key2_1": "val2_1",
|
||||
"key2_2": {
|
||||
"key2_2_1": "val2_2_1"
|
||||
}
|
||||
},
|
||||
"key3": 123
|
||||
}
|
||||
`),
|
||||
},
|
||||
excludeFiles: map[string][]byte{
|
||||
"p1/p2/config.json": []byte(`{
|
||||
"service": {
|
||||
"maintainer": "dev.team@example.com",
|
||||
"serviceName": "auth-service",
|
||||
"endpoint": "http://auth.internal.svc",
|
||||
"config": {
|
||||
"retries": 5,
|
||||
"timeout": "30s"
|
||||
}
|
||||
}
|
||||
}
|
||||
`),
|
||||
},
|
||||
repoPathsError: nil,
|
||||
expected: []map[string]any{
|
||||
{
|
||||
"cluster": map[string]any{
|
||||
"owner": "john.doe@example.com",
|
||||
"name": "production",
|
||||
"address": "https://kubernetes.default.svc",
|
||||
},
|
||||
"key1": "val1",
|
||||
"key2": map[string]any{
|
||||
"key2_1": "val2_1",
|
||||
"key2_2": map[string]any{
|
||||
"key2_2_1": "val2_2_1",
|
||||
},
|
||||
},
|
||||
"key3": float64(123),
|
||||
"path": map[string]any{
|
||||
"path": "cluster-config/production",
|
||||
"basename": "production",
|
||||
"filename": "config.json",
|
||||
"basenameNormalized": "production",
|
||||
"filenameNormalized": "config.json",
|
||||
"segments": []string{
|
||||
"cluster-config",
|
||||
"production",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "filter files according to multiple file-paths with exclude",
|
||||
files: []v1alpha1.GitFileGeneratorItem{
|
||||
{Path: "**/config.json"},
|
||||
{Path: "p1/app2/config.json", Exclude: true},
|
||||
{Path: "p1/app3/config.json", Exclude: true},
|
||||
},
|
||||
includePattern: []string{"**/config.json"},
|
||||
excludePattern: []string{"p1/app2/config.json", "p1/app3/config.json"},
|
||||
includeFiles: map[string][]byte{
|
||||
"p1/config.json": []byte(`{
|
||||
"cluster": {
|
||||
"owner": "john.doe@example.com",
|
||||
"name": "production",
|
||||
"address": "https://kubernetes.default.svc",
|
||||
"inner": {
|
||||
"one" : "two"
|
||||
}
|
||||
}
|
||||
}`),
|
||||
},
|
||||
excludeFiles: map[string][]byte{
|
||||
"p1/app2/config.json": []byte(`{
|
||||
"database": {
|
||||
"admin": "alice.smith@example.com",
|
||||
"env": "staging",
|
||||
"url": "postgres://db.internal.svc:5432",
|
||||
"settings": {
|
||||
"replicas": 3,
|
||||
"backup": "enabled"
|
||||
}
|
||||
}
|
||||
}
|
||||
`),
|
||||
"p1/app3/config.json": []byte(`{
|
||||
"storage": {
|
||||
"owner": "charlie.brown@example.com",
|
||||
"bucketName": "app-assets",
|
||||
"region": "us-west-2",
|
||||
"options": {
|
||||
"versioning": true,
|
||||
"encryption": "AES256"
|
||||
}
|
||||
}
|
||||
}
|
||||
`),
|
||||
},
|
||||
repoPathsError: nil,
|
||||
expected: []map[string]any{
|
||||
{
|
||||
"cluster": map[string]any{
|
||||
"owner": "john.doe@example.com",
|
||||
"name": "production",
|
||||
"address": "https://kubernetes.default.svc",
|
||||
"inner": map[string]any{
|
||||
"one": "two",
|
||||
},
|
||||
},
|
||||
"path": map[string]any{
|
||||
"path": "p1",
|
||||
"basename": "p1",
|
||||
"filename": "config.json",
|
||||
"basenameNormalized": "p1",
|
||||
"filenameNormalized": "config.json",
|
||||
"segments": []string{
|
||||
"p1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
for _, testCase := range cases {
|
||||
testCaseCopy := testCase
|
||||
|
||||
t.Run(testCaseCopy.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
argoCDServiceMock := mocks.Repos{}
|
||||
// IMPORTANT: we try to get the files from the repo server that matches the patterns
|
||||
// If we find those files also satisfy the exclude pattern, we remove them from map
|
||||
// This is generally done by the g.repos.GetFiles() function.
|
||||
// With the below mock setup, we make sure that if the GetFiles() function gets called
|
||||
// for a include or exclude pattern, it should always return the includeFiles or excludeFiles.
|
||||
for _, pattern := range testCaseCopy.excludePattern {
|
||||
argoCDServiceMock.
|
||||
On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, pattern, mock.Anything, mock.Anything).
|
||||
Return(testCaseCopy.excludeFiles, testCaseCopy.repoPathsError)
|
||||
}
|
||||
|
||||
for _, pattern := range testCaseCopy.includePattern {
|
||||
argoCDServiceMock.
|
||||
On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, pattern, mock.Anything, mock.Anything).
|
||||
Return(testCaseCopy.includeFiles, testCaseCopy.repoPathsError)
|
||||
}
|
||||
|
||||
gitGenerator := NewGitGenerator(&argoCDServiceMock, "")
|
||||
applicationSetInfo := v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "set",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
GoTemplate: true,
|
||||
Generators: []v1alpha1.ApplicationSetGenerator{{
|
||||
Git: &v1alpha1.GitGenerator{
|
||||
RepoURL: "RepoURL",
|
||||
Revision: "Revision",
|
||||
Files: testCaseCopy.files,
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
err := v1alpha1.AddToScheme(scheme)
|
||||
require.NoError(t, err)
|
||||
appProject := v1alpha1.AppProject{}
|
||||
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
|
||||
|
||||
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, client)
|
||||
|
||||
if testCaseCopy.expectedError != nil {
|
||||
require.EqualError(t, err, testCaseCopy.expectedError.Error())
|
||||
@@ -1029,6 +1955,8 @@ cluster:
|
||||
}
|
||||
|
||||
func TestGitGenerateParamsFromFilesGoTemplate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
// files is the list of paths/globs to match
|
||||
@@ -1370,7 +2298,6 @@ cluster:
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
|
||||
|
||||
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, client)
|
||||
fmt.Println(got, err)
|
||||
|
||||
if testCaseCopy.expectedError != nil {
|
||||
require.EqualError(t, err, testCaseCopy.expectedError.Error())
|
||||
|
||||
@@ -27,15 +27,15 @@ type Generator interface {
|
||||
}
|
||||
|
||||
var (
|
||||
EmptyAppSetGeneratorError = errors.New("ApplicationSet is empty")
|
||||
NoRequeueAfter time.Duration
|
||||
ErrEmptyAppSetGenerator = errors.New("ApplicationSet is empty")
|
||||
NoRequeueAfter time.Duration
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultRequeueAfterSeconds = 3 * time.Minute
|
||||
DefaultRequeueAfter = 3 * time.Minute
|
||||
)
|
||||
|
||||
func getDefaultRequeueAfter() time.Duration {
|
||||
// Default is 3 minutes, min is 1 second, max is 1 year
|
||||
return env.ParseDurationFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_REQUEUE_AFTER", DefaultRequeueAfterSeconds, 1*time.Second, 8760*time.Hour)
|
||||
return env.ParseDurationFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_REQUEUE_AFTER", DefaultRequeueAfter, 1*time.Second, 8760*time.Hour)
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ func Test_getDefaultRequeueAfter(t *testing.T) {
|
||||
requeueAfterEnv string
|
||||
want time.Duration
|
||||
}{
|
||||
{name: "Default", requeueAfterEnv: "", want: DefaultRequeueAfterSeconds},
|
||||
{name: "Default", requeueAfterEnv: "", want: DefaultRequeueAfter},
|
||||
{name: "Min", requeueAfterEnv: "1s", want: 1 * time.Second},
|
||||
{name: "Max", requeueAfterEnv: "8760h", want: 8760 * time.Hour},
|
||||
{name: "Override", requeueAfterEnv: "10m", want: 10 * time.Minute},
|
||||
{name: "LessThanMin", requeueAfterEnv: "1ms", want: DefaultRequeueAfterSeconds},
|
||||
{name: "MoreThanMax", requeueAfterEnv: "8761h", want: DefaultRequeueAfterSeconds},
|
||||
{name: "LessThanMin", requeueAfterEnv: "1ms", want: DefaultRequeueAfter},
|
||||
{name: "MoreThanMax", requeueAfterEnv: "8761h", want: DefaultRequeueAfter},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -31,11 +31,11 @@ func (g *ListGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Applicat
|
||||
|
||||
func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) {
|
||||
if appSetGenerator == nil {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
return nil, ErrEmptyAppSetGenerator
|
||||
}
|
||||
|
||||
if appSetGenerator.List == nil {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
return nil, ErrEmptyAppSetGenerator
|
||||
}
|
||||
|
||||
res := make([]map[string]any, len(appSetGenerator.List.Elements))
|
||||
@@ -77,7 +77,7 @@ func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appli
|
||||
}
|
||||
|
||||
// Append elements from ElementsYaml to the response
|
||||
if len(appSetGenerator.List.ElementsYaml) > 0 {
|
||||
if appSetGenerator.List.ElementsYaml != "" {
|
||||
var yamlElements []map[string]any
|
||||
err := yaml.Unmarshal([]byte(appSetGenerator.List.ElementsYaml), &yamlElements)
|
||||
if err != nil {
|
||||
|
||||
@@ -34,7 +34,7 @@ func NewMatrixGenerator(supportedGenerators map[string]Generator) Generator {
|
||||
|
||||
func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]any, error) {
|
||||
if appSetGenerator.Matrix == nil {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
return nil, ErrEmptyAppSetGenerator
|
||||
}
|
||||
|
||||
if len(appSetGenerator.Matrix.Generators) < 2 {
|
||||
@@ -71,7 +71,7 @@ func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.App
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to combine string maps with merging params for the matrix generator: %w", err)
|
||||
}
|
||||
res = append(res, utils.ConvertToMapStringInterface(val))
|
||||
res = append(res, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"dario.cat/mergo"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/utils"
|
||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
|
||||
@@ -52,7 +52,7 @@ func (m *MergeGenerator) getParamSetsForAllGenerators(generators []argoprojiov1a
|
||||
// GenerateParams gets the params produced by the MergeGenerator.
|
||||
func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]any, error) {
|
||||
if appSetGenerator.Merge == nil {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
return nil, ErrEmptyAppSetGenerator
|
||||
}
|
||||
|
||||
if len(appSetGenerator.Merge.Generators) < 2 {
|
||||
@@ -83,11 +83,8 @@ func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appl
|
||||
}
|
||||
baseParamSetsByMergeKey[mergeKeyValue] = baseParamSet
|
||||
} else {
|
||||
overriddenParamSet, err := utils.CombineStringMapsAllowDuplicates(baseParamSet, overrideParamSet)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error combining string maps: %w", err)
|
||||
}
|
||||
baseParamSetsByMergeKey[mergeKeyValue] = utils.ConvertToMapStringInterface(overriddenParamSet)
|
||||
maps.Copy(baseParamSet, overrideParamSet)
|
||||
baseParamSetsByMergeKey[mergeKeyValue] = baseParamSet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ func listOfMapsToSet(maps []map[string]any) (map[string]bool, error) {
|
||||
}
|
||||
|
||||
func TestMergeGenerate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
|
||||
@@ -210,6 +212,8 @@ func toAPIExtensionsJSON(t *testing.T, g any) *apiextensionsv1.JSON {
|
||||
}
|
||||
|
||||
func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
mergeKeys []string
|
||||
|
||||
276
applicationset/generators/mocks/Generator.go
generated
276
applicationset/generators/mocks/Generator.go
generated
@@ -1,90 +1,17 @@
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
client "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
time "time"
|
||||
|
||||
v1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
// Generator is an autogenerated mock type for the Generator type
|
||||
type Generator struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// GenerateParams provides a mock function with given fields: appSetGenerator, applicationSetInfo, _a2
|
||||
func (_m *Generator) GenerateParams(appSetGenerator *v1alpha1.ApplicationSetGenerator, applicationSetInfo *v1alpha1.ApplicationSet, _a2 client.Client) ([]map[string]interface{}, error) {
|
||||
ret := _m.Called(appSetGenerator, applicationSetInfo, _a2)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GenerateParams")
|
||||
}
|
||||
|
||||
var r0 []map[string]interface{}
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) ([]map[string]interface{}, error)); ok {
|
||||
return rf(appSetGenerator, applicationSetInfo, _a2)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) []map[string]interface{}); ok {
|
||||
r0 = rf(appSetGenerator, applicationSetInfo, _a2)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]map[string]interface{})
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) error); ok {
|
||||
r1 = rf(appSetGenerator, applicationSetInfo, _a2)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetRequeueAfter provides a mock function with given fields: appSetGenerator
|
||||
func (_m *Generator) GetRequeueAfter(appSetGenerator *v1alpha1.ApplicationSetGenerator) time.Duration {
|
||||
ret := _m.Called(appSetGenerator)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetRequeueAfter")
|
||||
}
|
||||
|
||||
var r0 time.Duration
|
||||
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator) time.Duration); ok {
|
||||
r0 = rf(appSetGenerator)
|
||||
} else {
|
||||
r0 = ret.Get(0).(time.Duration)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// GetTemplate provides a mock function with given fields: appSetGenerator
|
||||
func (_m *Generator) GetTemplate(appSetGenerator *v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate {
|
||||
ret := _m.Called(appSetGenerator)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetTemplate")
|
||||
}
|
||||
|
||||
var r0 *v1alpha1.ApplicationSetTemplate
|
||||
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate); ok {
|
||||
r0 = rf(appSetGenerator)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*v1alpha1.ApplicationSetTemplate)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// NewGenerator creates a new instance of Generator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewGenerator(t interface {
|
||||
@@ -98,3 +25,194 @@ func NewGenerator(t interface {
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
// Generator is an autogenerated mock type for the Generator type
|
||||
type Generator struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type Generator_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *Generator) EXPECT() *Generator_Expecter {
|
||||
return &Generator_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// GenerateParams provides a mock function for the type Generator
|
||||
func (_mock *Generator) GenerateParams(appSetGenerator *v1alpha1.ApplicationSetGenerator, applicationSetInfo *v1alpha1.ApplicationSet, client1 client.Client) ([]map[string]any, error) {
|
||||
ret := _mock.Called(appSetGenerator, applicationSetInfo, client1)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GenerateParams")
|
||||
}
|
||||
|
||||
var r0 []map[string]any
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) ([]map[string]any, error)); ok {
|
||||
return returnFunc(appSetGenerator, applicationSetInfo, client1)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) []map[string]any); ok {
|
||||
r0 = returnFunc(appSetGenerator, applicationSetInfo, client1)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]map[string]any)
|
||||
}
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) error); ok {
|
||||
r1 = returnFunc(appSetGenerator, applicationSetInfo, client1)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Generator_GenerateParams_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GenerateParams'
|
||||
type Generator_GenerateParams_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GenerateParams is a helper method to define mock.On call
|
||||
// - appSetGenerator *v1alpha1.ApplicationSetGenerator
|
||||
// - applicationSetInfo *v1alpha1.ApplicationSet
|
||||
// - client1 client.Client
|
||||
func (_e *Generator_Expecter) GenerateParams(appSetGenerator interface{}, applicationSetInfo interface{}, client1 interface{}) *Generator_GenerateParams_Call {
|
||||
return &Generator_GenerateParams_Call{Call: _e.mock.On("GenerateParams", appSetGenerator, applicationSetInfo, client1)}
|
||||
}
|
||||
|
||||
func (_c *Generator_GenerateParams_Call) Run(run func(appSetGenerator *v1alpha1.ApplicationSetGenerator, applicationSetInfo *v1alpha1.ApplicationSet, client1 client.Client)) *Generator_GenerateParams_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 *v1alpha1.ApplicationSetGenerator
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(*v1alpha1.ApplicationSetGenerator)
|
||||
}
|
||||
var arg1 *v1alpha1.ApplicationSet
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(*v1alpha1.ApplicationSet)
|
||||
}
|
||||
var arg2 client.Client
|
||||
if args[2] != nil {
|
||||
arg2 = args[2].(client.Client)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Generator_GenerateParams_Call) Return(stringToVs []map[string]any, err error) *Generator_GenerateParams_Call {
|
||||
_c.Call.Return(stringToVs, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Generator_GenerateParams_Call) RunAndReturn(run func(appSetGenerator *v1alpha1.ApplicationSetGenerator, applicationSetInfo *v1alpha1.ApplicationSet, client1 client.Client) ([]map[string]any, error)) *Generator_GenerateParams_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetRequeueAfter provides a mock function for the type Generator
|
||||
func (_mock *Generator) GetRequeueAfter(appSetGenerator *v1alpha1.ApplicationSetGenerator) time.Duration {
|
||||
ret := _mock.Called(appSetGenerator)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetRequeueAfter")
|
||||
}
|
||||
|
||||
var r0 time.Duration
|
||||
if returnFunc, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator) time.Duration); ok {
|
||||
r0 = returnFunc(appSetGenerator)
|
||||
} else {
|
||||
r0 = ret.Get(0).(time.Duration)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Generator_GetRequeueAfter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRequeueAfter'
|
||||
type Generator_GetRequeueAfter_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetRequeueAfter is a helper method to define mock.On call
|
||||
// - appSetGenerator *v1alpha1.ApplicationSetGenerator
|
||||
func (_e *Generator_Expecter) GetRequeueAfter(appSetGenerator interface{}) *Generator_GetRequeueAfter_Call {
|
||||
return &Generator_GetRequeueAfter_Call{Call: _e.mock.On("GetRequeueAfter", appSetGenerator)}
|
||||
}
|
||||
|
||||
func (_c *Generator_GetRequeueAfter_Call) Run(run func(appSetGenerator *v1alpha1.ApplicationSetGenerator)) *Generator_GetRequeueAfter_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 *v1alpha1.ApplicationSetGenerator
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(*v1alpha1.ApplicationSetGenerator)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Generator_GetRequeueAfter_Call) Return(duration time.Duration) *Generator_GetRequeueAfter_Call {
|
||||
_c.Call.Return(duration)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Generator_GetRequeueAfter_Call) RunAndReturn(run func(appSetGenerator *v1alpha1.ApplicationSetGenerator) time.Duration) *Generator_GetRequeueAfter_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetTemplate provides a mock function for the type Generator
|
||||
func (_mock *Generator) GetTemplate(appSetGenerator *v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate {
|
||||
ret := _mock.Called(appSetGenerator)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetTemplate")
|
||||
}
|
||||
|
||||
var r0 *v1alpha1.ApplicationSetTemplate
|
||||
if returnFunc, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate); ok {
|
||||
r0 = returnFunc(appSetGenerator)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*v1alpha1.ApplicationSetTemplate)
|
||||
}
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Generator_GetTemplate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTemplate'
|
||||
type Generator_GetTemplate_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetTemplate is a helper method to define mock.On call
|
||||
// - appSetGenerator *v1alpha1.ApplicationSetGenerator
|
||||
func (_e *Generator_Expecter) GetTemplate(appSetGenerator interface{}) *Generator_GetTemplate_Call {
|
||||
return &Generator_GetTemplate_Call{Call: _e.mock.On("GetTemplate", appSetGenerator)}
|
||||
}
|
||||
|
||||
func (_c *Generator_GetTemplate_Call) Run(run func(appSetGenerator *v1alpha1.ApplicationSetGenerator)) *Generator_GetTemplate_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 *v1alpha1.ApplicationSetGenerator
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(*v1alpha1.ApplicationSetGenerator)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Generator_GetTemplate_Call) Return(applicationSetTemplate *v1alpha1.ApplicationSetTemplate) *Generator_GetTemplate_Call {
|
||||
_c.Call.Return(applicationSetTemplate)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Generator_GetTemplate_Call) RunAndReturn(run func(appSetGenerator *v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate) *Generator_GetTemplate_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/jeremywohl/flatten"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
@@ -20,23 +19,19 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultPluginRequeueAfterSeconds = 30 * time.Minute
|
||||
DefaultPluginRequeueAfter = 30 * time.Minute
|
||||
)
|
||||
|
||||
var _ Generator = (*PluginGenerator)(nil)
|
||||
|
||||
type PluginGenerator struct {
|
||||
client client.Client
|
||||
ctx context.Context
|
||||
clientset kubernetes.Interface
|
||||
namespace string
|
||||
}
|
||||
|
||||
func NewPluginGenerator(ctx context.Context, client client.Client, clientset kubernetes.Interface, namespace string) Generator {
|
||||
func NewPluginGenerator(client client.Client, namespace string) Generator {
|
||||
g := &PluginGenerator{
|
||||
client: client,
|
||||
ctx: ctx,
|
||||
clientset: clientset,
|
||||
namespace: namespace,
|
||||
}
|
||||
return g
|
||||
@@ -49,7 +44,7 @@ func (g *PluginGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Ap
|
||||
return time.Duration(*appSetGenerator.Plugin.RequeueAfterSeconds) * time.Second
|
||||
}
|
||||
|
||||
return DefaultPluginRequeueAfterSeconds
|
||||
return DefaultPluginRequeueAfter
|
||||
}
|
||||
|
||||
func (g *PluginGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
|
||||
@@ -58,11 +53,11 @@ func (g *PluginGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Applic
|
||||
|
||||
func (g *PluginGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) {
|
||||
if appSetGenerator == nil {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
return nil, ErrEmptyAppSetGenerator
|
||||
}
|
||||
|
||||
if appSetGenerator.Plugin == nil {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
return nil, ErrEmptyAppSetGenerator
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -14,8 +14,6 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
kubefake "k8s.io/client-go/kubernetes/fake"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
@@ -628,8 +626,6 @@ func TestPluginGenerateParams(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
ctx := t.Context()
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
generatorConfig := argoprojiov1alpha1.ApplicationSetGenerator{
|
||||
@@ -666,11 +662,9 @@ func TestPluginGenerateParams(t *testing.T) {
|
||||
testCase.configmap.Data["baseUrl"] = fakeServer.URL
|
||||
}
|
||||
|
||||
fakeClient := kubefake.NewSimpleClientset(append([]runtime.Object{}, testCase.configmap, testCase.secret)...)
|
||||
|
||||
fakeClientWithCache := fake.NewClientBuilder().WithObjects([]client.Object{testCase.configmap, testCase.secret}...).Build()
|
||||
|
||||
pluginGenerator := NewPluginGenerator(ctx, fakeClientWithCache, fakeClient, "default")
|
||||
pluginGenerator := NewPluginGenerator(fakeClientWithCache, "default")
|
||||
|
||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
|
||||
"github.com/gosimple/slug"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/services"
|
||||
pullrequest "github.com/argoproj/argo-cd/v3/applicationset/services/pull_request"
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/utils"
|
||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
@@ -19,7 +21,7 @@ import (
|
||||
var _ Generator = (*PullRequestGenerator)(nil)
|
||||
|
||||
const (
|
||||
DefaultPullRequestRequeueAfterSeconds = 30 * time.Minute
|
||||
DefaultPullRequestRequeueAfter = 30 * time.Minute
|
||||
)
|
||||
|
||||
type PullRequestGenerator struct {
|
||||
@@ -44,7 +46,7 @@ func (g *PullRequestGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alph
|
||||
return time.Duration(*appSetGenerator.PullRequest.RequeueAfterSeconds) * time.Second
|
||||
}
|
||||
|
||||
return DefaultPullRequestRequeueAfterSeconds
|
||||
return DefaultPullRequestRequeueAfter
|
||||
}
|
||||
|
||||
func (g *PullRequestGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
|
||||
@@ -53,11 +55,11 @@ func (g *PullRequestGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.A
|
||||
|
||||
func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) {
|
||||
if appSetGenerator == nil {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
return nil, ErrEmptyAppSetGenerator
|
||||
}
|
||||
|
||||
if appSetGenerator.PullRequest == nil {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
return nil, ErrEmptyAppSetGenerator
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
@@ -157,7 +159,8 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching Secret token: %w", err)
|
||||
}
|
||||
return pullrequest.NewGiteaService(token, providerConfig.API, providerConfig.Owner, providerConfig.Repo, providerConfig.Insecure)
|
||||
|
||||
return pullrequest.NewGiteaService(token, providerConfig.API, providerConfig.Owner, providerConfig.Repo, providerConfig.Labels, providerConfig.Insecure)
|
||||
}
|
||||
if generatorConfig.BitbucketServer != nil {
|
||||
providerConfig := generatorConfig.BitbucketServer
|
||||
@@ -213,12 +216,27 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
|
||||
}
|
||||
|
||||
func (g *PullRequestGenerator) github(ctx context.Context, cfg *argoprojiov1alpha1.PullRequestGeneratorGithub, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
|
||||
var metricsCtx *services.MetricsContext
|
||||
var httpClient *http.Client
|
||||
|
||||
if g.enableGitHubAPIMetrics {
|
||||
metricsCtx = &services.MetricsContext{
|
||||
AppSetNamespace: applicationSetInfo.Namespace,
|
||||
AppSetName: applicationSetInfo.Name,
|
||||
}
|
||||
httpClient = services.NewGitHubMetricsClient(metricsCtx)
|
||||
}
|
||||
|
||||
// use an app if it was configured
|
||||
if cfg.AppSecretName != "" {
|
||||
auth, err := g.GitHubApps.GetAuthSecret(ctx, cfg.AppSecretName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting GitHub App secret: %w", err)
|
||||
}
|
||||
|
||||
if g.enableGitHubAPIMetrics {
|
||||
return pullrequest.NewGithubAppService(*auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels, httpClient)
|
||||
}
|
||||
return pullrequest.NewGithubAppService(*auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
|
||||
}
|
||||
|
||||
@@ -227,5 +245,9 @@ func (g *PullRequestGenerator) github(ctx context.Context, cfg *argoprojiov1alph
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching Secret token: %w", err)
|
||||
}
|
||||
|
||||
if g.enableGitHubAPIMetrics {
|
||||
return pullrequest.NewGithubService(token, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels, httpClient)
|
||||
}
|
||||
return pullrequest.NewGithubService(token, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
|
||||
}
|
||||
|
||||
@@ -275,6 +275,8 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAllowedSCMProviderPullRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
providerConfig *argoprojiov1alpha1.PullRequestGenerator
|
||||
@@ -325,7 +327,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
|
||||
"gitea.myorg.com",
|
||||
"bitbucket.myorg.com",
|
||||
"azuredevops.myorg.com",
|
||||
}, true, nil, true))
|
||||
}, true, true, nil, true))
|
||||
|
||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -348,7 +350,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSCMProviderDisabled_PRGenerator(t *testing.T) {
|
||||
generator := NewPullRequestGenerator(nil, NewSCMConfig("", []string{}, false, nil, true))
|
||||
generator := NewPullRequestGenerator(nil, NewSCMConfig("", []string{}, false, true, nil, true))
|
||||
|
||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/services"
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/services/github_app_auth"
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/services/scm_provider"
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/utils"
|
||||
@@ -21,7 +23,7 @@ import (
|
||||
var _ Generator = (*SCMProviderGenerator)(nil)
|
||||
|
||||
const (
|
||||
DefaultSCMProviderRequeueAfterSeconds = 30 * time.Minute
|
||||
DefaultSCMProviderRequeueAfter = 30 * time.Minute
|
||||
)
|
||||
|
||||
type SCMProviderGenerator struct {
|
||||
@@ -31,20 +33,22 @@ type SCMProviderGenerator struct {
|
||||
SCMConfig
|
||||
}
|
||||
type SCMConfig struct {
|
||||
scmRootCAPath string
|
||||
allowedSCMProviders []string
|
||||
enableSCMProviders bool
|
||||
GitHubApps github_app_auth.Credentials
|
||||
tokenRefStrictMode bool
|
||||
scmRootCAPath string
|
||||
allowedSCMProviders []string
|
||||
enableSCMProviders bool
|
||||
enableGitHubAPIMetrics bool
|
||||
GitHubApps github_app_auth.Credentials
|
||||
tokenRefStrictMode bool
|
||||
}
|
||||
|
||||
func NewSCMConfig(scmRootCAPath string, allowedSCMProviders []string, enableSCMProviders bool, gitHubApps github_app_auth.Credentials, tokenRefStrictMode bool) SCMConfig {
|
||||
func NewSCMConfig(scmRootCAPath string, allowedSCMProviders []string, enableSCMProviders bool, enableGitHubAPIMetrics bool, gitHubApps github_app_auth.Credentials, tokenRefStrictMode bool) SCMConfig {
|
||||
return SCMConfig{
|
||||
scmRootCAPath: scmRootCAPath,
|
||||
allowedSCMProviders: allowedSCMProviders,
|
||||
enableSCMProviders: enableSCMProviders,
|
||||
GitHubApps: gitHubApps,
|
||||
tokenRefStrictMode: tokenRefStrictMode,
|
||||
scmRootCAPath: scmRootCAPath,
|
||||
allowedSCMProviders: allowedSCMProviders,
|
||||
enableSCMProviders: enableSCMProviders,
|
||||
enableGitHubAPIMetrics: enableGitHubAPIMetrics,
|
||||
GitHubApps: gitHubApps,
|
||||
tokenRefStrictMode: tokenRefStrictMode,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +73,7 @@ func (g *SCMProviderGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alph
|
||||
return time.Duration(*appSetGenerator.SCMProvider.RequeueAfterSeconds) * time.Second
|
||||
}
|
||||
|
||||
return DefaultSCMProviderRequeueAfterSeconds
|
||||
return DefaultSCMProviderRequeueAfter
|
||||
}
|
||||
|
||||
func (g *SCMProviderGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
|
||||
@@ -118,11 +122,11 @@ func ScmProviderAllowed(applicationSetInfo *argoprojiov1alpha1.ApplicationSet, g
|
||||
|
||||
func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) {
|
||||
if appSetGenerator == nil {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
return nil, ErrEmptyAppSetGenerator
|
||||
}
|
||||
|
||||
if appSetGenerator.SCMProvider == nil {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
return nil, ErrEmptyAppSetGenerator
|
||||
}
|
||||
|
||||
if !g.enableSCMProviders {
|
||||
@@ -253,6 +257,7 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
|
||||
params := map[string]any{
|
||||
"organization": repo.Organization,
|
||||
"repository": repo.Repository,
|
||||
"repository_id": repo.RepositoryId,
|
||||
"url": repo.URL,
|
||||
"branch": repo.Branch,
|
||||
"sha": repo.SHA,
|
||||
@@ -273,23 +278,36 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
|
||||
}
|
||||
|
||||
func (g *SCMProviderGenerator) githubProvider(ctx context.Context, github *argoprojiov1alpha1.SCMProviderGeneratorGithub, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (scm_provider.SCMProviderService, error) {
|
||||
var metricsCtx *services.MetricsContext
|
||||
var httpClient *http.Client
|
||||
|
||||
if g.enableGitHubAPIMetrics {
|
||||
metricsCtx = &services.MetricsContext{
|
||||
AppSetNamespace: applicationSetInfo.Namespace,
|
||||
AppSetName: applicationSetInfo.Name,
|
||||
}
|
||||
httpClient = services.NewGitHubMetricsClient(metricsCtx)
|
||||
}
|
||||
|
||||
if github.AppSecretName != "" {
|
||||
auth, err := g.GitHubApps.GetAuthSecret(ctx, github.AppSecretName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching Github app secret: %w", err)
|
||||
}
|
||||
|
||||
return scm_provider.NewGithubAppProviderFor(
|
||||
*auth,
|
||||
github.Organization,
|
||||
github.API,
|
||||
github.AllBranches,
|
||||
)
|
||||
if g.enableGitHubAPIMetrics {
|
||||
return scm_provider.NewGithubAppProviderFor(*auth, github.Organization, github.API, github.AllBranches, httpClient)
|
||||
}
|
||||
return scm_provider.NewGithubAppProviderFor(*auth, github.Organization, github.API, github.AllBranches)
|
||||
}
|
||||
|
||||
token, err := utils.GetSecretRef(ctx, g.client, github.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching Github token: %w", err)
|
||||
}
|
||||
|
||||
if g.enableGitHubAPIMetrics {
|
||||
return scm_provider.NewGithubProvider(github.Organization, token, github.API, github.AllBranches, httpClient)
|
||||
}
|
||||
return scm_provider.NewGithubProvider(github.Organization, token, github.API, github.AllBranches)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
)
|
||||
|
||||
func TestSCMProviderGenerateParams(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
repos []*scm_provider.Repository
|
||||
@@ -25,6 +27,7 @@ func TestSCMProviderGenerateParams(t *testing.T) {
|
||||
{
|
||||
Organization: "myorg",
|
||||
Repository: "repo1",
|
||||
RepositoryId: 190320251,
|
||||
URL: "git@github.com:myorg/repo1.git",
|
||||
Branch: "main",
|
||||
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
|
||||
@@ -33,6 +36,7 @@ func TestSCMProviderGenerateParams(t *testing.T) {
|
||||
{
|
||||
Organization: "myorg",
|
||||
Repository: "repo2",
|
||||
RepositoryId: 190320252,
|
||||
URL: "git@github.com:myorg/repo2.git",
|
||||
Branch: "main",
|
||||
SHA: "59d0",
|
||||
@@ -42,6 +46,7 @@ func TestSCMProviderGenerateParams(t *testing.T) {
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "repo1",
|
||||
"repository_id": 190320251,
|
||||
"url": "git@github.com:myorg/repo1.git",
|
||||
"branch": "main",
|
||||
"branchNormalized": "main",
|
||||
@@ -53,6 +58,7 @@ func TestSCMProviderGenerateParams(t *testing.T) {
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "repo2",
|
||||
"repository_id": 190320252,
|
||||
"url": "git@github.com:myorg/repo2.git",
|
||||
"branch": "main",
|
||||
"branchNormalized": "main",
|
||||
@@ -69,6 +75,7 @@ func TestSCMProviderGenerateParams(t *testing.T) {
|
||||
{
|
||||
Organization: "myorg",
|
||||
Repository: "repo3",
|
||||
RepositoryId: 190320253,
|
||||
URL: "git@github.com:myorg/repo3.git",
|
||||
Branch: "main",
|
||||
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
|
||||
@@ -83,6 +90,7 @@ func TestSCMProviderGenerateParams(t *testing.T) {
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "repo3",
|
||||
"repository_id": 190320253,
|
||||
"url": "git@github.com:myorg/repo3.git",
|
||||
"branch": "main",
|
||||
"branchNormalized": "main",
|
||||
@@ -95,6 +103,52 @@ func TestSCMProviderGenerateParams(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Repos with and without id",
|
||||
repos: []*scm_provider.Repository{
|
||||
{
|
||||
Organization: "myorg",
|
||||
Repository: "repo4",
|
||||
RepositoryId: "idaz09",
|
||||
URL: "git@github.com:myorg/repo4.git",
|
||||
Branch: "main",
|
||||
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
|
||||
},
|
||||
{
|
||||
Organization: "myorg",
|
||||
Repository: "repo5",
|
||||
URL: "git@github.com:myorg/repo5.git",
|
||||
Branch: "main",
|
||||
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
|
||||
},
|
||||
},
|
||||
expected: []map[string]any{
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "repo4",
|
||||
"repository_id": "idaz09",
|
||||
"url": "git@github.com:myorg/repo4.git",
|
||||
"branch": "main",
|
||||
"branchNormalized": "main",
|
||||
"sha": "0bc57212c3cbbec69d20b34c507284bd300def5b",
|
||||
"short_sha": "0bc57212",
|
||||
"short_sha_7": "0bc5721",
|
||||
"labels": "",
|
||||
},
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "repo5",
|
||||
"repository_id": nil,
|
||||
"url": "git@github.com:myorg/repo5.git",
|
||||
"branch": "main",
|
||||
"branchNormalized": "main",
|
||||
"sha": "0bc57212c3cbbec69d20b34c507284bd300def5b",
|
||||
"short_sha": "0bc57212",
|
||||
"short_sha_7": "0bc5721",
|
||||
"labels": "",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range cases {
|
||||
@@ -133,6 +187,8 @@ func TestSCMProviderGenerateParams(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAllowedSCMProvider(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
providerConfig *argoprojiov1alpha1.SCMProviderGenerator
|
||||
|
||||
@@ -10,15 +10,15 @@ import (
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/services"
|
||||
)
|
||||
|
||||
func GetGenerators(ctx context.Context, c client.Client, k8sClient kubernetes.Interface, namespace string, argoCDService services.Repos, dynamicClient dynamic.Interface, scmConfig SCMConfig) map[string]Generator {
|
||||
func GetGenerators(ctx context.Context, c client.Client, k8sClient kubernetes.Interface, controllerNamespace string, argoCDService services.Repos, dynamicClient dynamic.Interface, scmConfig SCMConfig) map[string]Generator {
|
||||
terminalGenerators := map[string]Generator{
|
||||
"List": NewListGenerator(),
|
||||
"Clusters": NewClusterGenerator(ctx, c, k8sClient, namespace),
|
||||
"Git": NewGitGenerator(argoCDService, namespace),
|
||||
"Clusters": NewClusterGenerator(ctx, c, k8sClient, controllerNamespace),
|
||||
"Git": NewGitGenerator(argoCDService, controllerNamespace),
|
||||
"SCMProvider": NewSCMProviderGenerator(c, scmConfig),
|
||||
"ClusterDecisionResource": NewDuckTypeGenerator(ctx, dynamicClient, k8sClient, namespace),
|
||||
"ClusterDecisionResource": NewDuckTypeGenerator(ctx, dynamicClient, k8sClient, controllerNamespace),
|
||||
"PullRequest": NewPullRequestGenerator(c, scmConfig),
|
||||
"Plugin": NewPluginGenerator(ctx, c, k8sClient, namespace),
|
||||
"Plugin": NewPluginGenerator(c, controllerNamespace),
|
||||
}
|
||||
|
||||
nestedGenerators := map[string]Generator{
|
||||
|
||||
@@ -57,7 +57,7 @@ func TestValueInterpolation(t *testing.T) {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
err := appendTemplatedValues(testCase.values, testCase.params, false, nil)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, testCase.expected, testCase.params)
|
||||
assert.Equal(t, testCase.expected, testCase.params)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func TestValueInterpolationWithGoTemplating(t *testing.T) {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
err := appendTemplatedValues(testCase.values, testCase.params, true, nil)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, testCase.expected, testCase.params)
|
||||
assert.Equal(t, testCase.expected, testCase.params)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +58,7 @@ func NewApplicationsetMetrics(appsetLister applisters.ApplicationSetLister, apps
|
||||
metrics.Registry.MustRegister(reconcileHistogram)
|
||||
metrics.Registry.MustRegister(appsetCollector)
|
||||
|
||||
kubectlMetricsServer := kubectl.NewKubectlMetrics()
|
||||
kubectlMetricsServer.RegisterWithClientGo()
|
||||
kubectl.RegisterWithClientGo()
|
||||
kubectl.RegisterWithPrometheus(metrics.Registry)
|
||||
|
||||
return ApplicationsetMetrics{
|
||||
|
||||
@@ -174,7 +174,7 @@ func TestApplicationsetCollector(t *testing.T) {
|
||||
appsetCollector := newAppsetCollector(utils.NewAppsetLister(client), collectedLabels, filter)
|
||||
|
||||
metrics.Registry.MustRegister(appsetCollector)
|
||||
req, err := http.NewRequest(http.MethodGet, "/metrics", nil)
|
||||
req, err := http.NewRequest(http.MethodGet, "/metrics", http.NoBody)
|
||||
require.NoError(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
handler := promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{})
|
||||
@@ -216,7 +216,7 @@ func TestObserveReconcile(t *testing.T) {
|
||||
|
||||
appsetMetrics := NewApplicationsetMetrics(utils.NewAppsetLister(client), collectedLabels, filter)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "/metrics", nil)
|
||||
req, err := http.NewRequest(http.MethodGet, "/metrics", http.NoBody)
|
||||
require.NoError(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
handler := promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{})
|
||||
|
||||
241
applicationset/services/github_metrics.go
Normal file
241
applicationset/services/github_metrics.go
Normal file
@@ -0,0 +1,241 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"sigs.k8s.io/controller-runtime/pkg/metrics"
|
||||
)
|
||||
|
||||
// Doc for the GitHub API rate limit headers:
|
||||
// https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#checking-the-status-of-your-rate-limit
|
||||
|
||||
// Metric names as constants
|
||||
const (
|
||||
githubAPIRequestTotalMetricName = "argocd_github_api_requests_total"
|
||||
githubAPIRequestDurationMetricName = "argocd_github_api_request_duration_seconds"
|
||||
githubAPIRateLimitRemainingMetricName = "argocd_github_api_rate_limit_remaining"
|
||||
githubAPIRateLimitLimitMetricName = "argocd_github_api_rate_limit_limit"
|
||||
githubAPIRateLimitResetMetricName = "argocd_github_api_rate_limit_reset_seconds"
|
||||
githubAPIRateLimitUsedMetricName = "argocd_github_api_rate_limit_used"
|
||||
)
|
||||
|
||||
// GitHubMetrics groups all metric vectors for easier injection and registration
|
||||
type GitHubMetrics struct {
|
||||
RequestTotal *prometheus.CounterVec
|
||||
RequestDuration *prometheus.HistogramVec
|
||||
RateLimitRemaining *prometheus.GaugeVec
|
||||
RateLimitLimit *prometheus.GaugeVec
|
||||
RateLimitReset *prometheus.GaugeVec
|
||||
RateLimitUsed *prometheus.GaugeVec
|
||||
}
|
||||
|
||||
// Factory for a new set of GitHub metrics (for tests or custom registries)
|
||||
func NewGitHubMetrics() *GitHubMetrics {
|
||||
return &GitHubMetrics{
|
||||
RequestTotal: NewGitHubAPIRequestTotal(),
|
||||
RequestDuration: NewGitHubAPIRequestDuration(),
|
||||
RateLimitRemaining: NewGitHubAPIRateLimitRemaining(),
|
||||
RateLimitLimit: NewGitHubAPIRateLimitLimit(),
|
||||
RateLimitReset: NewGitHubAPIRateLimitReset(),
|
||||
RateLimitUsed: NewGitHubAPIRateLimitUsed(),
|
||||
}
|
||||
}
|
||||
|
||||
// Factory functions for each metric vector
|
||||
func NewGitHubAPIRequestTotal() *prometheus.CounterVec {
|
||||
return prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: githubAPIRequestTotalMetricName,
|
||||
Help: "Total number of GitHub API requests",
|
||||
},
|
||||
[]string{"method", "endpoint", "status", "appset_namespace", "appset_name"},
|
||||
)
|
||||
}
|
||||
|
||||
func NewGitHubAPIRequestDuration() *prometheus.HistogramVec {
|
||||
return prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: githubAPIRequestDurationMetricName,
|
||||
Help: "GitHub API request duration in seconds",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
},
|
||||
[]string{"method", "endpoint", "appset_namespace", "appset_name"},
|
||||
)
|
||||
}
|
||||
|
||||
func NewGitHubAPIRateLimitRemaining() *prometheus.GaugeVec {
|
||||
return prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: githubAPIRateLimitRemainingMetricName,
|
||||
Help: "The number of requests remaining in the current rate limit window",
|
||||
},
|
||||
[]string{"endpoint", "appset_namespace", "appset_name", "resource"},
|
||||
)
|
||||
}
|
||||
|
||||
func NewGitHubAPIRateLimitLimit() *prometheus.GaugeVec {
|
||||
return prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: githubAPIRateLimitLimitMetricName,
|
||||
Help: "The maximum number of requests that you can make per hour",
|
||||
},
|
||||
[]string{"endpoint", "appset_namespace", "appset_name", "resource"},
|
||||
)
|
||||
}
|
||||
|
||||
func NewGitHubAPIRateLimitReset() *prometheus.GaugeVec {
|
||||
return prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: githubAPIRateLimitResetMetricName,
|
||||
Help: "The time left till the current rate limit window resets, in seconds",
|
||||
},
|
||||
[]string{"endpoint", "appset_namespace", "appset_name", "resource"},
|
||||
)
|
||||
}
|
||||
|
||||
func NewGitHubAPIRateLimitUsed() *prometheus.GaugeVec {
|
||||
return prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: githubAPIRateLimitUsedMetricName,
|
||||
Help: "The number of requests used in the current rate limit window",
|
||||
},
|
||||
[]string{"endpoint", "appset_namespace", "appset_name", "resource"},
|
||||
)
|
||||
}
|
||||
|
||||
// Global metrics (registered with the default registry)
|
||||
var globalGitHubMetrics = NewGitHubMetrics()
|
||||
|
||||
func init() {
|
||||
log.Debug("Registering GitHub API AppSet metrics")
|
||||
metrics.Registry.MustRegister(globalGitHubMetrics.RequestTotal)
|
||||
metrics.Registry.MustRegister(globalGitHubMetrics.RequestDuration)
|
||||
metrics.Registry.MustRegister(globalGitHubMetrics.RateLimitRemaining)
|
||||
metrics.Registry.MustRegister(globalGitHubMetrics.RateLimitLimit)
|
||||
metrics.Registry.MustRegister(globalGitHubMetrics.RateLimitReset)
|
||||
metrics.Registry.MustRegister(globalGitHubMetrics.RateLimitUsed)
|
||||
}
|
||||
|
||||
type MetricsContext struct {
|
||||
AppSetNamespace string
|
||||
AppSetName string
|
||||
}
|
||||
|
||||
// GitHubMetricsTransport is a custom http.RoundTripper that collects GitHub API metrics
|
||||
type GitHubMetricsTransport struct {
|
||||
transport http.RoundTripper
|
||||
metricsContext *MetricsContext
|
||||
metrics *GitHubMetrics
|
||||
}
|
||||
|
||||
// RoundTrip implements http.RoundTripper interface and collects metrics along with debug logging
|
||||
func (t *GitHubMetricsTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
endpoint := req.URL.Path
|
||||
method := req.Method
|
||||
|
||||
appsetNamespace := "unknown"
|
||||
appsetName := "unknown"
|
||||
|
||||
if t.metricsContext != nil {
|
||||
appsetNamespace = t.metricsContext.AppSetNamespace
|
||||
appsetName = t.metricsContext.AppSetName
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"method": method,
|
||||
"endpoint": endpoint,
|
||||
"applicationset": map[string]string{"name": appsetName, "namespace": appsetNamespace},
|
||||
}).Debugf("Invoking GitHub API")
|
||||
|
||||
startTime := time.Now()
|
||||
resp, err := t.transport.RoundTrip(req)
|
||||
duration := time.Since(startTime)
|
||||
|
||||
// Record metrics
|
||||
t.metrics.RequestDuration.WithLabelValues(method, endpoint, appsetNamespace, appsetName).Observe(duration.Seconds())
|
||||
|
||||
status := "0"
|
||||
if resp != nil {
|
||||
status = strconv.Itoa(resp.StatusCode)
|
||||
}
|
||||
t.metrics.RequestTotal.WithLabelValues(method, endpoint, status, appsetNamespace, appsetName).Inc()
|
||||
|
||||
if resp != nil {
|
||||
resetHumanReadableTime := ""
|
||||
remainingInt := 0
|
||||
limitInt := 0
|
||||
usedInt := 0
|
||||
resource := resp.Header.Get("X-RateLimit-Resource")
|
||||
|
||||
// Record rate limit metrics if available
|
||||
if resetTime := resp.Header.Get("X-RateLimit-Reset"); resetTime != "" {
|
||||
if resetUnix, err := strconv.ParseInt(resetTime, 10, 64); err == nil {
|
||||
// Calculate seconds until reset (reset timestamp - current time)
|
||||
secondsUntilReset := resetUnix - time.Now().Unix()
|
||||
t.metrics.RateLimitReset.WithLabelValues(endpoint, appsetNamespace, appsetName, resource).Set(float64(secondsUntilReset))
|
||||
resetHumanReadableTime = time.Unix(resetUnix, 0).Local().Format("2006-01-02 15:04:05 MST")
|
||||
}
|
||||
}
|
||||
if remaining := resp.Header.Get("X-RateLimit-Remaining"); remaining != "" {
|
||||
if remainingInt, err = strconv.Atoi(remaining); err == nil {
|
||||
t.metrics.RateLimitRemaining.WithLabelValues(endpoint, appsetNamespace, appsetName, resource).Set(float64(remainingInt))
|
||||
}
|
||||
}
|
||||
if limit := resp.Header.Get("X-RateLimit-Limit"); limit != "" {
|
||||
if limitInt, err = strconv.Atoi(limit); err == nil {
|
||||
t.metrics.RateLimitLimit.WithLabelValues(endpoint, appsetNamespace, appsetName, resource).Set(float64(limitInt))
|
||||
}
|
||||
}
|
||||
if used := resp.Header.Get("X-RateLimit-Used"); used != "" {
|
||||
if usedInt, err = strconv.Atoi(used); err == nil {
|
||||
t.metrics.RateLimitUsed.WithLabelValues(endpoint, appsetNamespace, appsetName, resource).Set(float64(usedInt))
|
||||
}
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"endpoint": endpoint,
|
||||
"reset": resetHumanReadableTime,
|
||||
"remaining": remainingInt,
|
||||
"limit": limitInt,
|
||||
"used": usedInt,
|
||||
"resource": resource,
|
||||
"applicationset": map[string]string{"name": appsetName, "namespace": appsetNamespace},
|
||||
}).Debugf("GitHub API rate limit info")
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// Full constructor (for tests and advanced use)
|
||||
func NewGitHubMetricsTransport(
|
||||
transport http.RoundTripper,
|
||||
metricsContext *MetricsContext,
|
||||
metrics *GitHubMetrics,
|
||||
) *GitHubMetricsTransport {
|
||||
return &GitHubMetricsTransport{
|
||||
transport: transport,
|
||||
metricsContext: metricsContext,
|
||||
metrics: metrics,
|
||||
}
|
||||
}
|
||||
|
||||
// Default constructor
|
||||
func NewDefaultGitHubMetricsTransport(transport http.RoundTripper, metricsContext *MetricsContext) *GitHubMetricsTransport {
|
||||
return NewGitHubMetricsTransport(
|
||||
transport,
|
||||
metricsContext,
|
||||
globalGitHubMetrics,
|
||||
)
|
||||
}
|
||||
|
||||
// NewGitHubMetricsClient wraps an http.Client with metrics middleware
|
||||
func NewGitHubMetricsClient(metricsContext *MetricsContext) *http.Client {
|
||||
log.Debug("Creating new GitHub metrics client")
|
||||
return &http.Client{
|
||||
Transport: NewDefaultGitHubMetricsTransport(http.DefaultTransport, metricsContext),
|
||||
}
|
||||
}
|
||||
221
applicationset/services/github_metrics_test.go
Normal file
221
applicationset/services/github_metrics_test.go
Normal file
@@ -0,0 +1,221 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type Metric struct {
|
||||
name string
|
||||
labels []string
|
||||
value string
|
||||
}
|
||||
|
||||
var (
|
||||
endpointLabel = "endpoint=\"/api/test\""
|
||||
URL = "/api/test"
|
||||
appsetNamespaceLabel = "appset_namespace=\"test-ns\""
|
||||
appsetNamespace = "test-ns"
|
||||
appsetName = "test-appset"
|
||||
appsetNameLabel = "appset_name=\"test-appset\""
|
||||
resourceLabel = "resource=\"core\""
|
||||
|
||||
rateLimitMetrics = []Metric{
|
||||
{
|
||||
name: githubAPIRateLimitRemainingMetricName,
|
||||
labels: []string{endpointLabel, appsetNamespaceLabel, appsetNameLabel, resourceLabel},
|
||||
value: "42",
|
||||
},
|
||||
{
|
||||
name: githubAPIRateLimitLimitMetricName,
|
||||
labels: []string{endpointLabel, appsetNamespaceLabel, appsetNameLabel, resourceLabel},
|
||||
value: "100",
|
||||
},
|
||||
{
|
||||
name: githubAPIRateLimitUsedMetricName,
|
||||
labels: []string{endpointLabel, appsetNamespaceLabel, appsetNameLabel, resourceLabel},
|
||||
value: "58",
|
||||
},
|
||||
{
|
||||
name: githubAPIRateLimitResetMetricName,
|
||||
labels: []string{endpointLabel, appsetNamespaceLabel, appsetNameLabel, resourceLabel},
|
||||
value: "1",
|
||||
},
|
||||
}
|
||||
successRequestMetrics = Metric{
|
||||
name: githubAPIRequestTotalMetricName,
|
||||
labels: []string{"method=\"GET\"", endpointLabel, "status=\"201\"", appsetNamespaceLabel, appsetNameLabel},
|
||||
value: "1",
|
||||
}
|
||||
failureRequestMetrics = Metric{
|
||||
name: githubAPIRequestTotalMetricName,
|
||||
labels: []string{"method=\"GET\"", endpointLabel, "status=\"0\"", appsetNamespaceLabel, appsetNameLabel},
|
||||
value: "1",
|
||||
}
|
||||
)
|
||||
|
||||
func TestGitHubMetrics_CollectorApproach_Success(t *testing.T) {
|
||||
metrics := NewGitHubMetrics()
|
||||
reg := prometheus.NewRegistry()
|
||||
reg.MustRegister(
|
||||
metrics.RequestTotal,
|
||||
metrics.RequestDuration,
|
||||
metrics.RateLimitRemaining,
|
||||
metrics.RateLimitLimit,
|
||||
metrics.RateLimitReset,
|
||||
metrics.RateLimitUsed,
|
||||
)
|
||||
|
||||
// Setup a fake HTTP server
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("X-RateLimit-Reset", strconv.FormatInt(time.Now().Unix()+1, 10))
|
||||
w.Header().Set("X-RateLimit-Remaining", "42")
|
||||
w.Header().Set("X-RateLimit-Limit", "100")
|
||||
w.Header().Set("X-RateLimit-Used", "58")
|
||||
w.Header().Set("X-RateLimit-Resource", "core")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
_, _ = w.Write([]byte("ok"))
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
metricsCtx := &MetricsContext{AppSetNamespace: appsetNamespace, AppSetName: appsetName}
|
||||
client := &http.Client{
|
||||
Transport: NewGitHubMetricsTransport(
|
||||
http.DefaultTransport,
|
||||
metricsCtx,
|
||||
metrics,
|
||||
),
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, ts.URL+URL, http.NoBody)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
// Expose and scrape metrics
|
||||
handler := promhttp.HandlerFor(reg, promhttp.HandlerOpts{})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
resp, err = http.Get(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to scrape metrics: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
metricsOutput := string(body)
|
||||
|
||||
sort.Strings(successRequestMetrics.labels)
|
||||
assert.Contains(t, metricsOutput, successRequestMetrics.name+"{"+strings.Join(successRequestMetrics.labels, ",")+"} "+successRequestMetrics.value)
|
||||
|
||||
for _, metric := range rateLimitMetrics {
|
||||
sort.Strings(metric.labels)
|
||||
assert.Contains(t, metricsOutput, metric.name+"{"+strings.Join(metric.labels, ",")+"} "+metric.value)
|
||||
}
|
||||
}
|
||||
|
||||
type RoundTripperFunc func(*http.Request) (*http.Response, error)
|
||||
|
||||
func (f RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
|
||||
|
||||
func TestGitHubMetrics_CollectorApproach_NoRateLimitMetricsOnNilResponse(t *testing.T) {
|
||||
metrics := NewGitHubMetrics()
|
||||
reg := prometheus.NewRegistry()
|
||||
reg.MustRegister(
|
||||
metrics.RequestTotal,
|
||||
metrics.RequestDuration,
|
||||
metrics.RateLimitRemaining,
|
||||
metrics.RateLimitLimit,
|
||||
metrics.RateLimitReset,
|
||||
metrics.RateLimitUsed,
|
||||
)
|
||||
|
||||
client := &http.Client{
|
||||
Transport: &GitHubMetricsTransport{
|
||||
transport: RoundTripperFunc(func(*http.Request) (*http.Response, error) {
|
||||
return nil, http.ErrServerClosed
|
||||
}),
|
||||
metricsContext: &MetricsContext{AppSetNamespace: appsetNamespace, AppSetName: appsetName},
|
||||
metrics: metrics,
|
||||
},
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, URL, http.NoBody)
|
||||
_, _ = client.Do(req)
|
||||
|
||||
handler := promhttp.HandlerFor(reg, promhttp.HandlerOpts{})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
resp, err := http.Get(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to scrape metrics: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
metricsOutput := string(body)
|
||||
|
||||
// Verify request metric exists with status "0"
|
||||
sort.Strings(failureRequestMetrics.labels)
|
||||
assert.Contains(t, metricsOutput, failureRequestMetrics.name+"{"+strings.Join(failureRequestMetrics.labels, ",")+"} "+failureRequestMetrics.value)
|
||||
|
||||
// Verify rate limit metrics don't exist
|
||||
for _, metric := range rateLimitMetrics {
|
||||
sort.Strings(metric.labels)
|
||||
assert.NotContains(t, metricsOutput, metric.name+"{"+strings.Join(metric.labels, ",")+"} "+metric.value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewGitHubMetricsClient(t *testing.T) {
|
||||
// Test cases
|
||||
testCases := []struct {
|
||||
name string
|
||||
metricsCtx *MetricsContext
|
||||
}{
|
||||
{
|
||||
name: "with metrics context",
|
||||
metricsCtx: &MetricsContext{
|
||||
AppSetNamespace: appsetNamespace,
|
||||
AppSetName: appsetName,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with nil metrics context",
|
||||
metricsCtx: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Create client
|
||||
client := NewGitHubMetricsClient(tc.metricsCtx)
|
||||
|
||||
// Assert client is not nil
|
||||
assert.NotNil(t, client)
|
||||
|
||||
// Assert transport is properly configured
|
||||
transport, ok := client.Transport.(*GitHubMetricsTransport)
|
||||
assert.True(t, ok, "Transport should be GitHubMetricsTransport")
|
||||
|
||||
// Verify transport configuration
|
||||
assert.Equal(t, tc.metricsCtx, transport.metricsContext)
|
||||
assert.NotNil(t, transport.metrics, "Metrics should not be nil")
|
||||
assert.Equal(t, http.DefaultTransport, transport.transport, "Base transport should be http.DefaultTransport")
|
||||
|
||||
// Verify metrics are global metrics
|
||||
assert.Equal(t, globalGitHubMetrics, transport.metrics, "Should use global metrics")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,27 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/bradleyfalzon/ghinstallation/v2"
|
||||
"github.com/google/go-github/v66/github"
|
||||
"github.com/google/go-github/v69/github"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/services/github_app_auth"
|
||||
appsetutils "github.com/argoproj/argo-cd/v3/applicationset/utils"
|
||||
)
|
||||
|
||||
func getOptionalHTTPClientAndTransport(optionalHTTPClient ...*http.Client) (*http.Client, http.RoundTripper) {
|
||||
httpClient := appsetutils.GetOptionalHTTPClient(optionalHTTPClient...)
|
||||
if len(optionalHTTPClient) > 0 && optionalHTTPClient[0] != nil && optionalHTTPClient[0].Transport != nil {
|
||||
// will either use the provided custom httpClient and it's transport
|
||||
return httpClient, optionalHTTPClient[0].Transport
|
||||
}
|
||||
// or the default httpClient and transport
|
||||
return httpClient, http.DefaultTransport
|
||||
}
|
||||
|
||||
// Client builds a github client for the given app authentication.
|
||||
func Client(g github_app_auth.Authentication, url string) (*github.Client, error) {
|
||||
rt, err := ghinstallation.New(http.DefaultTransport, g.Id, g.InstallationId, []byte(g.PrivateKey))
|
||||
func Client(g github_app_auth.Authentication, url string, optionalHTTPClient ...*http.Client) (*github.Client, error) {
|
||||
httpClient, transport := getOptionalHTTPClientAndTransport(optionalHTTPClient...)
|
||||
|
||||
rt, err := ghinstallation.New(transport, g.Id, g.InstallationId, []byte(g.PrivateKey))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create github app install: %w", err)
|
||||
}
|
||||
@@ -20,13 +33,12 @@ func Client(g github_app_auth.Authentication, url string) (*github.Client, error
|
||||
url = g.EnterpriseBaseURL
|
||||
}
|
||||
var client *github.Client
|
||||
httpClient.Transport = rt
|
||||
if url == "" {
|
||||
httpClient := http.Client{Transport: rt}
|
||||
client = github.NewClient(&httpClient)
|
||||
client = github.NewClient(httpClient)
|
||||
} else {
|
||||
rt.BaseURL = url
|
||||
httpClient := http.Client{Transport: rt}
|
||||
client, err = github.NewClient(&httpClient).WithEnterpriseURLs(url, url)
|
||||
client, err = github.NewClient(httpClient).WithEnterpriseURLs(url, url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create github enterprise client: %w", err)
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ func (c *Client) NewRequestWithContext(ctx context.Context, method, path string,
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
if len(c.token) != 0 {
|
||||
if c.token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.token)
|
||||
}
|
||||
|
||||
|
||||
274
applicationset/services/mocks/Repos.go
generated
274
applicationset/services/mocks/Repos.go
generated
@@ -1,78 +1,15 @@
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
"context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// Repos is an autogenerated mock type for the Repos type
|
||||
type Repos struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// GetDirectories provides a mock function with given fields: ctx, repoURL, revision, project, noRevisionCache, verifyCommit
|
||||
func (_m *Repos) GetDirectories(ctx context.Context, repoURL string, revision string, project string, noRevisionCache bool, verifyCommit bool) ([]string, error) {
|
||||
ret := _m.Called(ctx, repoURL, revision, project, noRevisionCache, verifyCommit)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetDirectories")
|
||||
}
|
||||
|
||||
var r0 []string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool, bool) ([]string, error)); ok {
|
||||
return rf(ctx, repoURL, revision, project, noRevisionCache, verifyCommit)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool, bool) []string); ok {
|
||||
r0 = rf(ctx, repoURL, revision, project, noRevisionCache, verifyCommit)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]string)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, bool, bool) error); ok {
|
||||
r1 = rf(ctx, repoURL, revision, project, noRevisionCache, verifyCommit)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetFiles provides a mock function with given fields: ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit
|
||||
func (_m *Repos) GetFiles(ctx context.Context, repoURL string, revision string, project string, pattern string, noRevisionCache bool, verifyCommit bool) (map[string][]byte, error) {
|
||||
ret := _m.Called(ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetFiles")
|
||||
}
|
||||
|
||||
var r0 map[string][]byte
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, bool, bool) (map[string][]byte, error)); ok {
|
||||
return rf(ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, bool, bool) map[string][]byte); ok {
|
||||
r0 = rf(ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string][]byte)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string, bool, bool) error); ok {
|
||||
r1 = rf(ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// NewRepos creates a new instance of Repos. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewRepos(t interface {
|
||||
@@ -86,3 +23,206 @@ func NewRepos(t interface {
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
// Repos is an autogenerated mock type for the Repos type
|
||||
type Repos struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type Repos_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *Repos) EXPECT() *Repos_Expecter {
|
||||
return &Repos_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// GetDirectories provides a mock function for the type Repos
|
||||
func (_mock *Repos) GetDirectories(ctx context.Context, repoURL string, revision string, project string, noRevisionCache bool, verifyCommit bool) ([]string, error) {
|
||||
ret := _mock.Called(ctx, repoURL, revision, project, noRevisionCache, verifyCommit)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetDirectories")
|
||||
}
|
||||
|
||||
var r0 []string
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string, bool, bool) ([]string, error)); ok {
|
||||
return returnFunc(ctx, repoURL, revision, project, noRevisionCache, verifyCommit)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string, bool, bool) []string); ok {
|
||||
r0 = returnFunc(ctx, repoURL, revision, project, noRevisionCache, verifyCommit)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]string)
|
||||
}
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, string, bool, bool) error); ok {
|
||||
r1 = returnFunc(ctx, repoURL, revision, project, noRevisionCache, verifyCommit)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repos_GetDirectories_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetDirectories'
|
||||
type Repos_GetDirectories_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetDirectories is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - repoURL string
|
||||
// - revision string
|
||||
// - project string
|
||||
// - noRevisionCache bool
|
||||
// - verifyCommit bool
|
||||
func (_e *Repos_Expecter) GetDirectories(ctx interface{}, repoURL interface{}, revision interface{}, project interface{}, noRevisionCache interface{}, verifyCommit interface{}) *Repos_GetDirectories_Call {
|
||||
return &Repos_GetDirectories_Call{Call: _e.mock.On("GetDirectories", ctx, repoURL, revision, project, noRevisionCache, verifyCommit)}
|
||||
}
|
||||
|
||||
func (_c *Repos_GetDirectories_Call) Run(run func(ctx context.Context, repoURL string, revision string, project string, noRevisionCache bool, verifyCommit bool)) *Repos_GetDirectories_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 string
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(string)
|
||||
}
|
||||
var arg2 string
|
||||
if args[2] != nil {
|
||||
arg2 = args[2].(string)
|
||||
}
|
||||
var arg3 string
|
||||
if args[3] != nil {
|
||||
arg3 = args[3].(string)
|
||||
}
|
||||
var arg4 bool
|
||||
if args[4] != nil {
|
||||
arg4 = args[4].(bool)
|
||||
}
|
||||
var arg5 bool
|
||||
if args[5] != nil {
|
||||
arg5 = args[5].(bool)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
arg3,
|
||||
arg4,
|
||||
arg5,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repos_GetDirectories_Call) Return(strings []string, err error) *Repos_GetDirectories_Call {
|
||||
_c.Call.Return(strings, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repos_GetDirectories_Call) RunAndReturn(run func(ctx context.Context, repoURL string, revision string, project string, noRevisionCache bool, verifyCommit bool) ([]string, error)) *Repos_GetDirectories_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetFiles provides a mock function for the type Repos
|
||||
func (_mock *Repos) GetFiles(ctx context.Context, repoURL string, revision string, project string, pattern string, noRevisionCache bool, verifyCommit bool) (map[string][]byte, error) {
|
||||
ret := _mock.Called(ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetFiles")
|
||||
}
|
||||
|
||||
var r0 map[string][]byte
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string, string, bool, bool) (map[string][]byte, error)); ok {
|
||||
return returnFunc(ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string, string, bool, bool) map[string][]byte); ok {
|
||||
r0 = returnFunc(ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string][]byte)
|
||||
}
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, string, string, bool, bool) error); ok {
|
||||
r1 = returnFunc(ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repos_GetFiles_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFiles'
|
||||
type Repos_GetFiles_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetFiles is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - repoURL string
|
||||
// - revision string
|
||||
// - project string
|
||||
// - pattern string
|
||||
// - noRevisionCache bool
|
||||
// - verifyCommit bool
|
||||
func (_e *Repos_Expecter) GetFiles(ctx interface{}, repoURL interface{}, revision interface{}, project interface{}, pattern interface{}, noRevisionCache interface{}, verifyCommit interface{}) *Repos_GetFiles_Call {
|
||||
return &Repos_GetFiles_Call{Call: _e.mock.On("GetFiles", ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit)}
|
||||
}
|
||||
|
||||
func (_c *Repos_GetFiles_Call) Run(run func(ctx context.Context, repoURL string, revision string, project string, pattern string, noRevisionCache bool, verifyCommit bool)) *Repos_GetFiles_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 string
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(string)
|
||||
}
|
||||
var arg2 string
|
||||
if args[2] != nil {
|
||||
arg2 = args[2].(string)
|
||||
}
|
||||
var arg3 string
|
||||
if args[3] != nil {
|
||||
arg3 = args[3].(string)
|
||||
}
|
||||
var arg4 string
|
||||
if args[4] != nil {
|
||||
arg4 = args[4].(string)
|
||||
}
|
||||
var arg5 bool
|
||||
if args[5] != nil {
|
||||
arg5 = args[5].(bool)
|
||||
}
|
||||
var arg6 bool
|
||||
if args[6] != nil {
|
||||
arg6 = args[6].(bool)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
arg3,
|
||||
arg4,
|
||||
arg5,
|
||||
arg6,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repos_GetFiles_Call) Return(stringToBytes map[string][]byte, err error) *Repos_GetFiles_Call {
|
||||
_c.Call.Return(stringToBytes, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repos_GetFiles_Call) RunAndReturn(run func(ctx context.Context, repoURL string, revision string, project string, pattern string, noRevisionCache bool, verifyCommit bool) (map[string][]byte, error)) *Repos_GetFiles_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops"
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops/core"
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops/git"
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops/v7"
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/core"
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/git"
|
||||
)
|
||||
|
||||
const AZURE_DEVOPS_DEFAULT_URL = "https://dev.azure.com"
|
||||
|
||||
@@ -4,10 +4,9 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops/webapi"
|
||||
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops/core"
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops/git"
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/core"
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/git"
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/webapi"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -17,10 +17,19 @@ type BitbucketCloudService struct {
|
||||
}
|
||||
|
||||
type BitbucketCloudPullRequest struct {
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Source BitbucketCloudPullRequestSource `json:"source"`
|
||||
Author BitbucketCloudPullRequestAuthor `json:"author"`
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Source BitbucketCloudPullRequestSource `json:"source"`
|
||||
Author BitbucketCloudPullRequestAuthor `json:"author"`
|
||||
Destination BitbucketCloudPullRequestDestination `json:"destination"`
|
||||
}
|
||||
|
||||
type BitbucketCloudPullRequestDestination struct {
|
||||
Branch BitbucketCloudPullRequestDestinationBranch `json:"branch"`
|
||||
}
|
||||
|
||||
type BitbucketCloudPullRequestDestinationBranch struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type BitbucketCloudPullRequestSource struct {
|
||||
@@ -136,11 +145,12 @@ func (b *BitbucketCloudService) List(_ context.Context) ([]*PullRequest, error)
|
||||
pullRequests := []*PullRequest{}
|
||||
for _, pull := range pulls {
|
||||
pullRequests = append(pullRequests, &PullRequest{
|
||||
Number: pull.ID,
|
||||
Title: pull.Title,
|
||||
Branch: pull.Source.Branch.Name,
|
||||
HeadSHA: pull.Source.Commit.Hash,
|
||||
Author: pull.Author.Nickname,
|
||||
Number: pull.ID,
|
||||
Title: pull.Title,
|
||||
Branch: pull.Source.Branch.Name,
|
||||
TargetBranch: pull.Destination.Branch.Name,
|
||||
HeadSHA: pull.Source.Commit.Hash,
|
||||
Author: pull.Author.Nickname,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
|
||||
var err error
|
||||
switch r.RequestURI {
|
||||
case "/repositories/OWNER/REPO/pullrequests/":
|
||||
_, err = io.WriteString(w, fmt.Sprintf(`{
|
||||
_, err = fmt.Fprintf(w, `{
|
||||
"size": 2,
|
||||
"pagelen": 1,
|
||||
"page": 1,
|
||||
@@ -177,9 +177,9 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
|
||||
}
|
||||
}
|
||||
]
|
||||
}`, r.Host))
|
||||
}`, r.Host)
|
||||
case "/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=2":
|
||||
_, err = io.WriteString(w, fmt.Sprintf(`{
|
||||
_, err = fmt.Fprintf(w, `{
|
||||
"size": 2,
|
||||
"pagelen": 1,
|
||||
"page": 2,
|
||||
@@ -202,7 +202,7 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
|
||||
}
|
||||
}
|
||||
]
|
||||
}`, r.Host))
|
||||
}`, r.Host)
|
||||
default:
|
||||
t.Fail()
|
||||
}
|
||||
@@ -329,7 +329,7 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
|
||||
var err error
|
||||
switch r.RequestURI {
|
||||
case "/repositories/OWNER/REPO/pullrequests/":
|
||||
_, err = io.WriteString(w, fmt.Sprintf(`{
|
||||
_, err = fmt.Fprintf(w, `{
|
||||
"size": 2,
|
||||
"pagelen": 1,
|
||||
"page": 1,
|
||||
@@ -349,6 +349,11 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
|
||||
},
|
||||
"author": {
|
||||
"nickname": "testName"
|
||||
},
|
||||
"destination": {
|
||||
"branch": {
|
||||
"name": "master"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -365,12 +370,17 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
|
||||
},
|
||||
"author": {
|
||||
"nickname": "testName"
|
||||
},
|
||||
"destination": {
|
||||
"branch": {
|
||||
"name": "branch-200"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}`, r.Host))
|
||||
}`, r.Host)
|
||||
case "/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=2":
|
||||
_, err = io.WriteString(w, fmt.Sprintf(`{
|
||||
_, err = fmt.Fprintf(w, `{
|
||||
"size": 2,
|
||||
"pagelen": 1,
|
||||
"page": 2,
|
||||
@@ -390,10 +400,15 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
|
||||
},
|
||||
"author": {
|
||||
"nickname": "testName"
|
||||
},
|
||||
"destination": {
|
||||
"branch": {
|
||||
"name": "master"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}`, r.Host))
|
||||
}`, r.Host)
|
||||
default:
|
||||
t.Fail()
|
||||
}
|
||||
@@ -413,18 +428,20 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, pullRequests, 2)
|
||||
assert.Equal(t, PullRequest{
|
||||
Number: 101,
|
||||
Title: "feat(101)",
|
||||
Branch: "feature-101",
|
||||
HeadSHA: "1a8dd249c04a",
|
||||
Author: "testName",
|
||||
Number: 101,
|
||||
Title: "feat(101)",
|
||||
Branch: "feature-101",
|
||||
HeadSHA: "1a8dd249c04a",
|
||||
Author: "testName",
|
||||
TargetBranch: "master",
|
||||
}, *pullRequests[0])
|
||||
assert.Equal(t, PullRequest{
|
||||
Number: 102,
|
||||
Title: "feat(102)",
|
||||
Branch: "feature-102",
|
||||
HeadSHA: "6344d9623e3b",
|
||||
Author: "testName",
|
||||
Number: 102,
|
||||
Title: "feat(102)",
|
||||
Branch: "feature-102",
|
||||
HeadSHA: "6344d9623e3b",
|
||||
Author: "testName",
|
||||
TargetBranch: "master",
|
||||
}, *pullRequests[1])
|
||||
|
||||
regexp = `.*2$`
|
||||
@@ -438,11 +455,12 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, pullRequests, 1)
|
||||
assert.Equal(t, PullRequest{
|
||||
Number: 102,
|
||||
Title: "feat(102)",
|
||||
Branch: "feature-102",
|
||||
HeadSHA: "6344d9623e3b",
|
||||
Author: "testName",
|
||||
Number: 102,
|
||||
Title: "feat(102)",
|
||||
Branch: "feature-102",
|
||||
HeadSHA: "6344d9623e3b",
|
||||
Author: "testName",
|
||||
TargetBranch: "master",
|
||||
}, *pullRequests[0])
|
||||
|
||||
regexp = `[\d{2}`
|
||||
@@ -454,4 +472,23 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
|
||||
regexp = `feature-2[\d]{2}`
|
||||
targetRegexp := `branch.*`
|
||||
pullRequests, err = ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{
|
||||
{
|
||||
BranchMatch: ®exp,
|
||||
TargetBranchMatch: &targetRegexp,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, pullRequests, 1)
|
||||
assert.Equal(t, PullRequest{
|
||||
Number: 200,
|
||||
Title: "feat(200)",
|
||||
Branch: "feature-200",
|
||||
HeadSHA: "4cf807e67a6d",
|
||||
Author: "testName",
|
||||
TargetBranch: "branch-200",
|
||||
}, *pullRequests[0])
|
||||
}
|
||||
|
||||
@@ -3,12 +3,11 @@ package pull_request
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
bitbucketv1 "github.com/gfleury/go-bitbucket-v1"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/utils"
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/services"
|
||||
)
|
||||
|
||||
type BitbucketService struct {
|
||||
@@ -49,15 +48,10 @@ func NewBitbucketServiceNoAuth(ctx context.Context, url, projectKey, repositoryS
|
||||
}
|
||||
|
||||
func newBitbucketService(ctx context.Context, bitbucketConfig *bitbucketv1.Configuration, projectKey, repositorySlug string, scmRootCAPath string, insecure bool, caCerts []byte) (PullRequestService, error) {
|
||||
bitbucketConfig.BasePath = utils.NormalizeBitbucketBasePath(bitbucketConfig.BasePath)
|
||||
tlsConfig := utils.GetTlsConfig(scmRootCAPath, insecure, caCerts)
|
||||
bitbucketConfig.HTTPClient = &http.Client{Transport: &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
}}
|
||||
bitbucketClient := bitbucketv1.NewAPIClient(ctx, bitbucketConfig)
|
||||
bbClient := services.SetupBitbucketClient(ctx, bitbucketConfig, scmRootCAPath, insecure, caCerts)
|
||||
|
||||
return &BitbucketService{
|
||||
client: bitbucketClient,
|
||||
client: bbClient,
|
||||
projectKey: projectKey,
|
||||
repositorySlug: repositorySlug,
|
||||
}, nil
|
||||
|
||||
@@ -14,11 +14,12 @@ type GiteaService struct {
|
||||
client *gitea.Client
|
||||
owner string
|
||||
repo string
|
||||
labels []string
|
||||
}
|
||||
|
||||
var _ PullRequestService = (*GiteaService)(nil)
|
||||
|
||||
func NewGiteaService(token, url, owner, repo string, insecure bool) (PullRequestService, error) {
|
||||
func NewGiteaService(token, url, owner, repo string, labels []string, insecure bool) (PullRequestService, error) {
|
||||
if token == "" {
|
||||
token = os.Getenv("GITEA_TOKEN")
|
||||
}
|
||||
@@ -42,6 +43,7 @@ func NewGiteaService(token, url, owner, repo string, insecure bool) (PullRequest
|
||||
client: client,
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
labels: labels,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -56,6 +58,9 @@ func (g *GiteaService) List(ctx context.Context) ([]*PullRequest, error) {
|
||||
}
|
||||
list := []*PullRequest{}
|
||||
for _, pr := range prs {
|
||||
if !giteaContainLabels(g.labels, pr.Labels) {
|
||||
continue
|
||||
}
|
||||
list = append(list, &PullRequest{
|
||||
Number: int(pr.Index),
|
||||
Title: pr.Title,
|
||||
@@ -69,6 +74,21 @@ func (g *GiteaService) List(ctx context.Context) ([]*PullRequest, error) {
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// containLabels returns true if gotLabels contains expectedLabels
|
||||
func giteaContainLabels(expectedLabels []string, gotLabels []*gitea.Label) bool {
|
||||
gotLabelNamesMap := make(map[string]bool)
|
||||
for i := 0; i < len(gotLabels); i++ {
|
||||
gotLabelNamesMap[gotLabels[i].Name] = true
|
||||
}
|
||||
for _, expected := range expectedLabels {
|
||||
v, ok := gotLabelNamesMap[expected]
|
||||
if !v || !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Get the Gitea pull request label names.
|
||||
func getGiteaPRLabelNames(giteaLabels []*gitea.Label) []string {
|
||||
var labelNames []string
|
||||
|
||||
@@ -52,7 +52,7 @@ func giteaMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
|
||||
},
|
||||
"title": "add an empty file",
|
||||
"body": "",
|
||||
"labels": [],
|
||||
"labels": [{"id": 1, "name": "label1", "color": "00aabb", "description": "foo", "url": ""}],
|
||||
"milestone": null,
|
||||
"assignee": null,
|
||||
"assignees": null,
|
||||
@@ -246,11 +246,59 @@ func giteaMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGiteaContainLabels(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
Labels []string
|
||||
PullLabels []*gitea.Label
|
||||
Expect bool
|
||||
}{
|
||||
{
|
||||
Name: "Match labels",
|
||||
Labels: []string{"label1", "label2"},
|
||||
PullLabels: []*gitea.Label{
|
||||
{Name: "label1"},
|
||||
{Name: "label2"},
|
||||
{Name: "label3"},
|
||||
},
|
||||
Expect: true,
|
||||
},
|
||||
{
|
||||
Name: "Not match labels",
|
||||
Labels: []string{"label1", "label4"},
|
||||
PullLabels: []*gitea.Label{
|
||||
{Name: "label1"},
|
||||
{Name: "label2"},
|
||||
{Name: "label3"},
|
||||
},
|
||||
Expect: false,
|
||||
},
|
||||
{
|
||||
Name: "No specify",
|
||||
Labels: []string{},
|
||||
PullLabels: []*gitea.Label{
|
||||
{Name: "label1"},
|
||||
{Name: "label2"},
|
||||
{Name: "label3"},
|
||||
},
|
||||
Expect: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.Name, func(t *testing.T) {
|
||||
if got := giteaContainLabels(c.Labels, c.PullLabels); got != c.Expect {
|
||||
t.Errorf("expect: %v, got: %v", c.Expect, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGiteaList(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
giteaMockHandler(t)(w, r)
|
||||
}))
|
||||
host, err := NewGiteaService("", ts.URL, "test-argocd", "pr-test", false)
|
||||
host, err := NewGiteaService("", ts.URL, "test-argocd", "pr-test", []string{"label1"}, false)
|
||||
require.NoError(t, err)
|
||||
prs, err := host.List(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -6,7 +6,9 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/google/go-github/v66/github"
|
||||
"github.com/google/go-github/v69/github"
|
||||
|
||||
appsetutils "github.com/argoproj/argo-cd/v3/applicationset/utils"
|
||||
)
|
||||
|
||||
type GithubService struct {
|
||||
@@ -18,18 +20,28 @@ type GithubService struct {
|
||||
|
||||
var _ PullRequestService = (*GithubService)(nil)
|
||||
|
||||
func NewGithubService(token, url, owner, repo string, labels []string) (PullRequestService, error) {
|
||||
func NewGithubService(token, url, owner, repo string, labels []string, optionalHTTPClient ...*http.Client) (PullRequestService, error) {
|
||||
// Undocumented environment variable to set a default token, to be used in testing to dodge anonymous rate limits.
|
||||
if token == "" {
|
||||
token = os.Getenv("GITHUB_TOKEN")
|
||||
}
|
||||
httpClient := &http.Client{}
|
||||
|
||||
var client *github.Client
|
||||
httpClient := appsetutils.GetOptionalHTTPClient(optionalHTTPClient...)
|
||||
|
||||
if url == "" {
|
||||
client = github.NewClient(httpClient).WithAuthToken(token)
|
||||
if token == "" {
|
||||
client = github.NewClient(httpClient)
|
||||
} else {
|
||||
client = github.NewClient(httpClient).WithAuthToken(token)
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
client, err = github.NewClient(httpClient).WithEnterpriseURLs(url, url)
|
||||
if token == "" {
|
||||
client, err = github.NewClient(httpClient).WithEnterpriseURLs(url, url)
|
||||
} else {
|
||||
client, err = github.NewClient(httpClient).WithAuthToken(token).WithEnterpriseURLs(url, url)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package pull_request
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/services/github_app_auth"
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/services/internal/github_app"
|
||||
appsetutils "github.com/argoproj/argo-cd/v3/applicationset/utils"
|
||||
)
|
||||
|
||||
func NewGithubAppService(g github_app_auth.Authentication, url, owner, repo string, labels []string) (PullRequestService, error) {
|
||||
client, err := github_app.Client(g, url)
|
||||
func NewGithubAppService(g github_app_auth.Authentication, url, owner, repo string, labels []string, optionalHTTPClient ...*http.Client) (PullRequestService, error) {
|
||||
httpClient := appsetutils.GetOptionalHTTPClient(optionalHTTPClient...)
|
||||
client, err := github_app.Client(g, url, httpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package pull_request
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-github/v66/github"
|
||||
"github.com/google/go-github/v69/github"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v3/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v3/util/db"
|
||||
"github.com/argoproj/argo-cd/v3/util/io"
|
||||
utilio "github.com/argoproj/argo-cd/v3/util/io"
|
||||
)
|
||||
|
||||
type argoCDService struct {
|
||||
@@ -36,7 +36,7 @@ func NewArgoCDService(db db.ArgoDB, submoduleEnabled bool, repoClientset apiclie
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error initializing new repo server client: %w", err)
|
||||
}
|
||||
defer io.Close(closer)
|
||||
defer utilio.Close(closer)
|
||||
return client.GetGitFiles(ctx, fileRequest)
|
||||
},
|
||||
getGitDirectoriesFromRepoServer: func(ctx context.Context, dirRequest *apiclient.GitDirectoriesRequest) (*apiclient.GitDirectoriesResponse, error) {
|
||||
@@ -44,7 +44,7 @@ func NewArgoCDService(db db.ArgoDB, submoduleEnabled bool, repoClientset apiclie
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error initialising new repo server client: %w", err)
|
||||
}
|
||||
defer io.Close(closer)
|
||||
defer utilio.Close(closer)
|
||||
return client.GetGitDirectories(ctx, dirRequest)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,22 +4,21 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
pathpkg "path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/arn"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/codecommit"
|
||||
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/exp/maps"
|
||||
"k8s.io/utils/strings/slices"
|
||||
|
||||
application "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
@@ -299,7 +298,7 @@ func (p *AWSCodeCommitProvider) getTagFilters() []*resourcegroupstaggingapi.TagF
|
||||
filter.Values = append(filter.Values, aws.String(tagFilter.Value))
|
||||
}
|
||||
}
|
||||
return maps.Values(filters)
|
||||
return slices.Collect(maps.Values(filters))
|
||||
}
|
||||
|
||||
func getCodeCommitRepoName(repoArn string) (string, error) {
|
||||
@@ -341,7 +340,7 @@ func toAbsolutePath(path string) string {
|
||||
if filepath.IsAbs(path) {
|
||||
return path
|
||||
}
|
||||
return filepath.ToSlash(filepath.Join("/", path))
|
||||
return filepath.ToSlash(filepath.Join("/", path)) //nolint:gocritic // Prepend slash to have an absolute path
|
||||
}
|
||||
|
||||
func createAWSDiscoveryClients(_ context.Context, role string, region string) (*resourcegroupstaggingapi.ResourceGroupsTaggingAPI, *codecommit.CodeCommit, error) {
|
||||
|
||||
@@ -1,170 +1,16 @@
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
codecommit "github.com/aws/aws-sdk-go/service/codecommit"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/service/codecommit"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
request "github.com/aws/aws-sdk-go/aws/request"
|
||||
)
|
||||
|
||||
// AWSCodeCommitClient is an autogenerated mock type for the AWSCodeCommitClient type
|
||||
type AWSCodeCommitClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// GetFolderWithContext provides a mock function with given fields: _a0, _a1, _a2
|
||||
func (_m *AWSCodeCommitClient) GetFolderWithContext(_a0 context.Context, _a1 *codecommit.GetFolderInput, _a2 ...request.Option) (*codecommit.GetFolderOutput, error) {
|
||||
_va := make([]interface{}, len(_a2))
|
||||
for _i := range _a2 {
|
||||
_va[_i] = _a2[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, _a0, _a1)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetFolderWithContext")
|
||||
}
|
||||
|
||||
var r0 *codecommit.GetFolderOutput
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.GetFolderInput, ...request.Option) (*codecommit.GetFolderOutput, error)); ok {
|
||||
return rf(_a0, _a1, _a2...)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.GetFolderInput, ...request.Option) *codecommit.GetFolderOutput); ok {
|
||||
r0 = rf(_a0, _a1, _a2...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*codecommit.GetFolderOutput)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *codecommit.GetFolderInput, ...request.Option) error); ok {
|
||||
r1 = rf(_a0, _a1, _a2...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetRepositoryWithContext provides a mock function with given fields: _a0, _a1, _a2
|
||||
func (_m *AWSCodeCommitClient) GetRepositoryWithContext(_a0 context.Context, _a1 *codecommit.GetRepositoryInput, _a2 ...request.Option) (*codecommit.GetRepositoryOutput, error) {
|
||||
_va := make([]interface{}, len(_a2))
|
||||
for _i := range _a2 {
|
||||
_va[_i] = _a2[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, _a0, _a1)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetRepositoryWithContext")
|
||||
}
|
||||
|
||||
var r0 *codecommit.GetRepositoryOutput
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.GetRepositoryInput, ...request.Option) (*codecommit.GetRepositoryOutput, error)); ok {
|
||||
return rf(_a0, _a1, _a2...)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.GetRepositoryInput, ...request.Option) *codecommit.GetRepositoryOutput); ok {
|
||||
r0 = rf(_a0, _a1, _a2...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*codecommit.GetRepositoryOutput)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *codecommit.GetRepositoryInput, ...request.Option) error); ok {
|
||||
r1 = rf(_a0, _a1, _a2...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// ListBranchesWithContext provides a mock function with given fields: _a0, _a1, _a2
|
||||
func (_m *AWSCodeCommitClient) ListBranchesWithContext(_a0 context.Context, _a1 *codecommit.ListBranchesInput, _a2 ...request.Option) (*codecommit.ListBranchesOutput, error) {
|
||||
_va := make([]interface{}, len(_a2))
|
||||
for _i := range _a2 {
|
||||
_va[_i] = _a2[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, _a0, _a1)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListBranchesWithContext")
|
||||
}
|
||||
|
||||
var r0 *codecommit.ListBranchesOutput
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.ListBranchesInput, ...request.Option) (*codecommit.ListBranchesOutput, error)); ok {
|
||||
return rf(_a0, _a1, _a2...)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.ListBranchesInput, ...request.Option) *codecommit.ListBranchesOutput); ok {
|
||||
r0 = rf(_a0, _a1, _a2...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*codecommit.ListBranchesOutput)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *codecommit.ListBranchesInput, ...request.Option) error); ok {
|
||||
r1 = rf(_a0, _a1, _a2...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// ListRepositoriesWithContext provides a mock function with given fields: _a0, _a1, _a2
|
||||
func (_m *AWSCodeCommitClient) ListRepositoriesWithContext(_a0 context.Context, _a1 *codecommit.ListRepositoriesInput, _a2 ...request.Option) (*codecommit.ListRepositoriesOutput, error) {
|
||||
_va := make([]interface{}, len(_a2))
|
||||
for _i := range _a2 {
|
||||
_va[_i] = _a2[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, _a0, _a1)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListRepositoriesWithContext")
|
||||
}
|
||||
|
||||
var r0 *codecommit.ListRepositoriesOutput
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.ListRepositoriesInput, ...request.Option) (*codecommit.ListRepositoriesOutput, error)); ok {
|
||||
return rf(_a0, _a1, _a2...)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.ListRepositoriesInput, ...request.Option) *codecommit.ListRepositoriesOutput); ok {
|
||||
r0 = rf(_a0, _a1, _a2...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*codecommit.ListRepositoriesOutput)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *codecommit.ListRepositoriesInput, ...request.Option) error); ok {
|
||||
r1 = rf(_a0, _a1, _a2...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// NewAWSCodeCommitClient creates a new instance of AWSCodeCommitClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewAWSCodeCommitClient(t interface {
|
||||
@@ -178,3 +24,364 @@ func NewAWSCodeCommitClient(t interface {
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
// AWSCodeCommitClient is an autogenerated mock type for the AWSCodeCommitClient type
|
||||
type AWSCodeCommitClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type AWSCodeCommitClient_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *AWSCodeCommitClient) EXPECT() *AWSCodeCommitClient_Expecter {
|
||||
return &AWSCodeCommitClient_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// GetFolderWithContext provides a mock function for the type AWSCodeCommitClient
|
||||
func (_mock *AWSCodeCommitClient) GetFolderWithContext(v aws.Context, getFolderInput *codecommit.GetFolderInput, options ...request.Option) (*codecommit.GetFolderOutput, error) {
|
||||
// request.Option
|
||||
_va := make([]interface{}, len(options))
|
||||
for _i := range options {
|
||||
_va[_i] = options[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, v, getFolderInput)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _mock.Called(_ca...)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetFolderWithContext")
|
||||
}
|
||||
|
||||
var r0 *codecommit.GetFolderOutput
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(aws.Context, *codecommit.GetFolderInput, ...request.Option) (*codecommit.GetFolderOutput, error)); ok {
|
||||
return returnFunc(v, getFolderInput, options...)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(aws.Context, *codecommit.GetFolderInput, ...request.Option) *codecommit.GetFolderOutput); ok {
|
||||
r0 = returnFunc(v, getFolderInput, options...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*codecommit.GetFolderOutput)
|
||||
}
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(aws.Context, *codecommit.GetFolderInput, ...request.Option) error); ok {
|
||||
r1 = returnFunc(v, getFolderInput, options...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// AWSCodeCommitClient_GetFolderWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFolderWithContext'
|
||||
type AWSCodeCommitClient_GetFolderWithContext_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetFolderWithContext is a helper method to define mock.On call
|
||||
// - v aws.Context
|
||||
// - getFolderInput *codecommit.GetFolderInput
|
||||
// - options ...request.Option
|
||||
func (_e *AWSCodeCommitClient_Expecter) GetFolderWithContext(v interface{}, getFolderInput interface{}, options ...interface{}) *AWSCodeCommitClient_GetFolderWithContext_Call {
|
||||
return &AWSCodeCommitClient_GetFolderWithContext_Call{Call: _e.mock.On("GetFolderWithContext",
|
||||
append([]interface{}{v, getFolderInput}, options...)...)}
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_GetFolderWithContext_Call) Run(run func(v aws.Context, getFolderInput *codecommit.GetFolderInput, options ...request.Option)) *AWSCodeCommitClient_GetFolderWithContext_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 aws.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(aws.Context)
|
||||
}
|
||||
var arg1 *codecommit.GetFolderInput
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(*codecommit.GetFolderInput)
|
||||
}
|
||||
var arg2 []request.Option
|
||||
variadicArgs := make([]request.Option, len(args)-2)
|
||||
for i, a := range args[2:] {
|
||||
if a != nil {
|
||||
variadicArgs[i] = a.(request.Option)
|
||||
}
|
||||
}
|
||||
arg2 = variadicArgs
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2...,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_GetFolderWithContext_Call) Return(getFolderOutput *codecommit.GetFolderOutput, err error) *AWSCodeCommitClient_GetFolderWithContext_Call {
|
||||
_c.Call.Return(getFolderOutput, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_GetFolderWithContext_Call) RunAndReturn(run func(v aws.Context, getFolderInput *codecommit.GetFolderInput, options ...request.Option) (*codecommit.GetFolderOutput, error)) *AWSCodeCommitClient_GetFolderWithContext_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetRepositoryWithContext provides a mock function for the type AWSCodeCommitClient
|
||||
func (_mock *AWSCodeCommitClient) GetRepositoryWithContext(v aws.Context, getRepositoryInput *codecommit.GetRepositoryInput, options ...request.Option) (*codecommit.GetRepositoryOutput, error) {
|
||||
// request.Option
|
||||
_va := make([]interface{}, len(options))
|
||||
for _i := range options {
|
||||
_va[_i] = options[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, v, getRepositoryInput)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _mock.Called(_ca...)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetRepositoryWithContext")
|
||||
}
|
||||
|
||||
var r0 *codecommit.GetRepositoryOutput
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(aws.Context, *codecommit.GetRepositoryInput, ...request.Option) (*codecommit.GetRepositoryOutput, error)); ok {
|
||||
return returnFunc(v, getRepositoryInput, options...)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(aws.Context, *codecommit.GetRepositoryInput, ...request.Option) *codecommit.GetRepositoryOutput); ok {
|
||||
r0 = returnFunc(v, getRepositoryInput, options...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*codecommit.GetRepositoryOutput)
|
||||
}
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(aws.Context, *codecommit.GetRepositoryInput, ...request.Option) error); ok {
|
||||
r1 = returnFunc(v, getRepositoryInput, options...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// AWSCodeCommitClient_GetRepositoryWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRepositoryWithContext'
|
||||
type AWSCodeCommitClient_GetRepositoryWithContext_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetRepositoryWithContext is a helper method to define mock.On call
|
||||
// - v aws.Context
|
||||
// - getRepositoryInput *codecommit.GetRepositoryInput
|
||||
// - options ...request.Option
|
||||
func (_e *AWSCodeCommitClient_Expecter) GetRepositoryWithContext(v interface{}, getRepositoryInput interface{}, options ...interface{}) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
|
||||
return &AWSCodeCommitClient_GetRepositoryWithContext_Call{Call: _e.mock.On("GetRepositoryWithContext",
|
||||
append([]interface{}{v, getRepositoryInput}, options...)...)}
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_GetRepositoryWithContext_Call) Run(run func(v aws.Context, getRepositoryInput *codecommit.GetRepositoryInput, options ...request.Option)) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 aws.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(aws.Context)
|
||||
}
|
||||
var arg1 *codecommit.GetRepositoryInput
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(*codecommit.GetRepositoryInput)
|
||||
}
|
||||
var arg2 []request.Option
|
||||
variadicArgs := make([]request.Option, len(args)-2)
|
||||
for i, a := range args[2:] {
|
||||
if a != nil {
|
||||
variadicArgs[i] = a.(request.Option)
|
||||
}
|
||||
}
|
||||
arg2 = variadicArgs
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2...,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_GetRepositoryWithContext_Call) Return(getRepositoryOutput *codecommit.GetRepositoryOutput, err error) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
|
||||
_c.Call.Return(getRepositoryOutput, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_GetRepositoryWithContext_Call) RunAndReturn(run func(v aws.Context, getRepositoryInput *codecommit.GetRepositoryInput, options ...request.Option) (*codecommit.GetRepositoryOutput, error)) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListBranchesWithContext provides a mock function for the type AWSCodeCommitClient
|
||||
func (_mock *AWSCodeCommitClient) ListBranchesWithContext(v aws.Context, listBranchesInput *codecommit.ListBranchesInput, options ...request.Option) (*codecommit.ListBranchesOutput, error) {
|
||||
// request.Option
|
||||
_va := make([]interface{}, len(options))
|
||||
for _i := range options {
|
||||
_va[_i] = options[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, v, listBranchesInput)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _mock.Called(_ca...)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListBranchesWithContext")
|
||||
}
|
||||
|
||||
var r0 *codecommit.ListBranchesOutput
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(aws.Context, *codecommit.ListBranchesInput, ...request.Option) (*codecommit.ListBranchesOutput, error)); ok {
|
||||
return returnFunc(v, listBranchesInput, options...)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(aws.Context, *codecommit.ListBranchesInput, ...request.Option) *codecommit.ListBranchesOutput); ok {
|
||||
r0 = returnFunc(v, listBranchesInput, options...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*codecommit.ListBranchesOutput)
|
||||
}
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(aws.Context, *codecommit.ListBranchesInput, ...request.Option) error); ok {
|
||||
r1 = returnFunc(v, listBranchesInput, options...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// AWSCodeCommitClient_ListBranchesWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListBranchesWithContext'
|
||||
type AWSCodeCommitClient_ListBranchesWithContext_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ListBranchesWithContext is a helper method to define mock.On call
|
||||
// - v aws.Context
|
||||
// - listBranchesInput *codecommit.ListBranchesInput
|
||||
// - options ...request.Option
|
||||
func (_e *AWSCodeCommitClient_Expecter) ListBranchesWithContext(v interface{}, listBranchesInput interface{}, options ...interface{}) *AWSCodeCommitClient_ListBranchesWithContext_Call {
|
||||
return &AWSCodeCommitClient_ListBranchesWithContext_Call{Call: _e.mock.On("ListBranchesWithContext",
|
||||
append([]interface{}{v, listBranchesInput}, options...)...)}
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_ListBranchesWithContext_Call) Run(run func(v aws.Context, listBranchesInput *codecommit.ListBranchesInput, options ...request.Option)) *AWSCodeCommitClient_ListBranchesWithContext_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 aws.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(aws.Context)
|
||||
}
|
||||
var arg1 *codecommit.ListBranchesInput
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(*codecommit.ListBranchesInput)
|
||||
}
|
||||
var arg2 []request.Option
|
||||
variadicArgs := make([]request.Option, len(args)-2)
|
||||
for i, a := range args[2:] {
|
||||
if a != nil {
|
||||
variadicArgs[i] = a.(request.Option)
|
||||
}
|
||||
}
|
||||
arg2 = variadicArgs
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2...,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_ListBranchesWithContext_Call) Return(listBranchesOutput *codecommit.ListBranchesOutput, err error) *AWSCodeCommitClient_ListBranchesWithContext_Call {
|
||||
_c.Call.Return(listBranchesOutput, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_ListBranchesWithContext_Call) RunAndReturn(run func(v aws.Context, listBranchesInput *codecommit.ListBranchesInput, options ...request.Option) (*codecommit.ListBranchesOutput, error)) *AWSCodeCommitClient_ListBranchesWithContext_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListRepositoriesWithContext provides a mock function for the type AWSCodeCommitClient
|
||||
func (_mock *AWSCodeCommitClient) ListRepositoriesWithContext(v aws.Context, listRepositoriesInput *codecommit.ListRepositoriesInput, options ...request.Option) (*codecommit.ListRepositoriesOutput, error) {
|
||||
// request.Option
|
||||
_va := make([]interface{}, len(options))
|
||||
for _i := range options {
|
||||
_va[_i] = options[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, v, listRepositoriesInput)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _mock.Called(_ca...)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListRepositoriesWithContext")
|
||||
}
|
||||
|
||||
var r0 *codecommit.ListRepositoriesOutput
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(aws.Context, *codecommit.ListRepositoriesInput, ...request.Option) (*codecommit.ListRepositoriesOutput, error)); ok {
|
||||
return returnFunc(v, listRepositoriesInput, options...)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(aws.Context, *codecommit.ListRepositoriesInput, ...request.Option) *codecommit.ListRepositoriesOutput); ok {
|
||||
r0 = returnFunc(v, listRepositoriesInput, options...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*codecommit.ListRepositoriesOutput)
|
||||
}
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(aws.Context, *codecommit.ListRepositoriesInput, ...request.Option) error); ok {
|
||||
r1 = returnFunc(v, listRepositoriesInput, options...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// AWSCodeCommitClient_ListRepositoriesWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListRepositoriesWithContext'
|
||||
type AWSCodeCommitClient_ListRepositoriesWithContext_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ListRepositoriesWithContext is a helper method to define mock.On call
|
||||
// - v aws.Context
|
||||
// - listRepositoriesInput *codecommit.ListRepositoriesInput
|
||||
// - options ...request.Option
|
||||
func (_e *AWSCodeCommitClient_Expecter) ListRepositoriesWithContext(v interface{}, listRepositoriesInput interface{}, options ...interface{}) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
|
||||
return &AWSCodeCommitClient_ListRepositoriesWithContext_Call{Call: _e.mock.On("ListRepositoriesWithContext",
|
||||
append([]interface{}{v, listRepositoriesInput}, options...)...)}
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_ListRepositoriesWithContext_Call) Run(run func(v aws.Context, listRepositoriesInput *codecommit.ListRepositoriesInput, options ...request.Option)) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 aws.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(aws.Context)
|
||||
}
|
||||
var arg1 *codecommit.ListRepositoriesInput
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(*codecommit.ListRepositoriesInput)
|
||||
}
|
||||
var arg2 []request.Option
|
||||
variadicArgs := make([]request.Option, len(args)-2)
|
||||
for i, a := range args[2:] {
|
||||
if a != nil {
|
||||
variadicArgs[i] = a.(request.Option)
|
||||
}
|
||||
}
|
||||
arg2 = variadicArgs
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2...,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_ListRepositoriesWithContext_Call) Return(listRepositoriesOutput *codecommit.ListRepositoriesOutput, err error) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
|
||||
_c.Call.Return(listRepositoriesOutput, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_ListRepositoriesWithContext_Call) RunAndReturn(run func(v aws.Context, listRepositoriesInput *codecommit.ListRepositoriesInput, options ...request.Option) (*codecommit.ListRepositoriesOutput, error)) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
@@ -1,58 +1,16 @@
|
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
request "github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
resourcegroupstaggingapi "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
|
||||
)
|
||||
|
||||
// AWSTaggingClient is an autogenerated mock type for the AWSTaggingClient type
|
||||
type AWSTaggingClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// GetResourcesWithContext provides a mock function with given fields: _a0, _a1, _a2
|
||||
func (_m *AWSTaggingClient) GetResourcesWithContext(_a0 context.Context, _a1 *resourcegroupstaggingapi.GetResourcesInput, _a2 ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error) {
|
||||
_va := make([]interface{}, len(_a2))
|
||||
for _i := range _a2 {
|
||||
_va[_i] = _a2[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, _a0, _a1)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetResourcesWithContext")
|
||||
}
|
||||
|
||||
var r0 *resourcegroupstaggingapi.GetResourcesOutput
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error)); ok {
|
||||
return rf(_a0, _a1, _a2...)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) *resourcegroupstaggingapi.GetResourcesOutput); ok {
|
||||
r0 = rf(_a0, _a1, _a2...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*resourcegroupstaggingapi.GetResourcesOutput)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) error); ok {
|
||||
r1 = rf(_a0, _a1, _a2...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// NewAWSTaggingClient creates a new instance of AWSTaggingClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewAWSTaggingClient(t interface {
|
||||
@@ -66,3 +24,103 @@ func NewAWSTaggingClient(t interface {
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
// AWSTaggingClient is an autogenerated mock type for the AWSTaggingClient type
|
||||
type AWSTaggingClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type AWSTaggingClient_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *AWSTaggingClient) EXPECT() *AWSTaggingClient_Expecter {
|
||||
return &AWSTaggingClient_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// GetResourcesWithContext provides a mock function for the type AWSTaggingClient
|
||||
func (_mock *AWSTaggingClient) GetResourcesWithContext(v aws.Context, getResourcesInput *resourcegroupstaggingapi.GetResourcesInput, options ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error) {
|
||||
// request.Option
|
||||
_va := make([]interface{}, len(options))
|
||||
for _i := range options {
|
||||
_va[_i] = options[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, v, getResourcesInput)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _mock.Called(_ca...)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetResourcesWithContext")
|
||||
}
|
||||
|
||||
var r0 *resourcegroupstaggingapi.GetResourcesOutput
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(aws.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error)); ok {
|
||||
return returnFunc(v, getResourcesInput, options...)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(aws.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) *resourcegroupstaggingapi.GetResourcesOutput); ok {
|
||||
r0 = returnFunc(v, getResourcesInput, options...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*resourcegroupstaggingapi.GetResourcesOutput)
|
||||
}
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(aws.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) error); ok {
|
||||
r1 = returnFunc(v, getResourcesInput, options...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// AWSTaggingClient_GetResourcesWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetResourcesWithContext'
|
||||
type AWSTaggingClient_GetResourcesWithContext_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetResourcesWithContext is a helper method to define mock.On call
|
||||
// - v aws.Context
|
||||
// - getResourcesInput *resourcegroupstaggingapi.GetResourcesInput
|
||||
// - options ...request.Option
|
||||
func (_e *AWSTaggingClient_Expecter) GetResourcesWithContext(v interface{}, getResourcesInput interface{}, options ...interface{}) *AWSTaggingClient_GetResourcesWithContext_Call {
|
||||
return &AWSTaggingClient_GetResourcesWithContext_Call{Call: _e.mock.On("GetResourcesWithContext",
|
||||
append([]interface{}{v, getResourcesInput}, options...)...)}
|
||||
}
|
||||
|
||||
func (_c *AWSTaggingClient_GetResourcesWithContext_Call) Run(run func(v aws.Context, getResourcesInput *resourcegroupstaggingapi.GetResourcesInput, options ...request.Option)) *AWSTaggingClient_GetResourcesWithContext_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 aws.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(aws.Context)
|
||||
}
|
||||
var arg1 *resourcegroupstaggingapi.GetResourcesInput
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(*resourcegroupstaggingapi.GetResourcesInput)
|
||||
}
|
||||
var arg2 []request.Option
|
||||
variadicArgs := make([]request.Option, len(args)-2)
|
||||
for i, a := range args[2:] {
|
||||
if a != nil {
|
||||
variadicArgs[i] = a.(request.Option)
|
||||
}
|
||||
}
|
||||
arg2 = variadicArgs
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2...,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AWSTaggingClient_GetResourcesWithContext_Call) Return(getResourcesOutput *resourcegroupstaggingapi.GetResourcesOutput, err error) *AWSTaggingClient_GetResourcesWithContext_Call {
|
||||
_c.Call.Return(getResourcesOutput, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AWSTaggingClient_GetResourcesWithContext_Call) RunAndReturn(run func(v aws.Context, getResourcesInput *resourcegroupstaggingapi.GetResourcesInput, options ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error)) *AWSTaggingClient_GetResourcesWithContext_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops"
|
||||
azureGit "github.com/microsoft/azure-devops-go-api/azuredevops/git"
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops/v7"
|
||||
azureGit "github.com/microsoft/azure-devops-go-api/azuredevops/v7/git"
|
||||
)
|
||||
|
||||
const AZURE_DEVOPS_DEFAULT_URL = "https://dev.azure.com"
|
||||
@@ -47,7 +47,6 @@ func (factory *devopsFactoryImpl) GetClient(ctx context.Context) (azureGit.Clien
|
||||
type AzureDevOpsProvider struct {
|
||||
organization string
|
||||
teamProject string
|
||||
accessToken string
|
||||
clientFactory AzureDevOpsClientFactory
|
||||
allBranches bool
|
||||
}
|
||||
@@ -69,7 +68,7 @@ func NewAzureDevOpsProvider(accessToken string, org string, url string, project
|
||||
|
||||
connection := azuredevops.NewPatConnection(devOpsURL, accessToken)
|
||||
|
||||
return &AzureDevOpsProvider{organization: org, teamProject: project, accessToken: accessToken, clientFactory: &devopsFactoryImpl{connection: connection}, allBranches: allBranches}, nil
|
||||
return &AzureDevOpsProvider{organization: org, teamProject: project, clientFactory: &devopsFactoryImpl{connection: connection}, allBranches: allBranches}, nil
|
||||
}
|
||||
|
||||
func (g *AzureDevOpsProvider) ListRepos(ctx context.Context, _ string) ([]*Repository, error) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,8 +12,8 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops"
|
||||
azureGit "github.com/microsoft/azure-devops-go-api/azuredevops/git"
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops/v7"
|
||||
azureGit "github.com/microsoft/azure-devops-go-api/azuredevops/v7/git"
|
||||
|
||||
azureMock "github.com/argoproj/argo-cd/v3/applicationset/services/scm_provider/azure_devops/git/mocks"
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
bitbucketv1 "github.com/gfleury/go-bitbucket-v1"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/utils"
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/services"
|
||||
)
|
||||
|
||||
type BitbucketServerProvider struct {
|
||||
@@ -49,15 +49,10 @@ func NewBitbucketServerProviderNoAuth(ctx context.Context, url, projectKey strin
|
||||
}
|
||||
|
||||
func newBitbucketServerProvider(ctx context.Context, bitbucketConfig *bitbucketv1.Configuration, projectKey string, allBranches bool, scmRootCAPath string, insecure bool, caCerts []byte) (*BitbucketServerProvider, error) {
|
||||
bitbucketConfig.BasePath = utils.NormalizeBitbucketBasePath(bitbucketConfig.BasePath)
|
||||
tlsConfig := utils.GetTlsConfig(scmRootCAPath, insecure, caCerts)
|
||||
bitbucketConfig.HTTPClient = &http.Client{Transport: &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
}}
|
||||
bitbucketClient := bitbucketv1.NewAPIClient(ctx, bitbucketConfig)
|
||||
bbClient := services.SetupBitbucketClient(ctx, bitbucketConfig, scmRootCAPath, insecure, caCerts)
|
||||
|
||||
return &BitbucketServerProvider{
|
||||
client: bitbucketClient,
|
||||
client: bbClient,
|
||||
projectKey: projectKey,
|
||||
allBranches: allBranches,
|
||||
}, nil
|
||||
|
||||
@@ -350,6 +350,7 @@ func TestGetBranchesMissingDefault(t *testing.T) {
|
||||
assert.Empty(t, r.Header.Get("Authorization"))
|
||||
if r.RequestURI == "/rest/api/1.0/projects/PROJECT/repos/REPO/branches/default" {
|
||||
http.Error(w, "Not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
defaultHandler(t)(w, r)
|
||||
}))
|
||||
@@ -393,6 +394,7 @@ func TestGetBranchesErrorDefaultBranch(t *testing.T) {
|
||||
assert.Empty(t, r.Header.Get("Authorization"))
|
||||
if r.RequestURI == "/rest/api/1.0/projects/PROJECT/repos/REPO/branches/default" {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defaultHandler(t)(w, r)
|
||||
}))
|
||||
@@ -543,6 +545,7 @@ func TestListReposMissingDefaultBranch(t *testing.T) {
|
||||
assert.Empty(t, r.Header.Get("Authorization"))
|
||||
if r.RequestURI == "/rest/api/1.0/projects/PROJECT/repos/REPO/branches/default" {
|
||||
http.Error(w, "Not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
defaultHandler(t)(w, r)
|
||||
}))
|
||||
@@ -559,6 +562,7 @@ func TestListReposErrorDefaultBranch(t *testing.T) {
|
||||
assert.Empty(t, r.Header.Get("Authorization"))
|
||||
if r.RequestURI == "/rest/api/1.0/projects/PROJECT/repos/REPO/branches/default" {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defaultHandler(t)(w, r)
|
||||
}))
|
||||
|
||||
@@ -6,7 +6,9 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/google/go-github/v66/github"
|
||||
"github.com/google/go-github/v69/github"
|
||||
|
||||
appsetutils "github.com/argoproj/argo-cd/v3/applicationset/utils"
|
||||
)
|
||||
|
||||
type GithubProvider struct {
|
||||
@@ -17,18 +19,28 @@ type GithubProvider struct {
|
||||
|
||||
var _ SCMProviderService = &GithubProvider{}
|
||||
|
||||
func NewGithubProvider(organization string, token string, url string, allBranches bool) (*GithubProvider, error) {
|
||||
func NewGithubProvider(organization string, token string, url string, allBranches bool, optionalHTTPClient ...*http.Client) (*GithubProvider, error) {
|
||||
// Undocumented environment variable to set a default token, to be used in testing to dodge anonymous rate limits.
|
||||
if token == "" {
|
||||
token = os.Getenv("GITHUB_TOKEN")
|
||||
}
|
||||
httpClient := &http.Client{}
|
||||
|
||||
var client *github.Client
|
||||
httpClient := appsetutils.GetOptionalHTTPClient(optionalHTTPClient...)
|
||||
|
||||
if url == "" {
|
||||
client = github.NewClient(httpClient).WithAuthToken(token)
|
||||
if token == "" {
|
||||
client = github.NewClient(httpClient)
|
||||
} else {
|
||||
client = github.NewClient(httpClient).WithAuthToken(token)
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
client, err = github.NewClient(httpClient).WithEnterpriseURLs(url, url)
|
||||
if token == "" {
|
||||
client, err = github.NewClient(httpClient).WithEnterpriseURLs(url, url)
|
||||
} else {
|
||||
client, err = github.NewClient(httpClient).WithAuthToken(token).WithEnterpriseURLs(url, url)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package scm_provider
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/services/github_app_auth"
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/services/internal/github_app"
|
||||
appsetutils "github.com/argoproj/argo-cd/v3/applicationset/utils"
|
||||
)
|
||||
|
||||
func NewGithubAppProviderFor(g github_app_auth.Authentication, organization string, url string, allBranches bool) (*GithubProvider, error) {
|
||||
client, err := github_app.Client(g, url)
|
||||
func NewGithubAppProviderFor(g github_app_auth.Authentication, organization string, url string, allBranches bool, optionalHTTPClient ...*http.Client) (*GithubProvider, error) {
|
||||
httpClient := appsetutils.GetOptionalHTTPClient(optionalHTTPClient...)
|
||||
client, err := github_app.Client(g, url, httpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -267,6 +267,14 @@ func TestGithubListRepos(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
metricsCtx := &services.MetricsContext{
|
||||
AppSetNamespace: "test-ns",
|
||||
AppSetName: "test-appset",
|
||||
}
|
||||
|
||||
httpClient := services.NewGitHubMetricsClient(metricsCtx)
|
||||
*/
|
||||
func TestGithubHasPath(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
githubMockHandler(t)(w, r)
|
||||
|
||||
@@ -104,6 +104,7 @@ func (g *GitlabProvider) ListRepos(_ context.Context, cloneProtocol string) ([]*
|
||||
var repoLabels []string
|
||||
if len(gitlabRepo.Topics) == 0 {
|
||||
// fallback to for gitlab prior to 14.5
|
||||
//nolint:staticcheck
|
||||
repoLabels = gitlabRepo.TagList
|
||||
} else {
|
||||
repoLabels = gitlabRepo.Topics
|
||||
|
||||
@@ -172,9 +172,10 @@ func getApplicableFilters(filters []*Filter) map[FilterType][]*Filter {
|
||||
FilterTypeRepo: {},
|
||||
}
|
||||
for _, filter := range filters {
|
||||
if filter.FilterType == FilterTypeBranch {
|
||||
switch filter.FilterType {
|
||||
case FilterTypeBranch:
|
||||
filterMap[FilterTypeBranch] = append(filterMap[FilterTypeBranch], filter)
|
||||
} else if filter.FilterType == FilterTypeRepo {
|
||||
case FilterTypeRepo:
|
||||
filterMap[FilterTypeRepo] = append(filterMap[FilterTypeRepo], filter)
|
||||
}
|
||||
}
|
||||
|
||||
22
applicationset/services/util.go
Normal file
22
applicationset/services/util.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
bitbucketv1 "github.com/gfleury/go-bitbucket-v1"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/applicationset/utils"
|
||||
)
|
||||
|
||||
// SetupBitbucketClient configures and creates a Bitbucket API client with TLS settings
|
||||
func SetupBitbucketClient(ctx context.Context, config *bitbucketv1.Configuration, scmRootCAPath string, insecure bool, caCerts []byte) *bitbucketv1.APIClient {
|
||||
config.BasePath = utils.NormalizeBitbucketBasePath(config.BasePath)
|
||||
tlsConfig := utils.GetTlsConfig(scmRootCAPath, insecure, caCerts)
|
||||
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
transport.TLSClientConfig = tlsConfig
|
||||
config.HTTPClient = &http.Client{Transport: transport}
|
||||
|
||||
return bitbucketv1.NewAPIClient(ctx, config)
|
||||
}
|
||||
36
applicationset/services/util_test.go
Normal file
36
applicationset/services/util_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
bitbucketv1 "github.com/gfleury/go-bitbucket-v1"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSetupBitbucketClient(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
cfg := &bitbucketv1.Configuration{}
|
||||
|
||||
// Act
|
||||
client := SetupBitbucketClient(ctx, cfg, "", false, nil)
|
||||
|
||||
// Assert
|
||||
require.NotNil(t, client, "expected client to be created")
|
||||
require.NotNil(t, cfg.HTTPClient, "expected HTTPClient to be set")
|
||||
|
||||
// The transport should be a clone of DefaultTransport
|
||||
tr, ok := cfg.HTTPClient.Transport.(*http.Transport)
|
||||
require.True(t, ok, "expected HTTPClient.Transport to be *http.Transport")
|
||||
require.NotSame(t, http.DefaultTransport, tr, "transport should be a clone, not the global DefaultTransport")
|
||||
|
||||
// Ensure TLSClientConfig is set
|
||||
require.IsType(t, &tls.Config{}, tr.TLSClientConfig)
|
||||
|
||||
// Defaults from http.DefaultTransport.Clone() should be preserved
|
||||
require.Greater(t, tr.IdleConnTimeout, time.Duration(0), "IdleConnTimeout should be non-zero")
|
||||
require.Positive(t, tr.MaxIdleConns, "MaxIdleConns should be non-zero")
|
||||
require.Greater(t, tr.TLSHandshakeTimeout, time.Duration(0), "TLSHandshakeTimeout should be non-zero")
|
||||
}
|
||||
@@ -17,7 +17,7 @@ func BuildResourceStatus(statusMap map[string]argov1alpha1.ResourceStatus, apps
|
||||
status.Name = app.Name
|
||||
status.Namespace = app.Namespace
|
||||
status.Status = app.Status.Sync.Status
|
||||
status.Health = app.Status.Health.DeepCopy()
|
||||
status.Health = &argov1alpha1.HealthStatus{Status: app.Status.Health.Status}
|
||||
|
||||
statusMap[app.Name] = status
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
. "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
. "github.com/argoproj/argo-cd/v3/pkg/client/listers/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
listers "github.com/argoproj/argo-cd/v3/pkg/client/listers/application/v1alpha1"
|
||||
)
|
||||
|
||||
// Implements AppsetLister interface with controller-runtime client
|
||||
@@ -15,16 +15,16 @@ type AppsetLister struct {
|
||||
Client ctrlclient.Client
|
||||
}
|
||||
|
||||
func NewAppsetLister(client ctrlclient.Client) ApplicationSetLister {
|
||||
func NewAppsetLister(client ctrlclient.Client) listers.ApplicationSetLister {
|
||||
return &AppsetLister{Client: client}
|
||||
}
|
||||
|
||||
func (l *AppsetLister) List(_ labels.Selector) (ret []*ApplicationSet, err error) {
|
||||
func (l *AppsetLister) List(_ labels.Selector) (ret []*v1alpha1.ApplicationSet, err error) {
|
||||
return clientListAppsets(l.Client, ctrlclient.ListOptions{})
|
||||
}
|
||||
|
||||
// ApplicationSets returns an object that can list and get ApplicationSets.
|
||||
func (l *AppsetLister) ApplicationSets(namespace string) ApplicationSetNamespaceLister {
|
||||
func (l *AppsetLister) ApplicationSets(namespace string) listers.ApplicationSetNamespaceLister {
|
||||
return &appsetNamespaceLister{
|
||||
Client: l.Client,
|
||||
Namespace: namespace,
|
||||
@@ -37,19 +37,19 @@ type appsetNamespaceLister struct {
|
||||
Namespace string
|
||||
}
|
||||
|
||||
func (n *appsetNamespaceLister) List(_ labels.Selector) (ret []*ApplicationSet, err error) {
|
||||
func (n *appsetNamespaceLister) List(_ labels.Selector) (ret []*v1alpha1.ApplicationSet, err error) {
|
||||
return clientListAppsets(n.Client, ctrlclient.ListOptions{Namespace: n.Namespace})
|
||||
}
|
||||
|
||||
func (n *appsetNamespaceLister) Get(_ string) (*ApplicationSet, error) {
|
||||
appset := ApplicationSet{}
|
||||
func (n *appsetNamespaceLister) Get(_ string) (*v1alpha1.ApplicationSet, error) {
|
||||
appset := v1alpha1.ApplicationSet{}
|
||||
err := n.Client.Get(context.TODO(), ctrlclient.ObjectKeyFromObject(&appset), &appset)
|
||||
return &appset, err
|
||||
}
|
||||
|
||||
func clientListAppsets(client ctrlclient.Client, listOptions ctrlclient.ListOptions) (ret []*ApplicationSet, err error) {
|
||||
var appsetlist ApplicationSetList
|
||||
var results []*ApplicationSet
|
||||
func clientListAppsets(client ctrlclient.Client, listOptions ctrlclient.ListOptions) (ret []*v1alpha1.ApplicationSet, err error) {
|
||||
var appsetlist v1alpha1.ApplicationSetList
|
||||
var results []*v1alpha1.ApplicationSet
|
||||
|
||||
err = client.List(context.TODO(), &appsetlist, &listOptions)
|
||||
|
||||
|
||||
@@ -79,10 +79,10 @@ func CreateOrUpdate(ctx context.Context, logCtx *log.Entry, c client.Client, ign
|
||||
return a.Cmp(b) == 0
|
||||
},
|
||||
func(a, b metav1.MicroTime) bool {
|
||||
return a.UTC() == b.UTC()
|
||||
return a.UTC().Equal(b.UTC())
|
||||
},
|
||||
func(a, b metav1.Time) bool {
|
||||
return a.UTC() == b.UTC()
|
||||
return a.UTC().Equal(b.UTC())
|
||||
},
|
||||
func(a, b labels.Selector) bool {
|
||||
return a.String() == b.String()
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
)
|
||||
|
||||
func Test_applyIgnoreDifferences(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
appMeta := metav1.TypeMeta{
|
||||
APIVersion: v1alpha1.ApplicationSchemaGroupVersionKind.GroupVersion().String(),
|
||||
Kind: v1alpha1.ApplicationSchemaGroupVersionKind.Kind,
|
||||
|
||||
@@ -13,20 +13,11 @@ func ConvertToMapStringString(mapStringInterface map[string]any) map[string]stri
|
||||
return mapStringString
|
||||
}
|
||||
|
||||
func ConvertToMapStringInterface(mapStringString map[string]string) map[string]any {
|
||||
mapStringInterface := make(map[string]any, len(mapStringString))
|
||||
|
||||
for key, value := range mapStringString {
|
||||
mapStringInterface[key] = value
|
||||
}
|
||||
return mapStringInterface
|
||||
}
|
||||
|
||||
func CombineStringMaps(aSI map[string]any, bSI map[string]any) (map[string]string, error) {
|
||||
func CombineStringMaps(aSI map[string]any, bSI map[string]any) (map[string]any, error) {
|
||||
a := ConvertToMapStringString(aSI)
|
||||
b := ConvertToMapStringString(bSI)
|
||||
|
||||
res := map[string]string{}
|
||||
res := map[string]any{}
|
||||
|
||||
for k, v := range a {
|
||||
res[k] = v
|
||||
@@ -42,21 +33,3 @@ func CombineStringMaps(aSI map[string]any, bSI map[string]any) (map[string]strin
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// CombineStringMapsAllowDuplicates merges two maps. Where there are duplicates, take the latter map's value.
|
||||
func CombineStringMapsAllowDuplicates(aSI map[string]any, bSI map[string]any) (map[string]string, error) {
|
||||
a := ConvertToMapStringString(aSI)
|
||||
b := ConvertToMapStringString(bSI)
|
||||
|
||||
res := map[string]string{}
|
||||
|
||||
for k, v := range a {
|
||||
res[k] = v
|
||||
}
|
||||
|
||||
for k, v := range b {
|
||||
res[k] = v
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user