mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-03-06 16:38:46 +01:00
Compare commits
493 Commits
update-ver
...
release-2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf0fa07aed | ||
|
|
e19aaaa73a | ||
|
|
6ebbc75e2a | ||
|
|
dbb7f0363e | ||
|
|
aa77055fd4 | ||
|
|
5aa0bb9124 | ||
|
|
b5bdef0d06 | ||
|
|
4e2fe302c3 | ||
|
|
35a7d6c7fa | ||
|
|
c227d2277b | ||
|
|
bc54c768ae | ||
|
|
a67037a944 | ||
|
|
47f929501d | ||
|
|
f2ae45b10a | ||
|
|
85c70c7f4b | ||
|
|
11959ade81 | ||
|
|
7893979a1e | ||
|
|
a64649dd05 | ||
|
|
a0b0c3bfdb | ||
|
|
ae04112320 | ||
|
|
a5ae7bd161 | ||
|
|
0fa1c8b0d6 | ||
|
|
c5a252c4cc | ||
|
|
e94f6a8f84 | ||
|
|
6f9ae9d4f7 | ||
|
|
08fdc9c7c4 | ||
|
|
9324949efc | ||
|
|
e2a36c3e7a | ||
|
|
36b8a12a38 | ||
|
|
3be5efa79c | ||
|
|
231e359c22 | ||
|
|
2a22e19e06 | ||
|
|
17b0df1168 | ||
|
|
4df9a46e72 | ||
|
|
7113616085 | ||
|
|
c74355d838 | ||
|
|
4629a2b033 | ||
|
|
3983cc4b9d | ||
|
|
951656f083 | ||
|
|
02f7c231b5 | ||
|
|
6f2274a6a1 | ||
|
|
36f80e5f5c | ||
|
|
2b45cc8478 | ||
|
|
152565a32e | ||
|
|
3ef5ba76a9 | ||
|
|
104fb13769 | ||
|
|
1b025d2eaa | ||
|
|
6e5d303629 | ||
|
|
0b459f224b | ||
|
|
c80e77165c | ||
|
|
17e831eec6 | ||
|
|
9b00d1b712 | ||
|
|
40fb89fafb | ||
|
|
b84b143306 | ||
|
|
d6f3b02912 | ||
|
|
a6c84628ca | ||
|
|
ac694eb8f4 | ||
|
|
a2965ac6cd | ||
|
|
b87a3a8c94 | ||
|
|
35ab139a3f | ||
|
|
6f7af53bea | ||
|
|
684c033e72 | ||
|
|
113b53859d | ||
|
|
bd6e79da98 | ||
|
|
fd63155b0e | ||
|
|
85025e1dcb | ||
|
|
b3ba6e1844 | ||
|
|
870965d081 | ||
|
|
7415f47b2b | ||
|
|
a9c87d2ad7 | ||
|
|
0a4043099b | ||
|
|
ebab8ec625 | ||
|
|
c90d7baf31 | ||
|
|
d23e132bdf | ||
|
|
5698864d60 | ||
|
|
0c2f16b811 | ||
|
|
4e936006f7 | ||
|
|
365a1fc3e3 | ||
|
|
4a459f2f30 | ||
|
|
3bee2940f0 | ||
|
|
dffdb6bd03 | ||
|
|
330bcd2673 | ||
|
|
908c51bc5c | ||
|
|
c24062573f | ||
|
|
c279299281 | ||
|
|
24f8d82954 | ||
|
|
1ab570ad96 | ||
|
|
8612a99fe8 | ||
|
|
33fa128bad | ||
|
|
4e79aab03d | ||
|
|
7b89ae4424 | ||
|
|
2577d51796 | ||
|
|
2f8533efeb | ||
|
|
c698426a5c | ||
|
|
77556d9e64 | ||
|
|
1391ba7214 | ||
|
|
44e52c4ae7 | ||
|
|
4b55084a8f | ||
|
|
c689ea4957 | ||
|
|
ea9827791f | ||
|
|
dbdfc71270 | ||
|
|
31473491f5 | ||
|
|
016c2f25cd | ||
|
|
31e6e28ca4 | ||
|
|
f07088281b | ||
|
|
bf77b09b7b | ||
|
|
e982e0b80e | ||
|
|
3a468c6862 | ||
|
|
383f2a288b | ||
|
|
e006908fe9 | ||
|
|
2bc94af7bd | ||
|
|
b9a32bb86e | ||
|
|
356e33ac29 | ||
|
|
9ffef110da | ||
|
|
af721bbb63 | ||
|
|
91d249ec84 | ||
|
|
004ca26b9e | ||
|
|
2a17ca57ec | ||
|
|
89495d72df | ||
|
|
885bb57ae8 | ||
|
|
d6d7b1452d | ||
|
|
490c78b75f | ||
|
|
092e55e279 | ||
|
|
fe526dd33c | ||
|
|
cade0e970d | ||
|
|
e58eaaf36f | ||
|
|
0c4e249922 | ||
|
|
8ea6650dd7 | ||
|
|
804d4b8ca6 | ||
|
|
f33c4e6884 | ||
|
|
d1be2979a4 | ||
|
|
0872b762fb | ||
|
|
eeb846169d | ||
|
|
3535ab9400 | ||
|
|
1ee5010d6d | ||
|
|
cdcbe1b667 | ||
|
|
fd7c905b3a | ||
|
|
8b7ad25121 | ||
|
|
01e75ae295 | ||
|
|
e671cd447f | ||
|
|
7dd040c095 | ||
|
|
b4e29cff73 | ||
|
|
0d5ef9c835 | ||
|
|
2e92d12760 | ||
|
|
806c46f508 | ||
|
|
deb11bb2cf | ||
|
|
17737ebbe6 | ||
|
|
ca1d49062a | ||
|
|
f17eb7896a | ||
|
|
d94e07820f | ||
|
|
7852e44a2a | ||
|
|
40fda394eb | ||
|
|
0dc3003da0 | ||
|
|
34f6e86980 | ||
|
|
ec382b14a1 | ||
|
|
bae90d4079 | ||
|
|
bd016de57a | ||
|
|
927c04d8d5 | ||
|
|
3f42c538a1 | ||
|
|
826a11fdbe | ||
|
|
cfbbcef6f0 | ||
|
|
d9975b2fde | ||
|
|
dd61b74f72 | ||
|
|
f21034c2df | ||
|
|
d8dcc97f95 | ||
|
|
5d71e25ed4 | ||
|
|
f7a80fcb51 | ||
|
|
ffe3d47528 | ||
|
|
2f0e6e78c1 | ||
|
|
eee1a8add2 | ||
|
|
c71664849a | ||
|
|
27e9c13fb8 | ||
|
|
982300a006 | ||
|
|
68a0d00f97 | ||
|
|
af7f8af362 | ||
|
|
dacb2873f0 | ||
|
|
b0a1d82309 | ||
|
|
9612f73dbd | ||
|
|
5dd9bdc37c | ||
|
|
80ea9798ca | ||
|
|
05645b051e | ||
|
|
9a79b19bdf | ||
|
|
667800737d | ||
|
|
6cece3f550 | ||
|
|
50325a908a | ||
|
|
b5b443499b | ||
|
|
5d1d64fe83 | ||
|
|
83fa035f55 | ||
|
|
3ddee6d73f | ||
|
|
53c582bced | ||
|
|
00d995d3fa | ||
|
|
b1efb745f0 | ||
|
|
d8c6e19501 | ||
|
|
ca03596ea4 | ||
|
|
7c4eee26ef | ||
|
|
d36d31b367 | ||
|
|
d75aaaa0f7 | ||
|
|
e03a7b76dd | ||
|
|
5cde94c9ab | ||
|
|
f2b61ed39f | ||
|
|
d24b263601 | ||
|
|
2d6d30c1df | ||
|
|
fa74998f45 | ||
|
|
d11142e321 | ||
|
|
e45ff37aad | ||
|
|
9ec2a2e93c | ||
|
|
5470a48c82 | ||
|
|
a147c498e9 | ||
|
|
1477b0d874 | ||
|
|
be263caab3 | ||
|
|
5976d70460 | ||
|
|
5c581e75d7 | ||
|
|
a3591cdc65 | ||
|
|
e8f512686e | ||
|
|
46d4609921 | ||
|
|
6424cdeeea | ||
|
|
6f8aa41714 | ||
|
|
8388ff7078 | ||
|
|
c17aaee361 | ||
|
|
b70eacaadd | ||
|
|
be64ee5967 | ||
|
|
1c473be935 | ||
|
|
cea4f20463 | ||
|
|
3bcf92b11e | ||
|
|
62b1afe03e | ||
|
|
057e156132 | ||
|
|
793a818281 | ||
|
|
88994ea5cd | ||
|
|
771012bb65 | ||
|
|
a650701834 | ||
|
|
9d9371e06d | ||
|
|
9d904ae7c0 | ||
|
|
54c3f814a7 | ||
|
|
ec2fff5ea9 | ||
|
|
2170f3a832 | ||
|
|
ff6212fb2e | ||
|
|
ac75f954c9 | ||
|
|
722f58d5ed | ||
|
|
4b06175dd9 | ||
|
|
1c7eed15a6 | ||
|
|
2cfecf9b1b | ||
|
|
8f2750c532 | ||
|
|
a79e020821 | ||
|
|
c55ae0e8f7 | ||
|
|
2272ca10b7 | ||
|
|
f4708f4121 | ||
|
|
be0edcda16 | ||
|
|
303aa01979 | ||
|
|
57b557dc26 | ||
|
|
150a1844b0 | ||
|
|
446a030610 | ||
|
|
dc3ac021d1 | ||
|
|
a5112a68c3 | ||
|
|
241d377ced | ||
|
|
4ca903bf38 | ||
|
|
cdef31276a | ||
|
|
70473d2640 | ||
|
|
59e6f2c3ac | ||
|
|
41dc06d903 | ||
|
|
b392917e8d | ||
|
|
3a1030bc24 | ||
|
|
698aef34ec | ||
|
|
a952b016ce | ||
|
|
89ba316138 | ||
|
|
a400ad11d7 | ||
|
|
8f63ba3ee7 | ||
|
|
dea3848cae | ||
|
|
0496867b90 | ||
|
|
0cf9dee3c4 | ||
|
|
3dabad5b82 | ||
|
|
0deed6d265 | ||
|
|
8785b30268 | ||
|
|
a89adf3c55 | ||
|
|
435d17504b | ||
|
|
cec07ee1ce | ||
|
|
bbc51fba03 | ||
|
|
3185cc081c | ||
|
|
b47daa589c | ||
|
|
3cb7ecae5d | ||
|
|
bc9e7bd92b | ||
|
|
495d093096 | ||
|
|
ecbe53ce30 | ||
|
|
9403f256c0 | ||
|
|
a66bfcc594 | ||
|
|
653b485608 | ||
|
|
523f6231e7 | ||
|
|
0faa56b1ff | ||
|
|
f63b856394 | ||
|
|
bbe0926099 | ||
|
|
c9c6d7b49a | ||
|
|
7165540ec4 | ||
|
|
7da7259d9f | ||
|
|
0cecf7031d | ||
|
|
eb31712af7 | ||
|
|
74f2396dcf | ||
|
|
99d8024a2f | ||
|
|
2d2a020cf6 | ||
|
|
36e292ce84 | ||
|
|
646a6836de | ||
|
|
79a15fcb05 | ||
|
|
8c07ef5d66 | ||
|
|
e5a69e5fd4 | ||
|
|
08747292cf | ||
|
|
aa3b95561b | ||
|
|
af1afdaa31 | ||
|
|
bd3130b4fd | ||
|
|
f86f48fe67 | ||
|
|
c32d5dd2f3 | ||
|
|
3266b0ff63 | ||
|
|
14e20f3104 | ||
|
|
0f2126339e | ||
|
|
0c143f91ab | ||
|
|
330252dffc | ||
|
|
c30ab21454 | ||
|
|
d3ec7f8f4f | ||
|
|
34ec918fae | ||
|
|
b334da35aa | ||
|
|
902355bd01 | ||
|
|
28d68b723b | ||
|
|
739f4a95f6 | ||
|
|
e233334a4b | ||
|
|
3c6a6e1ee3 | ||
|
|
5d136037d8 | ||
|
|
e43de8216b | ||
|
|
ef42c6e8ab | ||
|
|
4ecffdd236 | ||
|
|
b2c59014a7 | ||
|
|
4289285188 | ||
|
|
d1dc735dc1 | ||
|
|
9f3d70d35b | ||
|
|
8840929187 | ||
|
|
ee983fe8ac | ||
|
|
0018fcc413 | ||
|
|
68c68f1a05 | ||
|
|
ccb2266f7c | ||
|
|
1d87c77b40 | ||
|
|
8c0456be18 | ||
|
|
26d5ad6f00 | ||
|
|
15482be82b | ||
|
|
c8e44ed080 | ||
|
|
fd5f3c4cc2 | ||
|
|
cdfbe6046b | ||
|
|
e9e4f50c1a | ||
|
|
f8d0d5970f | ||
|
|
d7cc8da4de | ||
|
|
ec2340a11f | ||
|
|
8e8970e2f4 | ||
|
|
1b0bf87e7b | ||
|
|
129892b5b6 | ||
|
|
980d60b292 | ||
|
|
4e8b0c09d5 | ||
|
|
09d7bc4c36 | ||
|
|
7c8b3d31a1 | ||
|
|
187c7d0993 | ||
|
|
60b7a315da | ||
|
|
42479f5f2c | ||
|
|
4b56525613 | ||
|
|
b8b678635b | ||
|
|
2d145f5107 | ||
|
|
3ca9416a15 | ||
|
|
0eac76938d | ||
|
|
fb4dd6cc47 | ||
|
|
e731520c76 | ||
|
|
21585ecd6a | ||
|
|
c4a7930994 | ||
|
|
944eaddd8d | ||
|
|
fab1580aea | ||
|
|
40fc133d7d | ||
|
|
e7e0700e7e | ||
|
|
cf7421e0b3 | ||
|
|
ddb2743701 | ||
|
|
50b2f03657 | ||
|
|
32be59c8be | ||
|
|
d25bbab019 | ||
|
|
0b289da7fe | ||
|
|
d685f156a7 | ||
|
|
228310d971 | ||
|
|
182eb05bf6 | ||
|
|
8a16fcb4af | ||
|
|
550b498de1 | ||
|
|
01a1ada7b7 | ||
|
|
4c982b22a5 | ||
|
|
dfa80652ee | ||
|
|
e4a7c2efbe | ||
|
|
7192eabaf4 | ||
|
|
9fa2901ad9 | ||
|
|
834d01e6e0 | ||
|
|
3b73678993 | ||
|
|
41c16eee00 | ||
|
|
9e594d495e | ||
|
|
7a5c25f58f | ||
|
|
1de8f94cdc | ||
|
|
9cb8fdaec1 | ||
|
|
d839933f0a | ||
|
|
cd05e6809f | ||
|
|
43c8d62362 | ||
|
|
d49d2aef16 | ||
|
|
4def3b859c | ||
|
|
0f5131a497 | ||
|
|
0994ec8845 | ||
|
|
8c797b0629 | ||
|
|
dddee33d64 | ||
|
|
d8112886ce | ||
|
|
46e6b79899 | ||
|
|
cd46c76ef8 | ||
|
|
64e718c549 | ||
|
|
96567956d0 | ||
|
|
d01d67b2fc | ||
|
|
79adf05a2c | ||
|
|
6bd178debc | ||
|
|
fe86d68c78 | ||
|
|
f40e7772c5 | ||
|
|
3b369eaf46 | ||
|
|
47554e4c1d | ||
|
|
b6dba43907 | ||
|
|
488583fedd | ||
|
|
032a596ef5 | ||
|
|
dfa11ae461 | ||
|
|
2a4c4a7349 | ||
|
|
9a4db2abf9 | ||
|
|
cae3817c79 | ||
|
|
4a0b61446a | ||
|
|
c3874a223e | ||
|
|
41e91d5acd | ||
|
|
4f6f1ccee5 | ||
|
|
6398c1f9ca | ||
|
|
5662367474 | ||
|
|
4606d1928b | ||
|
|
e0bae9f1c0 | ||
|
|
74839c8821 | ||
|
|
04964c908c | ||
|
|
8385d27e84 | ||
|
|
5d80789a26 | ||
|
|
591b33d956 | ||
|
|
82604652ee | ||
|
|
c651bd8de5 | ||
|
|
4ce4885e73 | ||
|
|
b6db057cf2 | ||
|
|
331cd38020 | ||
|
|
0a8a71e12c | ||
|
|
67c254eaa0 | ||
|
|
b44c805e9d | ||
|
|
babe35722a | ||
|
|
87a7dff458 | ||
|
|
ba439377d9 | ||
|
|
fa5ce09533 | ||
|
|
aed1be6388 | ||
|
|
f356a54f19 | ||
|
|
fafee48b3c | ||
|
|
ec3ea48cda | ||
|
|
daef931442 | ||
|
|
f12a43a275 | ||
|
|
f4962d4165 | ||
|
|
ce36b6e0e0 | ||
|
|
325878c4d1 | ||
|
|
f61f94f7fc | ||
|
|
1e7d8ad607 | ||
|
|
f8e016dea8 | ||
|
|
ea3a67a9f3 | ||
|
|
0c56178dfa | ||
|
|
51c241b06d | ||
|
|
c2a118137e | ||
|
|
eb6de27e78 | ||
|
|
7825821c1c | ||
|
|
ec0cb9db2b | ||
|
|
6a3a81cbe7 | ||
|
|
a08282bf6b | ||
|
|
3fcc8eadaa | ||
|
|
d4714f34ca | ||
|
|
0b12edda14 | ||
|
|
4484ce8558 | ||
|
|
c985b1690e | ||
|
|
2e102e1dd8 | ||
|
|
42bdb5ab1b | ||
|
|
29c7f5ad6c | ||
|
|
1a4a878703 | ||
|
|
56d173390d | ||
|
|
17d0acabce | ||
|
|
819e284ebd | ||
|
|
86926d716a | ||
|
|
e8331b4d12 | ||
|
|
ef39bd24ab | ||
|
|
ce0c3196d1 | ||
|
|
460ecf3ac2 | ||
|
|
8c5af34eaf | ||
|
|
f0911f2c7c | ||
|
|
fa6aaaf0f6 | ||
|
|
7d56e7cbd4 | ||
|
|
c5f49e10f9 | ||
|
|
e66ae4d2a3 | ||
|
|
4b7f7c3e0e | ||
|
|
139fc16dfb | ||
|
|
ff27043888 |
49
.github/workflows/ci-build.yaml
vendored
49
.github/workflows/ci-build.yaml
vendored
@@ -13,7 +13,7 @@ on:
|
||||
|
||||
env:
|
||||
# Golang version to use across CI steps
|
||||
GOLANG_VERSION: '1.19'
|
||||
GOLANG_VERSION: '1.20'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -28,9 +28,9 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Download all Go modules
|
||||
@@ -46,9 +46,9 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Restore go build cache
|
||||
@@ -70,13 +70,13 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@0ad9a0988b3973e851ab0a07adf248ec2e100376 # v3.3.1
|
||||
uses: golangci/golangci-lint-action@639cd343e1d3b897ff35927a75193d57cfcba299 # v3.6.0
|
||||
with:
|
||||
version: v1.51.0
|
||||
args: --timeout 10m --exclude SA5011 --verbose
|
||||
@@ -93,11 +93,11 @@ jobs:
|
||||
- name: Create checkout directory
|
||||
run: mkdir -p ~/go/src/github.com/argoproj
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
- name: Create symlink in GOPATH
|
||||
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Install required packages
|
||||
@@ -149,7 +149,7 @@ jobs:
|
||||
path: test-results/
|
||||
|
||||
test-go-race:
|
||||
name: Run unit tests with -race, for Go packages
|
||||
name: Run unit tests with -race for Go packages
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- build-go
|
||||
@@ -160,11 +160,11 @@ jobs:
|
||||
- name: Create checkout directory
|
||||
run: mkdir -p ~/go/src/github.com/argoproj
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
- name: Create symlink in GOPATH
|
||||
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Install required packages
|
||||
@@ -215,9 +215,9 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: Create symlink in GOPATH
|
||||
@@ -263,11 +263,11 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
||||
with:
|
||||
node-version: '18.15.0'
|
||||
node-version: '20.3.1'
|
||||
- name: Restore node dependency cache
|
||||
id: cache-dependencies
|
||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||
@@ -300,7 +300,7 @@ jobs:
|
||||
sonar_secret: ${{ secrets.SONAR_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Restore node dependency cache
|
||||
@@ -325,7 +325,7 @@ jobs:
|
||||
name: test-results
|
||||
path: test-results
|
||||
- name: Upload code coverage information to codecov.io
|
||||
uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1
|
||||
uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4
|
||||
with:
|
||||
file: coverage.out
|
||||
- name: Perform static code analysis using SonarCloud
|
||||
@@ -361,7 +361,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
k3s-version: [v1.26.0, v1.25.4, v1.24.3, v1.23.3]
|
||||
k3s-version: [v1.27.2, v1.26.0, v1.25.4, v1.24.3]
|
||||
needs:
|
||||
- build-go
|
||||
env:
|
||||
@@ -379,9 +379,9 @@ jobs:
|
||||
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
- name: GH actions workaround - Kill XSP4 process
|
||||
@@ -397,6 +397,7 @@ jobs:
|
||||
sudo mkdir -p $HOME/.kube && sudo chown -R runner $HOME/.kube
|
||||
sudo k3s kubectl config view --raw > $HOME/.kube/config
|
||||
sudo chown runner $HOME/.kube/config
|
||||
sudo chmod go-r $HOME/.kube/config
|
||||
kubectl version
|
||||
- name: Restore go build cache
|
||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||
@@ -425,9 +426,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.36.0
|
||||
docker pull ghcr.io/dexidp/dex:v2.37.0
|
||||
docker pull argoproj/argo-cd-ci-builder:v1.0.0
|
||||
docker pull redis:7.0.11-alpine
|
||||
docker pull redis:7.0.15-alpine
|
||||
- name: Create target directory for binaries in the build-process
|
||||
run: |
|
||||
mkdir -p dist
|
||||
|
||||
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
44
.github/workflows/image-reuse.yaml
vendored
44
.github/workflows/image-reuse.yaml
vendored
@@ -58,28 +58,26 @@ jobs:
|
||||
image-digest: ${{ steps.image.outputs.digest }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.3.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.3.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
if: ${{ github.ref_type == 'tag'}}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.3.0
|
||||
if: ${{ github.ref_type != 'tag'}}
|
||||
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
|
||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1
|
||||
with:
|
||||
go-version: ${{ inputs.go-version }}
|
||||
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@c3667d99424e7e6047999fb6246c0da843953c65 # v3.0.1
|
||||
with:
|
||||
cosign-release: 'v2.0.0'
|
||||
uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 # v3.4.0
|
||||
|
||||
- uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0
|
||||
- uses: docker/setup-buildx-action@4b4e9c3e2d4531116a6f8ba8e71fc6e2cb6e6c8c # v2.5.0
|
||||
- uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
|
||||
- uses: docker/setup-buildx-action@ecf95283f03858871ff00b787d79c419715afc34 # v2.7.0
|
||||
|
||||
- name: Setup tags for container image as a CSV type
|
||||
run: |
|
||||
@@ -106,7 +104,7 @@ jobs:
|
||||
echo 'EOF' >> $GITHUB_ENV
|
||||
|
||||
- name: Login to Quay.io
|
||||
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
|
||||
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ secrets.quay_username }}
|
||||
@@ -114,7 +112,7 @@ jobs:
|
||||
if: ${{ inputs.quay_image_name && inputs.push }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
|
||||
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ secrets.ghcr_username }}
|
||||
@@ -122,15 +120,30 @@ jobs:
|
||||
if: ${{ inputs.ghcr_image_name && inputs.push }}
|
||||
|
||||
- name: Login to dockerhub Container Registry
|
||||
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
|
||||
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
|
||||
with:
|
||||
username: ${{ secrets.docker_username }}
|
||||
password: ${{ secrets.docker_password }}
|
||||
if: ${{ inputs.docker_image_name && inputs.push }}
|
||||
|
||||
- name: Set up build args for container image
|
||||
run: |
|
||||
echo "GIT_TAG=$(if [ -z "`git status --porcelain`" ]; then git describe --exact-match --tags HEAD 2>/dev/null; fi)" >> $GITHUB_ENV
|
||||
echo "GIT_COMMIT=$(git rev-parse HEAD)" >> $GITHUB_ENV
|
||||
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
|
||||
echo "GIT_TREE_STATE=$(if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)" >> $GITHUB_ENV
|
||||
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@4d9e71b726748f254fe64fa44d273194bd18ec91
|
||||
with:
|
||||
large-packages: false
|
||||
docker-images: false
|
||||
swap-storage: false
|
||||
tool-cache: false
|
||||
|
||||
- name: Build and push container image
|
||||
id: image
|
||||
uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 #v4.0.0
|
||||
uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 #v4.1.1
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ inputs.platforms }}
|
||||
@@ -139,7 +152,12 @@ jobs:
|
||||
target: ${{ inputs.target }}
|
||||
provenance: false
|
||||
sbom: false
|
||||
|
||||
build-args: |
|
||||
GIT_TAG=${{env.GIT_TAG}}
|
||||
GIT_COMMIT=${{env.GIT_COMMIT}}
|
||||
BUILD_DATE=${{env.BUILD_DATE}}
|
||||
GIT_TREE_STATE=${{env.GIT_TREE_STATE}}
|
||||
|
||||
- name: Sign container images
|
||||
run: |
|
||||
for signing_tag in $SIGNING_TAGS; do
|
||||
|
||||
10
.github/workflows/image.yaml
vendored
10
.github/workflows/image.yaml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
image-tag: ${{ steps.image.outputs.tag}}
|
||||
platforms: ${{ steps.platforms.outputs.platforms }}
|
||||
steps:
|
||||
- uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
|
||||
- name: Set image tag for ghcr
|
||||
run: echo "tag=$(cat ./VERSION)-${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
uses: ./.github/workflows/image-reuse.yaml
|
||||
with:
|
||||
# Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations)
|
||||
go-version: 1.19
|
||||
go-version: 1.20
|
||||
platforms: ${{ needs.set-vars.outputs.platforms }}
|
||||
push: false
|
||||
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
quay_image_name: quay.io/argoproj/argocd:latest
|
||||
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)
|
||||
go-version: 1.19
|
||||
go-version: 1.20
|
||||
platforms: ${{ needs.set-vars.outputs.platforms }}
|
||||
push: true
|
||||
secrets:
|
||||
@@ -86,7 +86,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@v1.5.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.10.0
|
||||
with:
|
||||
image: ghcr.io/argoproj/argo-cd/argocd
|
||||
digest: ${{ needs.build-and-publish.outputs.image-digest }}
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.3.0
|
||||
- run: git clone "https://$TOKEN@github.com/argoproj/argoproj-deployments"
|
||||
env:
|
||||
TOKEN: ${{ secrets.TOKEN }}
|
||||
|
||||
11
.github/workflows/init-release.yaml
vendored
11
.github/workflows/init-release.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.2.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -56,8 +56,15 @@ jobs:
|
||||
make manifests-local VERSION=${{ inputs.TARGET_VERSION }}
|
||||
git diff
|
||||
|
||||
- name: Generate version compatibility table
|
||||
run: |
|
||||
git stash
|
||||
bash hack/update-supported-versions.sh
|
||||
git add -u .
|
||||
git stash pop
|
||||
|
||||
- name: Create pull request
|
||||
uses: peter-evans/create-pull-request@38e0b6e68b4c852a5500a94740f0e535e0d7ba54 # v4.2.4
|
||||
uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 # v5.0.2
|
||||
with:
|
||||
commit-message: "Bump version to ${{ inputs.TARGET_VERSION }}"
|
||||
title: "Bump version to ${{ inputs.TARGET_VERSION }} on ${{ inputs.TARGET_BRANCH }} branch"
|
||||
|
||||
2
.github/workflows/pr-title-check.yml
vendored
2
.github/workflows/pr-title-check.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
name: Validate PR Title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: thehanimo/pr-title-checker@cdafc664bf9b25678d4e6df76ff67b2fe21bb5d2 # v1.3.7
|
||||
- uses: thehanimo/pr-title-checker@0cf5902181e78341bb97bb06646396e5bd354b3f # v1.4.0
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
configuration_path: ".github/pr-title-checker-config.json"
|
||||
|
||||
69
.github/workflows/release.yaml
vendored
69
.github/workflows/release.yaml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
GOLANG_VERSION: '1.19' # Note: go-version must also be set in job argocd-image.with.go-version
|
||||
GOLANG_VERSION: '1.20' # Note: go-version must also be set in job argocd-image.with.go-version
|
||||
|
||||
jobs:
|
||||
argocd-image:
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
with:
|
||||
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)
|
||||
go-version: 1.19
|
||||
go-version: 1.20
|
||||
platforms: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
|
||||
push: true
|
||||
secrets:
|
||||
@@ -38,7 +38,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@v1.5.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.10.0
|
||||
with:
|
||||
image: quay.io/argoproj/argocd
|
||||
digest: ${{ needs.argocd-image.outputs.image-digest }}
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
|
||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
|
||||
@@ -88,14 +88,14 @@ jobs:
|
||||
echo "GIT_TREE_STATE=$(if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)" >> $GITHUB_ENV
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@f82d6c1c344bcacabba2c841718984797f664a6b # v4.2.0
|
||||
uses: goreleaser/goreleaser-action@336e29918d653399e599bfca99fadc1d7ffbc9f7 # v4.3.0
|
||||
id: run-goreleaser
|
||||
with:
|
||||
version: latest
|
||||
args: release --clean --timeout 55m
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
KUBECTL_VERSION: ${{ env.KUBECTL_VERSION }}
|
||||
KUBECTL_VERSION: ${{ env.KUBECTL_VERSION }}
|
||||
GIT_TREE_STATE: ${{ env.GIT_TREE_STATE }}
|
||||
|
||||
- name: Generate subject for provenance
|
||||
@@ -120,39 +120,35 @@ 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@v1.5.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0
|
||||
with:
|
||||
base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
|
||||
provenance-name: "argocd-cli.intoto.jsonl"
|
||||
upload-assets: true
|
||||
|
||||
generate-sbom:
|
||||
name: Create Sbom and sign assets
|
||||
name: Create SBOM and generate hash
|
||||
needs:
|
||||
- argocd-image
|
||||
- goreleaser
|
||||
permissions:
|
||||
contents: write # Needed for release uploads
|
||||
id-token: write # Needed for signing Sbom
|
||||
outputs:
|
||||
hashes: ${{ steps.sbom-hash.outputs.hashes}}
|
||||
if: github.repository == 'argoproj/argo-cd'
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.2.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
|
||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1
|
||||
with:
|
||||
go-version: ${{ env.GOLANG_VERSION }}
|
||||
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@c3667d99424e7e6047999fb6246c0da843953c65 # v3.0.1
|
||||
with:
|
||||
cosign-release: 'v2.0.0'
|
||||
|
||||
- name: Generate SBOM (spdx)
|
||||
id: spdx-builder
|
||||
env:
|
||||
@@ -183,21 +179,36 @@ jobs:
|
||||
|
||||
cd /tmp && tar -zcf sbom.tar.gz *.spdx
|
||||
|
||||
- name: Sign SBOM
|
||||
- name: Generate SBOM hash
|
||||
shell: bash
|
||||
id: sbom-hash
|
||||
run: |
|
||||
cosign sign-blob \
|
||||
--output-certificate=/tmp/sbom.tar.gz.pem \
|
||||
--output-signature=/tmp/sbom.tar.gz.sig \
|
||||
-y \
|
||||
/tmp/sbom.tar.gz
|
||||
# sha256sum generates sha256 hash for sbom.
|
||||
# base64 -w0 encodes to base64 and outputs on a single line.
|
||||
# sha256sum /tmp/sbom.tar.gz ... | base64 -w0
|
||||
echo "hashes=$(sha256sum /tmp/sbom.tar.gz | base64 -w0)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload SBOM and signature assets
|
||||
- name: Upload SBOM
|
||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
files: |
|
||||
/tmp/sbom.tar.*
|
||||
/tmp/sbom.tar.gz
|
||||
|
||||
sbom-provenance:
|
||||
needs: [generate-sbom]
|
||||
permissions:
|
||||
actions: read # for detecting the Github Actions environment
|
||||
id-token: write # Needed for provenance signing and ID
|
||||
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@v1.10.0
|
||||
with:
|
||||
base64-subjects: "${{ needs.generate-sbom.outputs.hashes }}"
|
||||
provenance-name: "argocd-sbom.intoto.jsonl"
|
||||
upload-assets: true
|
||||
|
||||
post-release:
|
||||
needs:
|
||||
@@ -211,7 +222,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.2.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -254,8 +265,8 @@ jobs:
|
||||
set -xue
|
||||
SOURCE_TAG=${{ github.ref_name }}
|
||||
VERSION_REF="${SOURCE_TAG#*v}"
|
||||
if echo "$VERSION_REF" | grep -E -- '^[0-9]+\.[0-9]+\.0$';then
|
||||
VERSION=$(awk 'BEGIN {FS=OFS="."} {$2++; print}' <<< "${VERSION_REF}")
|
||||
if echo "$VERSION_REF" | grep -E -- '^[0-9]+\.[0-9]+\.0-rc1';then
|
||||
VERSION=$(awk 'BEGIN {FS=OFS="."} {$2++; print}' <<< "${VERSION_REF%-rc1}")
|
||||
echo "Updating VERSION to: $VERSION"
|
||||
echo "UPDATE_VERSION=true" >> $GITHUB_ENV
|
||||
echo "NEW_VERSION=$VERSION" >> $GITHUB_ENV
|
||||
@@ -270,7 +281,7 @@ jobs:
|
||||
if: ${{ env.UPDATE_VERSION == 'true' }}
|
||||
|
||||
- name: Create PR to update VERSION on master branch
|
||||
uses: peter-evans/create-pull-request@38e0b6e68b4c852a5500a94740f0e535e0d7ba54 # v4.2.4
|
||||
uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 # v5.0.2
|
||||
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
@@ -30,12 +30,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@80e868c13c90f172d68d1f4501dee99e2479f7af # v2.1.3
|
||||
uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031 # v2.2.0
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
|
||||
2
.github/workflows/update-snyk.yaml
vendored
2
.github/workflows/update-snyk.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build reports
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -18,6 +18,7 @@ node_modules/
|
||||
.kube/
|
||||
./test/cmp/*.sock
|
||||
.envrc.remote
|
||||
.*.swp
|
||||
|
||||
# ignore built binaries
|
||||
cmd/argocd/argocd
|
||||
|
||||
2
.gitpod.Dockerfile
vendored
2
.gitpod.Dockerfile
vendored
@@ -13,6 +13,8 @@ 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
|
||||
|
||||
@@ -114,6 +114,7 @@ changelog:
|
||||
exclude:
|
||||
- '^test:'
|
||||
- '^.*?Bump(\([[:word:]]+\))?.+$'
|
||||
- '^.*?[Bot](\([[:word:]]+\))?.+$'
|
||||
|
||||
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
|
||||
23
Dockerfile
23
Dockerfile
@@ -1,12 +1,12 @@
|
||||
ARG BASE_IMAGE=docker.io/library/ubuntu:22.04@sha256:9a0bdde4188b896a372804be2384015e90e3f84906b750c1a53539b585fbbe7f
|
||||
ARG BASE_IMAGE=docker.io/library/ubuntu:22.04@sha256:0bced47fffa3361afa981854fcabcd4577cd43cebbb808cea2b1f33a3dd7f508
|
||||
####################################################################################################
|
||||
# Builder image
|
||||
# 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.19.6@sha256:7ce31d15a3a4dbf20446cccffa4020d3a2974ad2287d96123f55caf22c7adb71 AS builder
|
||||
FROM docker.io/library/golang:1.20.10@sha256:ed6c4a5918b0a1ffb97970f6493d742dc5c7ebf3ccbd417c215d52830b57b994 AS builder
|
||||
|
||||
RUN echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list
|
||||
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 \
|
||||
openssh-server \
|
||||
@@ -83,7 +83,7 @@ WORKDIR /home/argocd
|
||||
####################################################################################################
|
||||
# Argo CD UI stage
|
||||
####################################################################################################
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/node:18.15.0@sha256:8d9a875ee427897ef245302e31e2319385b092f1c3368b497e89790f240368f5 AS argocd-ui
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/node:20.3.1@sha256:2f0b0c15f97441defa812268ee943bbfaaf666ea6cf7cac62ee3f127906b35c6 AS argocd-ui
|
||||
|
||||
WORKDIR /src
|
||||
COPY ["ui/package.json", "ui/yarn.lock", "./"]
|
||||
@@ -101,7 +101,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.19.6@sha256:7ce31d15a3a4dbf20446cccffa4020d3a2974ad2287d96123f55caf22c7adb71 AS argocd-build
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.20.10@sha256:ed6c4a5918b0a1ffb97970f6493d742dc5c7ebf3ccbd417c215d52830b57b994 AS argocd-build
|
||||
|
||||
WORKDIR /go/src/github.com/argoproj/argo-cd
|
||||
|
||||
@@ -113,7 +113,18 @@ COPY . .
|
||||
COPY --from=argocd-ui /src/dist/app /go/src/github.com/argoproj/argo-cd/ui/dist/app
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH make argocd-all
|
||||
# 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
|
||||
RUN GIT_COMMIT=$GIT_COMMIT \
|
||||
GIT_TREE_STATE=$GIT_TREE_STATE \
|
||||
GIT_TAG=$GIT_TAG \
|
||||
BUILD_DATE=$BUILD_DATE \
|
||||
GOOS=$TARGETOS \
|
||||
GOARCH=$TARGETARCH \
|
||||
make argocd-all
|
||||
|
||||
####################################################################################################
|
||||
# Final image
|
||||
|
||||
20
Makefile
20
Makefile
@@ -9,11 +9,13 @@ GEN_RESOURCES_CLI_NAME=argocd-resources-gen
|
||||
HOST_OS:=$(shell go env GOOS)
|
||||
HOST_ARCH:=$(shell go env GOARCH)
|
||||
|
||||
TARGET_ARCH?=linux/amd64
|
||||
|
||||
VERSION=$(shell cat ${CURRENT_DIR}/VERSION)
|
||||
BUILD_DATE=$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')
|
||||
GIT_COMMIT=$(shell git rev-parse HEAD)
|
||||
GIT_TAG=$(shell if [ -z "`git status --porcelain`" ]; then git describe --exact-match --tags HEAD 2>/dev/null; fi)
|
||||
GIT_TREE_STATE=$(shell if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)
|
||||
BUILD_DATE:=$(if $(BUILD_DATE),$(BUILD_DATE),$(shell date -u +'%Y-%m-%dT%H:%M:%SZ'))
|
||||
GIT_COMMIT:=$(if $(GIT_COMMIT),$(GIT_COMMIT),$(shell git rev-parse HEAD))
|
||||
GIT_TAG:=$(if $(GIT_TAG),$(GIT_TAG),$(shell if [ -z "`git status --porcelain`" ]; then git describe --exact-match --tags HEAD 2>/dev/null; fi))
|
||||
GIT_TREE_STATE:=$(if $(GIT_TREE_STATE),$(GIT_TREE_STATE),$(shell if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi))
|
||||
VOLUME_MOUNT=$(shell if test "$(go env GOOS)" = "darwin"; then echo ":delegated"; elif test selinuxenabled; then echo ":delegated"; else echo ""; fi)
|
||||
KUBECTL_VERSION=$(shell go list -m k8s.io/client-go | head -n 1 | rev | cut -d' ' -f1 | rev)
|
||||
|
||||
@@ -283,7 +285,7 @@ controller:
|
||||
|
||||
.PHONY: build-ui
|
||||
build-ui:
|
||||
DOCKER_BUILDKIT=1 docker build -t argocd-ui --target argocd-ui .
|
||||
DOCKER_BUILDKIT=1 docker build -t argocd-ui --platform=$(TARGET_ARCH) --target argocd-ui .
|
||||
find ./ui/dist -type f -not -name gitkeep -delete
|
||||
docker run -v ${CURRENT_DIR}/ui/dist/app:/tmp/app --rm -t argocd-ui sh -c 'cp -r ./dist/app/* /tmp/app/'
|
||||
|
||||
@@ -294,7 +296,7 @@ ifeq ($(DEV_IMAGE), true)
|
||||
# the dist directory is under .dockerignore.
|
||||
IMAGE_TAG="dev-$(shell git describe --always --dirty)"
|
||||
image: build-ui
|
||||
DOCKER_BUILDKIT=1 docker build --platform=linux/amd64 -t argocd-base --target argocd-base .
|
||||
DOCKER_BUILDKIT=1 docker build --platform=$(TARGET_ARCH) -t argocd-base --target argocd-base .
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd ./cmd
|
||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-server
|
||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-application-controller
|
||||
@@ -302,10 +304,10 @@ image: build-ui
|
||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-cmp-server
|
||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-dex
|
||||
cp Dockerfile.dev dist
|
||||
DOCKER_BUILDKIT=1 docker build --platform=linux/amd64 -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) -f dist/Dockerfile.dev dist
|
||||
DOCKER_BUILDKIT=1 docker build --platform=$(TARGET_ARCH) -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) -f dist/Dockerfile.dev dist
|
||||
else
|
||||
image:
|
||||
DOCKER_BUILDKIT=1 docker build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) .
|
||||
DOCKER_BUILDKIT=1 docker build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) --platform=$(TARGET_ARCH) .
|
||||
endif
|
||||
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) ; fi
|
||||
|
||||
@@ -457,6 +459,8 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
|
||||
ARGOCD_IN_CI=$(ARGOCD_IN_CI) \
|
||||
BIN_MODE=$(ARGOCD_BIN_MODE) \
|
||||
ARGOCD_APPLICATION_NAMESPACES=argocd-e2e-external \
|
||||
ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACES=argocd-e2e-external \
|
||||
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 \
|
||||
goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START}
|
||||
|
||||
|
||||
6
Procfile
6
Procfile
@@ -1,7 +1,7 @@
|
||||
controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 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-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''}"
|
||||
api-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 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-server $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''}"
|
||||
dex: sh -c "ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/v2/cmd gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:$(grep "image: ghcr.io/dexidp/dex" manifests/base/dex/argocd-dex-server-deployment.yaml | cut -d':' -f3) dex serve /dex.yaml"
|
||||
redis: bash -c "if [ \"$ARGOCD_REDIS_LOCAL\" = 'true' ]; then redis-server --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; else docker run --rm --name argocd-redis -i -p ${ARGOCD_E2E_REDIS_PORT:-6379}:${ARGOCD_E2E_REDIS_PORT:-6379} redis:$(grep "image: redis" manifests/base/redis/argocd-redis-deployment.yaml | cut -d':' -f3) --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; fi"
|
||||
dex: sh -c "ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/v2/cmd gendexcfg -o `pwd`/dist/dex.yaml && (test -f dist/dex.yaml || { echo 'Failed to generate dex configuration'; exit 1; }) && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:$(grep "image: ghcr.io/dexidp/dex" manifests/base/dex/argocd-dex-server-deployment.yaml | cut -d':' -f3) dex serve /dex.yaml"
|
||||
redis: bash -c "if [ \"$ARGOCD_REDIS_LOCAL\" = 'true' ]; then redis-server --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; else docker run --rm --name argocd-redis -i -p ${ARGOCD_E2E_REDIS_PORT:-6379}:${ARGOCD_E2E_REDIS_PORT:-6379} docker.io/library/redis:$(grep "image: redis" manifests/base/redis/argocd-redis-deployment.yaml | cut -d':' -f3) --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; fi"
|
||||
repo-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} 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-repo-server ARGOCD_GPG_ENABLED=${ARGOCD_GPG_ENABLED:-false} $COMMAND --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --otlp-address=${ARGOCD_OTLP_ADDRESS}"
|
||||
cmp-server: [ "$ARGOCD_E2E_TEST" = 'true' ] && exit 0 || [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_BINARY_NAME=argocd-cmp-server ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} $COMMAND --config-dir-path ./test/cmp --loglevel debug --otlp-address=${ARGOCD_OTLP_ADDRESS}"
|
||||
ui: sh -c 'cd ui && ${ARGOCD_E2E_YARN_CMD:-yarn} start'
|
||||
@@ -9,4 +9,4 @@ git-server: test/fixture/testrepos/start-git.sh
|
||||
helm-registry: test/fixture/testrepos/start-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 "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 "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"
|
||||
notification: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "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"
|
||||
|
||||
@@ -35,9 +35,7 @@ impact on Argo CD before opening an issue at least roughly.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We currently support the most recent release (`N`, e.g. `1.8`) and the release
|
||||
previous to the most recent one (`N-1`, e.g. `1.7`). With the release of
|
||||
`N+1`, `N-1` drops out of support and `N` becomes `N-1`.
|
||||
We currently support the last 3 minor versions of Argo CD with security and bug fixes.
|
||||
|
||||
We regularly perform patch releases (e.g. `1.8.5` and `1.7.12`) for the
|
||||
supported versions, which will contain fixes for security vulnerabilities and
|
||||
@@ -65,9 +63,10 @@ We will publish security advisories using the
|
||||
feature to keep our community well-informed, and will credit you for your
|
||||
findings (unless you prefer to stay anonymous, of course).
|
||||
|
||||
Please report vulnerabilities by e-mail to the following address:
|
||||
There are two ways to report a vulnerability to the Argo CD team:
|
||||
|
||||
* cncf-argo-security@lists.cncf.io
|
||||
* By opening a draft GitHub security advisory: https://github.com/argoproj/argo-cd/security/advisories/new
|
||||
* By e-mail to the following address: cncf-argo-security@lists.cncf.io
|
||||
|
||||
## Internet Bug Bounty collaboration
|
||||
|
||||
|
||||
30
USERS.md
30
USERS.md
@@ -25,6 +25,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Arctiq Inc.](https://www.arctiq.ca)
|
||||
1. [ARZ Allgemeines Rechenzentrum GmbH](https://www.arz.at/)
|
||||
1. [Axual B.V.](https://axual.com)
|
||||
1. [Back Market](https://www.backmarket.com)
|
||||
1. [Baloise](https://www.baloise.com)
|
||||
1. [BCDevExchange DevOps Platform](https://bcdevexchange.org/DevOpsPlatform)
|
||||
1. [Beat](https://thebeat.co/en/)
|
||||
@@ -33,11 +34,11 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [BigPanda](https://bigpanda.io)
|
||||
1. [BioBox Analytics](https://biobox.io)
|
||||
1. [BMW Group](https://www.bmwgroup.com/)
|
||||
1. [PT Boer Technology (Btech)](https://btech.id/)
|
||||
1. [Boozt](https://www.booztgroup.com/)
|
||||
1. [Boticario](https://www.boticario.com.br/)
|
||||
1. [Bulder Bank](https://bulderbank.no)
|
||||
1. [Camptocamp](https://camptocamp.com)
|
||||
1. [Candis](https://www.candis.io)
|
||||
1. [Capital One](https://www.capitalone.com)
|
||||
1. [CARFAX](https://www.carfax.com)
|
||||
1. [CARFAX Europe](https://www.carfax.eu)
|
||||
@@ -45,21 +46,24 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Celonis](https://www.celonis.com/)
|
||||
1. [CERN](https://home.cern/)
|
||||
1. [Chargetrip](https://chargetrip.com)
|
||||
1. [Chainnodes](https://chainnodes.org)
|
||||
1. [Chime](https://www.chime.com)
|
||||
1. [Cisco ET&I](https://eti.cisco.com/)
|
||||
1. [Cloud Posse](https://www.cloudposse.com/)
|
||||
1. [Cloud Scale](https://cloudscaleinc.com/)
|
||||
1. [Cloudmate](https://cloudmt.co.kr/)
|
||||
1. [Cloudogu](https://cloudogu.com/)
|
||||
1. [Cobalt](https://www.cobalt.io/)
|
||||
1. [Codefresh](https://www.codefresh.io/)
|
||||
1. [Codility](https://www.codility.com/)
|
||||
1. [Commonbond](https://commonbond.co/)
|
||||
1. [Coralogix](https://coralogix.com/)
|
||||
1. [CROZ d.o.o.](https://croz.net/)
|
||||
1. [Crédit Agricole CIB](https://www.ca-cib.com)
|
||||
1. [CROZ d.o.o.](https://croz.net/)
|
||||
1. [CyberAgent](https://www.cyberagent.co.jp/en/)
|
||||
1. [Cybozu](https://cybozu-global.com)
|
||||
1. [D2iQ](https://www.d2iq.com)
|
||||
1. [DaoCloud](https://daocloud.io/)
|
||||
1. [Datarisk](https://www.datarisk.io/)
|
||||
1. [Deloitte](https://www.deloitte.com/)
|
||||
1. [Deutsche Telekom AG](https://telekom.com)
|
||||
@@ -96,8 +100,11 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [gloat](https://gloat.com/)
|
||||
1. [GLOBIS](https://globis.com)
|
||||
1. [Glovo](https://www.glovoapp.com)
|
||||
1. [GlueOps](https://glueops.dev)
|
||||
1. [GMETRI](https://gmetri.com/)
|
||||
1. [Gojek](https://www.gojek.io/)
|
||||
1. [GoTo](https://www.goto.com/)
|
||||
1. [GoTo Financial](https://gotofinancial.com/)
|
||||
1. [Greenpass](https://www.greenpass.com.br/)
|
||||
1. [Gridfuse](https://gridfuse.com/)
|
||||
1. [Groww](https://groww.in)
|
||||
@@ -109,6 +116,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [hipages](https://hipages.com.au/)
|
||||
1. [Hiya](https://hiya.com)
|
||||
1. [Honestbank](https://honestbank.com)
|
||||
1. [Hostinger](https://www.hostinger.com)
|
||||
1. [IBM](https://www.ibm.com/)
|
||||
1. [Ibotta](https://home.ibotta.com)
|
||||
1. [IITS-Consulting](https://iits-consulting.de)
|
||||
@@ -122,8 +130,8 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [JovianX](https://www.jovianx.com/)
|
||||
1. [Kaltura](https://corp.kaltura.com/)
|
||||
1. [Kandji](https://www.kandji.io/)
|
||||
1. [KarrotPay](https://www.daangnpay.com/)
|
||||
1. [Karrot](https://www.daangn.com/)
|
||||
1. [KarrotPay](https://www.daangnpay.com/)
|
||||
1. [Kasa](https://kasa.co.kr/)
|
||||
1. [Keeeb](https://www.keeeb.com/)
|
||||
1. [KelkooGroup](https://www.kelkoogroup.com)
|
||||
@@ -150,6 +158,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Max Kelsen](https://www.maxkelsen.com/)
|
||||
1. [MeDirect](https://medirect.com.mt/)
|
||||
1. [Meican](https://meican.com/)
|
||||
1. [Meilleurs Agents](https://www.meilleursagents.com/)
|
||||
1. [Mercedes-Benz Tech Innovation](https://www.mercedes-benz-techinnovation.com/)
|
||||
1. [Metanet](http://www.metanet.co.kr/en/)
|
||||
1. [MindSpore](https://mindspore.cn)
|
||||
@@ -170,6 +179,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Objective](https://www.objective.com.br/)
|
||||
1. [OCCMundial](https://occ.com.mx)
|
||||
1. [Octadesk](https://octadesk.com)
|
||||
1. [Olfeo](https://www.olfeo.com/)
|
||||
1. [omegaUp](https://omegaUp.com)
|
||||
1. [Omni](https://omni.se/)
|
||||
1. [openEuler](https://openeuler.org)
|
||||
@@ -182,13 +192,15 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [OpsVerse](https://opsverse.io)
|
||||
1. [Optoro](https://www.optoro.com/)
|
||||
1. [Orbital Insight](https://orbitalinsight.com/)
|
||||
1. [Oscar Health Insurance](https://hioscar.com/)
|
||||
1. [p3r](https://www.p3r.one/)
|
||||
1. [Packlink](https://www.packlink.com/)
|
||||
1. [Pandosearch](https://www.pandosearch.com/en/home)
|
||||
1. [PagerDuty](https://www.pagerduty.com/)
|
||||
1. [Pandosearch](https://www.pandosearch.com/en/home)
|
||||
1. [Patreon](https://www.patreon.com/)
|
||||
1. [PayPay](https://paypay.ne.jp/)
|
||||
1. [Peloton Interactive](https://www.onepeloton.com/)
|
||||
1. [PGS](https://www.pgs.com)
|
||||
1. [Pigment](https://www.gopigment.com/)
|
||||
1. [Pipefy](https://www.pipefy.com/)
|
||||
1. [Pismo](https://pismo.io/)
|
||||
@@ -197,9 +209,12 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [PostFinance](https://github.com/postfinance)
|
||||
1. [Preferred Networks](https://preferred.jp/en/)
|
||||
1. [Previder BV](https://previder.nl)
|
||||
1. [Procore](https://www.procore.com)
|
||||
1. [Productboard](https://www.productboard.com/)
|
||||
1. [Prudential](https://prudential.com.sg)
|
||||
1. [PT Boer Technology (Btech)](https://btech.id/)
|
||||
1. [PUBG](https://www.pubg.com)
|
||||
1. [Puzzle ITC](https://www.puzzle.ch/)
|
||||
1. [Qonto](https://qonto.com)
|
||||
1. [QuintoAndar](https://quintoandar.com.br)
|
||||
1. [Quipper](https://www.quipper.com/)
|
||||
@@ -219,9 +234,11 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Sap Labs](http://sap.com)
|
||||
1. [Sauce Labs](https://saucelabs.com/)
|
||||
1. [Schwarz IT](https://jobs.schwarz/it-mission)
|
||||
1. [SEEK](https://seek.com.au)
|
||||
1. [SI Analytics](https://si-analytics.ai)
|
||||
1. [Skit](https://skit.ai/)
|
||||
1. [Skyscanner](https://www.skyscanner.net/)
|
||||
1. [Smart Pension](https://www.smartpension.co.uk/)
|
||||
1. [Smilee.io](https://smilee.io)
|
||||
1. [Smood.ch](https://www.smood.ch/)
|
||||
1. [Snapp](https://snapp.ir/)
|
||||
@@ -232,6 +249,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Spendesk](https://spendesk.com/)
|
||||
1. [Splunk](https://splunk.com/)
|
||||
1. [Spores Labs](https://spores.app)
|
||||
1. [StreamNative](https://streamnative.io)
|
||||
1. [Stuart](https://stuart.com/)
|
||||
1. [Sumo Logic](https://sumologic.com/)
|
||||
1. [Sutpc](http://www.sutpc.com/)
|
||||
@@ -245,6 +263,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Techcombank](https://www.techcombank.com.vn/trang-chu)
|
||||
1. [Technacy](https://www.technacy.it/)
|
||||
1. [Tesla](https://tesla.com/)
|
||||
1. [The Scale Factory](https://www.scalefactory.com/)
|
||||
1. [ThousandEyes](https://www.thousandeyes.com/)
|
||||
1. [Ticketmaster](https://ticketmaster.com)
|
||||
1. [Tiger Analytics](https://www.tigeranalytics.com/)
|
||||
@@ -260,7 +279,9 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [ungleich.ch](https://ungleich.ch/)
|
||||
1. [Unifonic Inc](https://www.unifonic.com/)
|
||||
1. [Universidad Mesoamericana](https://www.umes.edu.gt/)
|
||||
1. [Urbantz](https://urbantz.com/)
|
||||
1. [Vectra](https://www.vectra.ai)
|
||||
1. [Veepee](https://www.veepee.com)
|
||||
1. [Viaduct](https://www.viaduct.ai/)
|
||||
1. [Vinted](https://vinted.com/)
|
||||
1. [Virtuo](https://www.govirtuo.com/)
|
||||
@@ -282,5 +303,6 @@ 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. [ZDF](https://www.zdf.de/)
|
||||
1. [Zimpler](https://www.zimpler.com/)
|
||||
1. [ZOZO](https://corp.zozo.com/)
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
@@ -41,7 +42,9 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/generators"
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/utils"
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
"github.com/argoproj/argo-cd/v2/util/db"
|
||||
"github.com/argoproj/argo-cd/v2/util/glob"
|
||||
|
||||
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
|
||||
@@ -68,16 +71,19 @@ var (
|
||||
// ApplicationSetReconciler reconciles a ApplicationSet object
|
||||
type ApplicationSetReconciler struct {
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
Recorder record.EventRecorder
|
||||
Generators map[string]generators.Generator
|
||||
ArgoDB db.ArgoDB
|
||||
ArgoAppClientset appclientset.Interface
|
||||
KubeClientset kubernetes.Interface
|
||||
utils.Policy
|
||||
Scheme *runtime.Scheme
|
||||
Recorder record.EventRecorder
|
||||
Generators map[string]generators.Generator
|
||||
ArgoDB db.ArgoDB
|
||||
ArgoAppClientset appclientset.Interface
|
||||
KubeClientset kubernetes.Interface
|
||||
Policy argov1alpha1.ApplicationsSyncPolicy
|
||||
EnablePolicyOverride bool
|
||||
utils.Renderer
|
||||
|
||||
EnableProgressiveSyncs bool
|
||||
ArgoCDNamespace string
|
||||
ApplicationSetNamespaces []string
|
||||
EnableProgressiveSyncs bool
|
||||
SCMRootCAPath string
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=argoproj.io,resources=applicationsets,verbs=get;list;watch;create;update;patch;delete
|
||||
@@ -120,7 +126,7 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
|
||||
|
||||
parametersGenerated = true
|
||||
|
||||
validateErrors, err := r.validateGeneratedApplications(ctx, desiredApplications, applicationSetInfo, req.Namespace)
|
||||
validateErrors, err := r.validateGeneratedApplications(ctx, desiredApplications, applicationSetInfo)
|
||||
if err != nil {
|
||||
// While some generators may return an error that requires user intervention,
|
||||
// other generators reference external resources that may change to cause
|
||||
@@ -148,19 +154,30 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
|
||||
// appSyncMap tracks which apps will be synced during this reconciliation.
|
||||
appSyncMap := map[string]bool{}
|
||||
|
||||
if r.EnableProgressiveSyncs && applicationSetInfo.Spec.Strategy != nil {
|
||||
applications, err := r.getCurrentApplications(ctx, applicationSetInfo)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("failed to get current applications for application set: %w", err)
|
||||
}
|
||||
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
|
||||
log.Infof("Removing %v unnecessary AppStatus entries from ApplicationSet %v", len(applicationSetInfo.Status.ApplicationStatus), applicationSetInfo.Name)
|
||||
|
||||
for _, app := range applications {
|
||||
appMap[app.Name] = app
|
||||
}
|
||||
err := r.setAppSetApplicationStatus(ctx, &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
|
||||
applications, err := r.getCurrentApplications(ctx, applicationSetInfo)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("failed to get current applications for application set: %w", err)
|
||||
}
|
||||
|
||||
appSyncMap, err = r.performProgressiveSyncs(ctx, applicationSetInfo, applications, desiredApplications, appMap)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("failed to perform progressive sync reconciliation for application set: %w", err)
|
||||
for _, app := range applications {
|
||||
appMap[app.Name] = app
|
||||
}
|
||||
|
||||
appSyncMap, err = r.performProgressiveSyncs(ctx, applicationSetInfo, applications, desiredApplications, appMap)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("failed to perform progressive sync reconciliation for application set: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +229,7 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
|
||||
}
|
||||
}
|
||||
|
||||
if r.Policy.Update() {
|
||||
if utils.DefaultPolicy(applicationSetInfo.Spec.SyncPolicy, r.Policy, r.EnablePolicyOverride).AllowUpdate() {
|
||||
err = r.createOrUpdateInCluster(ctx, applicationSetInfo, validApps)
|
||||
if err != nil {
|
||||
_ = r.setApplicationSetStatusCondition(ctx,
|
||||
@@ -242,7 +259,7 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
|
||||
}
|
||||
}
|
||||
|
||||
if r.Policy.Delete() {
|
||||
if utils.DefaultPolicy(applicationSetInfo.Spec.SyncPolicy, r.Policy, r.EnablePolicyOverride).AllowDelete() {
|
||||
err = r.deleteInCluster(ctx, applicationSetInfo, desiredApplications)
|
||||
if err != nil {
|
||||
_ = r.setApplicationSetStatusCondition(ctx,
|
||||
@@ -277,7 +294,6 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
|
||||
}
|
||||
|
||||
requeueAfter := r.getMinRequeueAfter(&applicationSetInfo)
|
||||
logCtx.WithField("requeueAfter", requeueAfter).Info("end reconcile")
|
||||
|
||||
if len(validateErrors) == 0 {
|
||||
if err := r.setApplicationSetStatusCondition(ctx,
|
||||
@@ -291,8 +307,13 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
|
||||
); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
} else if requeueAfter == time.Duration(0) {
|
||||
// Ensure that the request is requeued if there are validation errors.
|
||||
requeueAfter = ReconcileRequeueOnValidationError
|
||||
}
|
||||
|
||||
logCtx.WithField("requeueAfter", requeueAfter).Info("end reconcile")
|
||||
|
||||
return ctrl.Result{
|
||||
RequeueAfter: requeueAfter,
|
||||
}, nil
|
||||
@@ -402,7 +423,7 @@ func (r *ApplicationSetReconciler) setApplicationSetStatusCondition(ctx context.
|
||||
|
||||
// validateGeneratedApplications uses the Argo CD validation functions to verify the correctness of the
|
||||
// generated applications.
|
||||
func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Context, desiredApplications []argov1alpha1.Application, applicationSetInfo argov1alpha1.ApplicationSet, namespace string) (map[int]error, error) {
|
||||
func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Context, desiredApplications []argov1alpha1.Application, applicationSetInfo argov1alpha1.ApplicationSet) (map[int]error, error) {
|
||||
errorsByIndex := map[int]error{}
|
||||
namesSet := map[string]bool{}
|
||||
for i, app := range desiredApplications {
|
||||
@@ -414,7 +435,7 @@ func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Con
|
||||
continue
|
||||
}
|
||||
|
||||
proj, err := r.ArgoAppClientset.ArgoprojV1alpha1().AppProjects(namespace).Get(ctx, app.Spec.GetProject(), metav1.GetOptions{})
|
||||
proj, err := r.ArgoAppClientset.ArgoprojV1alpha1().AppProjects(r.ArgoCDNamespace).Get(ctx, app.Spec.GetProject(), metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if apierr.IsNotFound(err) {
|
||||
errorsByIndex[i] = fmt.Errorf("application references project %s which does not exist", app.Spec.Project)
|
||||
@@ -423,7 +444,7 @@ func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Con
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := utils.ValidateDestination(ctx, &app.Spec.Destination, r.KubeClientset, namespace); err != nil {
|
||||
if err := utils.ValidateDestination(ctx, &app.Spec.Destination, r.KubeClientset, r.ArgoCDNamespace); err != nil {
|
||||
errorsByIndex[i] = fmt.Errorf("application destination spec is invalid: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
@@ -496,7 +517,7 @@ func (r *ApplicationSetReconciler) generateApplications(applicationSetInfo argov
|
||||
tmplApplication := getTempApplication(a.Template)
|
||||
|
||||
for _, p := range a.Params {
|
||||
app, err := r.Renderer.RenderTemplateParams(tmplApplication, applicationSetInfo.Spec.SyncPolicy, p, applicationSetInfo.Spec.GoTemplate)
|
||||
app, err := r.Renderer.RenderTemplateParams(tmplApplication, applicationSetInfo.Spec.SyncPolicy, p, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("params", a.Params).WithField("generator", requestedGenerator).
|
||||
Error("error generating application from params")
|
||||
@@ -518,7 +539,15 @@ func (r *ApplicationSetReconciler) generateApplications(applicationSetInfo argov
|
||||
return res, applicationSetReason, firstError
|
||||
}
|
||||
|
||||
func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager, enableProgressiveSyncs bool) error {
|
||||
func ignoreNotAllowedNamespaces(namespaces []string) predicate.Predicate {
|
||||
return predicate.Funcs{
|
||||
CreateFunc: func(e event.CreateEvent) bool {
|
||||
return glob.MatchStringInList(namespaces, e.Object.GetNamespace(), false)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager, enableProgressiveSyncs bool, maxConcurrentReconciliations int) error {
|
||||
if err := mgr.GetFieldIndexer().IndexField(context.TODO(), &argov1alpha1.Application{}, ".metadata.controller", func(rawObj client.Object) []string {
|
||||
// grab the job object, extract the owner...
|
||||
app := rawObj.(*argov1alpha1.Application)
|
||||
@@ -539,9 +568,11 @@ func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager, enableProg
|
||||
|
||||
ownsHandler := getOwnsHandlerPredicates(enableProgressiveSyncs)
|
||||
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&argov1alpha1.ApplicationSet{}).
|
||||
return ctrl.NewControllerManagedBy(mgr).WithOptions(controller.Options{
|
||||
MaxConcurrentReconciles: maxConcurrentReconciliations,
|
||||
}).For(&argov1alpha1.ApplicationSet{}).
|
||||
Owns(&argov1alpha1.Application{}, builder.WithPredicates(ownsHandler)).
|
||||
WithEventFilter(ignoreNotAllowedNamespaces(r.ApplicationSetNamespaces)).
|
||||
Watches(
|
||||
&source.Kind{Type: &corev1.Secret{}},
|
||||
&clusterSecretEventHandler{
|
||||
@@ -565,6 +596,9 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
|
||||
appLog := log.WithFields(log.Fields{"app": generatedApp.Name, "appSet": applicationSet.Name})
|
||||
generatedApp.Namespace = applicationSet.Namespace
|
||||
|
||||
// Normalize to avoid fighting with the application controller.
|
||||
generatedApp.Spec = *argoutil.NormalizeApplicationSpec(&generatedApp.Spec)
|
||||
|
||||
found := &argov1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: generatedApp.Name,
|
||||
@@ -576,7 +610,7 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
|
||||
},
|
||||
}
|
||||
|
||||
action, err := utils.CreateOrUpdate(ctx, r.Client, found, func() error {
|
||||
action, err := utils.CreateOrUpdate(ctx, r.Client, found, normalizers.IgnoreNormalizerOpts{}, func() error {
|
||||
// Copy only the Application/ObjectMeta fields that are significant, from the generatedApp
|
||||
found.Spec = generatedApp.Spec
|
||||
|
||||
@@ -669,7 +703,7 @@ func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, applicat
|
||||
// settingsMgr := settings.NewSettingsManager(context.TODO(), r.KubeClientset, applicationSet.Namespace)
|
||||
// argoDB := db.NewDB(applicationSet.Namespace, settingsMgr, r.KubeClientset)
|
||||
// clusterList, err := argoDB.ListClusters(ctx)
|
||||
clusterList, err := utils.ListClusters(ctx, r.KubeClientset, applicationSet.Namespace)
|
||||
clusterList, err := utils.ListClusters(ctx, r.KubeClientset, r.ArgoCDNamespace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing clusters: %w", err)
|
||||
}
|
||||
@@ -730,7 +764,7 @@ func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx conte
|
||||
var validDestination bool
|
||||
|
||||
// Detect if the destination is invalid (name doesn't correspond to a matching cluster)
|
||||
if err := utils.ValidateDestination(ctx, &app.Spec.Destination, r.KubeClientset, applicationSet.Namespace); err != nil {
|
||||
if err := utils.ValidateDestination(ctx, &app.Spec.Destination, r.KubeClientset, r.ArgoCDNamespace); err != nil {
|
||||
appLog.Warnf("The destination cluster for %s couldn't be found: %v", app.Name, err)
|
||||
validDestination = false
|
||||
} else {
|
||||
@@ -851,45 +885,21 @@ func (r *ApplicationSetReconciler) buildAppDependencyList(ctx context.Context, a
|
||||
|
||||
selected := true // default to true, assuming the current Application is a match for the given step matchExpression
|
||||
|
||||
allNotInMatched := true // needed to support correct AND behavior between multiple NotIn MatchExpressions
|
||||
notInUsed := false // since we default to allNotInMatched == true, track whether a NotIn expression was actually used
|
||||
|
||||
for _, matchExpression := range step.MatchExpressions {
|
||||
|
||||
if matchExpression.Operator == "In" {
|
||||
if val, ok := app.Labels[matchExpression.Key]; ok {
|
||||
valueMatched := labelMatchedExpression(val, matchExpression)
|
||||
if val, ok := app.Labels[matchExpression.Key]; ok {
|
||||
valueMatched := labelMatchedExpression(val, matchExpression)
|
||||
|
||||
if !valueMatched { // none of the matchExpression values was a match with the Application'ss labels
|
||||
selected = false
|
||||
break
|
||||
}
|
||||
} else {
|
||||
selected = false // no matching label key with In means this Application will not be included in the current step
|
||||
if !valueMatched { // none of the matchExpression values was a match with the Application'ss labels
|
||||
selected = false
|
||||
break
|
||||
}
|
||||
} else if matchExpression.Operator == "NotIn" {
|
||||
notInUsed = true // a NotIn selector was used in this matchExpression
|
||||
if val, ok := app.Labels[matchExpression.Key]; ok {
|
||||
valueMatched := labelMatchedExpression(val, matchExpression)
|
||||
|
||||
if !valueMatched { // none of the matchExpression values was a match with the Application's labels
|
||||
allNotInMatched = false
|
||||
}
|
||||
} else {
|
||||
allNotInMatched = false // no matching label key with NotIn means this Application may still be included in the current step
|
||||
}
|
||||
} else { // handle invalid operator selection
|
||||
log.Warnf("skipping AppSet rollingUpdate step Application selection for %q, invalid matchExpression operator provided: %q ", applicationSet.Name, matchExpression.Operator)
|
||||
selected = false
|
||||
} else if matchExpression.Operator == "In" {
|
||||
selected = false // no matching label key with "In" operator means this Application will not be included in the current step
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if notInUsed && allNotInMatched { // check if all NotIn Expressions matched, if so exclude this Application
|
||||
selected = false
|
||||
}
|
||||
|
||||
if selected {
|
||||
appDependencyList[i] = append(appDependencyList[i], app.Name)
|
||||
if val, ok := appStepMap[app.Name]; ok {
|
||||
@@ -905,11 +915,20 @@ func (r *ApplicationSetReconciler) buildAppDependencyList(ctx context.Context, a
|
||||
}
|
||||
|
||||
func labelMatchedExpression(val string, matchExpression argov1alpha1.ApplicationMatchExpression) bool {
|
||||
valueMatched := false
|
||||
if matchExpression.Operator != "In" && matchExpression.Operator != "NotIn" {
|
||||
log.Errorf("skipping AppSet rollingUpdate step Application selection, invalid matchExpression operator provided: %q ", matchExpression.Operator)
|
||||
return false
|
||||
}
|
||||
|
||||
// if operator == In, default to false
|
||||
// if operator == NotIn, default to true
|
||||
valueMatched := matchExpression.Operator == "NotIn"
|
||||
|
||||
for _, value := range matchExpression.Values {
|
||||
if val == value {
|
||||
valueMatched = true
|
||||
break
|
||||
// first "In" match returns true
|
||||
// first "NotIn" match returns false
|
||||
return matchExpression.Operator == "In"
|
||||
}
|
||||
}
|
||||
return valueMatched
|
||||
@@ -1042,7 +1061,12 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
|
||||
}
|
||||
|
||||
if currentAppStatus.Status == "Pending" {
|
||||
if operationPhaseString == "Succeeded" && app.Status.OperationState.StartedAt.After(currentAppStatus.LastTransitionTime.Time) {
|
||||
// check for successful syncs started less than 10s before the Application transitioned to Pending
|
||||
// this covers race conditions where syncs initiated by RollingSync miraculously have a sync time before the transition to Pending state occurred (could be a few seconds)
|
||||
if operationPhaseString == "Succeeded" && app.Status.OperationState.StartedAt.Add(time.Duration(10)*time.Second).After(currentAppStatus.LastTransitionTime.Time) {
|
||||
if !app.Status.OperationState.StartedAt.After(currentAppStatus.LastTransitionTime.Time) {
|
||||
log.Warnf("Application %v was synced less than 10s prior to entering Pending status, we'll assume the AppSet controller triggered this sync and update its status to Progressing", app.Name)
|
||||
}
|
||||
log.Infof("Application %v has completed a sync successfully, updating its ApplicationSet status to Progressing", app.Name)
|
||||
currentAppStatus.LastTransitionTime = &now
|
||||
currentAppStatus.Status = "Progressing"
|
||||
@@ -1220,30 +1244,30 @@ func findApplicationStatusIndex(appStatuses []argov1alpha1.ApplicationSetApplica
|
||||
// with any new/changed Application statuses.
|
||||
func (r *ApplicationSetReconciler) setAppSetApplicationStatus(ctx context.Context, applicationSet *argov1alpha1.ApplicationSet, applicationStatuses []argov1alpha1.ApplicationSetApplicationStatus) error {
|
||||
needToUpdateStatus := false
|
||||
for i := range applicationStatuses {
|
||||
appStatus := applicationStatuses[i]
|
||||
idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, appStatus.Application)
|
||||
if idx == -1 {
|
||||
needToUpdateStatus = true
|
||||
break
|
||||
}
|
||||
currentStatus := applicationSet.Status.ApplicationStatus[idx]
|
||||
if currentStatus.Message != appStatus.Message || currentStatus.Status != appStatus.Status {
|
||||
needToUpdateStatus = true
|
||||
break
|
||||
|
||||
if len(applicationStatuses) != len(applicationSet.Status.ApplicationStatus) {
|
||||
needToUpdateStatus = true
|
||||
} else {
|
||||
for i := range applicationStatuses {
|
||||
appStatus := applicationStatuses[i]
|
||||
idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, appStatus.Application)
|
||||
if idx == -1 {
|
||||
needToUpdateStatus = true
|
||||
break
|
||||
}
|
||||
currentStatus := applicationSet.Status.ApplicationStatus[idx]
|
||||
if currentStatus.Message != appStatus.Message || currentStatus.Status != appStatus.Status || currentStatus.Step != appStatus.Step {
|
||||
needToUpdateStatus = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if needToUpdateStatus {
|
||||
// fetch updated Application Set object before updating it
|
||||
namespacedName := types.NamespacedName{Namespace: applicationSet.Namespace, Name: applicationSet.Name}
|
||||
if err := r.Get(ctx, namespacedName, applicationSet); err != nil {
|
||||
if client.IgnoreNotFound(err) != nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("error fetching updated application set: %v", err)
|
||||
}
|
||||
|
||||
// rebuild ApplicationStatus from scratch, we don't need any previous status history
|
||||
applicationSet.Status.ApplicationStatus = []argov1alpha1.ApplicationSetApplicationStatus{}
|
||||
for i := range applicationStatuses {
|
||||
applicationSet.Status.SetApplicationStatus(applicationStatuses[i])
|
||||
}
|
||||
@@ -1349,7 +1373,7 @@ func getOwnsHandlerPredicates(enableProgressiveSyncs bool) predicate.Funcs {
|
||||
return false
|
||||
}
|
||||
requeue := shouldRequeueApplicationSet(appOld, appNew, enableProgressiveSyncs)
|
||||
log.Debugf("requeue: %t caused by application %s", requeue, appNew.Name)
|
||||
log.Debugf("requeue: %t caused by application %s\n", requeue, appNew.Name)
|
||||
return requeue
|
||||
},
|
||||
GenericFunc: func(e event.GenericEvent) bool {
|
||||
|
||||
@@ -26,11 +26,12 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/generators"
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/utils"
|
||||
"github.com/argoproj/gitops-engine/pkg/health"
|
||||
"github.com/argoproj/gitops-engine/pkg/sync/common"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/generators"
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/utils"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned/fake"
|
||||
"github.com/argoproj/argo-cd/v2/util/collections"
|
||||
@@ -65,8 +66,8 @@ func (g *generatorMock) GetRequeueAfter(appSetGenerator *v1alpha1.ApplicationSet
|
||||
return args.Get(0).(time.Duration)
|
||||
}
|
||||
|
||||
func (r *rendererMock) RenderTemplateParams(tmpl *v1alpha1.Application, syncPolicy *v1alpha1.ApplicationSetSyncPolicy, params map[string]interface{}, useGoTemplate bool) (*v1alpha1.Application, error) {
|
||||
args := r.Called(tmpl, params, useGoTemplate)
|
||||
func (r *rendererMock) RenderTemplateParams(tmpl *v1alpha1.Application, syncPolicy *v1alpha1.ApplicationSetSyncPolicy, params map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (*v1alpha1.Application, error) {
|
||||
args := r.Called(tmpl, params, useGoTemplate, goTemplateOptions)
|
||||
|
||||
if args.Error(1) != nil {
|
||||
return nil, args.Error(1)
|
||||
@@ -165,10 +166,10 @@ func TestExtractApplications(t *testing.T) {
|
||||
for _, p := range cc.params {
|
||||
|
||||
if cc.rendererError != nil {
|
||||
rendererMock.On("RenderTemplateParams", getTempApplication(cc.template), p, false).
|
||||
rendererMock.On("RenderTemplateParams", getTempApplication(cc.template), p, false, []string(nil)).
|
||||
Return(nil, cc.rendererError)
|
||||
} else {
|
||||
rendererMock.On("RenderTemplateParams", getTempApplication(cc.template), p, false).
|
||||
rendererMock.On("RenderTemplateParams", getTempApplication(cc.template), p, false, []string(nil)).
|
||||
Return(&app, nil)
|
||||
expectedApps = append(expectedApps, app)
|
||||
}
|
||||
@@ -285,7 +286,7 @@ func TestMergeTemplateApplications(t *testing.T) {
|
||||
|
||||
rendererMock := rendererMock{}
|
||||
|
||||
rendererMock.On("RenderTemplateParams", getTempApplication(cc.expectedMerged), cc.params[0], false).
|
||||
rendererMock.On("RenderTemplateParams", getTempApplication(cc.expectedMerged), cc.params[0], false, []string(nil)).
|
||||
Return(&cc.expectedApps[0], nil)
|
||||
|
||||
r := ApplicationSetReconciler{
|
||||
@@ -365,6 +366,7 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
|
||||
Namespace: "namespace",
|
||||
ResourceVersion: "1",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{Project: "default"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -892,6 +894,60 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "Ensure that the app spec is normalized before applying",
|
||||
appSet: v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
Namespace: "namespace",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Template: v1alpha1.ApplicationSetTemplate{
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Project: "project",
|
||||
Source: &v1alpha1.ApplicationSource{
|
||||
Directory: &v1alpha1.ApplicationSourceDirectory{
|
||||
Jsonnet: v1alpha1.ApplicationSourceJsonnet{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
desiredApps: []v1alpha1.Application{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app1",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Project: "project",
|
||||
Source: &v1alpha1.ApplicationSource{
|
||||
Directory: &v1alpha1.ApplicationSourceDirectory{
|
||||
Jsonnet: v1alpha1.ApplicationSourceJsonnet{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []v1alpha1.Application{
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Application",
|
||||
APIVersion: "argoproj.io/v1alpha1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app1",
|
||||
Namespace: "namespace",
|
||||
ResourceVersion: "1",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Project: "project",
|
||||
Source: &v1alpha1.ApplicationSource{
|
||||
// Directory and jsonnet block are removed
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
|
||||
@@ -1223,13 +1279,15 @@ func TestCreateApplications(t *testing.T) {
|
||||
err = v1alpha1.AddToScheme(scheme)
|
||||
assert.Nil(t, err)
|
||||
|
||||
for _, c := range []struct {
|
||||
testCases := []struct {
|
||||
name string
|
||||
appSet v1alpha1.ApplicationSet
|
||||
existsApps []v1alpha1.Application
|
||||
apps []v1alpha1.Application
|
||||
expected []v1alpha1.Application
|
||||
}{
|
||||
{
|
||||
name: "no existing apps",
|
||||
appSet: v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
@@ -1255,10 +1313,14 @@ func TestCreateApplications(t *testing.T) {
|
||||
Namespace: "namespace",
|
||||
ResourceVersion: "1",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Project: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing apps",
|
||||
appSet: v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
@@ -1316,6 +1378,7 @@ func TestCreateApplications(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing apps with different project",
|
||||
appSet: v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
@@ -1372,39 +1435,42 @@ func TestCreateApplications(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
initObjs := []crtclient.Object{&c.appSet}
|
||||
for _, a := range c.existsApps {
|
||||
err = controllerutil.SetControllerReference(&c.appSet, &a, scheme)
|
||||
assert.Nil(t, err)
|
||||
initObjs = append(initObjs, &a)
|
||||
}
|
||||
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).Build()
|
||||
|
||||
r := ApplicationSetReconciler{
|
||||
Client: client,
|
||||
Scheme: scheme,
|
||||
Recorder: record.NewFakeRecorder(len(initObjs) + len(c.expected)),
|
||||
}
|
||||
|
||||
err = r.createInCluster(context.TODO(), c.appSet, c.apps)
|
||||
assert.Nil(t, err)
|
||||
|
||||
for _, obj := range c.expected {
|
||||
got := &v1alpha1.Application{}
|
||||
_ = client.Get(context.Background(), crtclient.ObjectKey{
|
||||
Namespace: obj.Namespace,
|
||||
Name: obj.Name,
|
||||
}, got)
|
||||
|
||||
err = controllerutil.SetControllerReference(&c.appSet, &obj, r.Scheme)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, obj, *got)
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range testCases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
initObjs := []crtclient.Object{&c.appSet}
|
||||
for _, a := range c.existsApps {
|
||||
err = controllerutil.SetControllerReference(&c.appSet, &a, scheme)
|
||||
assert.Nil(t, err)
|
||||
initObjs = append(initObjs, &a)
|
||||
}
|
||||
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).Build()
|
||||
|
||||
r := ApplicationSetReconciler{
|
||||
Client: client,
|
||||
Scheme: scheme,
|
||||
Recorder: record.NewFakeRecorder(len(initObjs) + len(c.expected)),
|
||||
}
|
||||
|
||||
err = r.createInCluster(context.TODO(), c.appSet, c.apps)
|
||||
assert.Nil(t, err)
|
||||
|
||||
for _, obj := range c.expected {
|
||||
got := &v1alpha1.Application{}
|
||||
_ = client.Get(context.Background(), crtclient.ObjectKey{
|
||||
Namespace: obj.Namespace,
|
||||
Name: obj.Name,
|
||||
}, got)
|
||||
|
||||
err = controllerutil.SetControllerReference(&c.appSet, &obj, r.Scheme)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, obj, *got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteInCluster(t *testing.T) {
|
||||
@@ -1808,13 +1874,14 @@ func TestValidateGeneratedApplications(t *testing.T) {
|
||||
Recorder: record.NewFakeRecorder(1),
|
||||
Generators: map[string]generators.Generator{},
|
||||
ArgoDB: &argoDBMock,
|
||||
ArgoCDNamespace: "namespace",
|
||||
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
|
||||
KubeClientset: kubeclientset,
|
||||
}
|
||||
|
||||
appSetInfo := v1alpha1.ApplicationSet{}
|
||||
|
||||
validationErrors, _ := r.validateGeneratedApplications(context.TODO(), cc.apps, appSetInfo, "namespace")
|
||||
validationErrors, _ := r.validateGeneratedApplications(context.TODO(), cc.apps, appSetInfo)
|
||||
var errorMessages []string
|
||||
for _, v := range validationErrors {
|
||||
errorMessages = append(errorMessages, v.Error())
|
||||
@@ -1915,7 +1982,8 @@ func TestReconcilerValidationErrorBehaviour(t *testing.T) {
|
||||
ArgoDB: &argoDBMock,
|
||||
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
|
||||
KubeClientset: kubeclientset,
|
||||
Policy: &utils.SyncPolicy{},
|
||||
Policy: v1alpha1.ApplicationsSyncPolicySync,
|
||||
ArgoCDNamespace: "argocd",
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
@@ -1928,7 +1996,7 @@ func TestReconcilerValidationErrorBehaviour(t *testing.T) {
|
||||
// Verify that on validation error, no error is returned, but the object is requeued
|
||||
res, err := r.Reconcile(context.Background(), req)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, res.RequeueAfter == 0)
|
||||
assert.True(t, res.RequeueAfter == ReconcileRequeueOnValidationError)
|
||||
|
||||
var app v1alpha1.Application
|
||||
|
||||
@@ -1998,6 +2066,337 @@ func TestSetApplicationSetStatusCondition(t *testing.T) {
|
||||
assert.Len(t, appSet.Status.Conditions, 3)
|
||||
}
|
||||
|
||||
func applicationsUpdateSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alpha1.ApplicationsSyncPolicy, recordBuffer int, allowPolicyOverride bool) v1alpha1.Application {
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
err := v1alpha1.AddToScheme(scheme)
|
||||
assert.Nil(t, err)
|
||||
err = v1alpha1.AddToScheme(scheme)
|
||||
assert.Nil(t, err)
|
||||
|
||||
defaultProject := v1alpha1.AppProject{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "argocd"},
|
||||
Spec: v1alpha1.AppProjectSpec{SourceRepos: []string{"*"}, Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "https://good-cluster"}}},
|
||||
}
|
||||
appSet := v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Generators: []v1alpha1.ApplicationSetGenerator{
|
||||
{
|
||||
List: &v1alpha1.ListGenerator{
|
||||
Elements: []apiextensionsv1.JSON{{
|
||||
Raw: []byte(`{"cluster": "good-cluster","url": "https://good-cluster"}`),
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
SyncPolicy: &v1alpha1.ApplicationSetSyncPolicy{
|
||||
ApplicationsSync: &applicationsSyncPolicy,
|
||||
},
|
||||
Template: v1alpha1.ApplicationSetTemplate{
|
||||
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
|
||||
Name: "{{cluster}}",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Source: &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"},
|
||||
Project: "default",
|
||||
Destination: v1alpha1.ApplicationDestination{Server: "{{url}}"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
kubeclientset := kubefake.NewSimpleClientset()
|
||||
argoDBMock := dbmocks.ArgoDB{}
|
||||
argoObjs := []runtime.Object{&defaultProject}
|
||||
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).Build()
|
||||
goodCluster := v1alpha1.Cluster{Server: "https://good-cluster", Name: "good-cluster"}
|
||||
argoDBMock.On("GetCluster", mock.Anything, "https://good-cluster").Return(&goodCluster, nil)
|
||||
argoDBMock.On("ListClusters", mock.Anything).Return(&v1alpha1.ClusterList{Items: []v1alpha1.Cluster{
|
||||
goodCluster,
|
||||
}}, nil)
|
||||
|
||||
r := ApplicationSetReconciler{
|
||||
Client: client,
|
||||
Scheme: scheme,
|
||||
Renderer: &utils.Render{},
|
||||
Recorder: record.NewFakeRecorder(recordBuffer),
|
||||
Generators: map[string]generators.Generator{
|
||||
"List": generators.NewListGenerator(),
|
||||
},
|
||||
ArgoDB: &argoDBMock,
|
||||
ArgoCDNamespace: "argocd",
|
||||
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
|
||||
KubeClientset: kubeclientset,
|
||||
Policy: v1alpha1.ApplicationsSyncPolicySync,
|
||||
EnablePolicyOverride: allowPolicyOverride,
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "argocd",
|
||||
Name: "name",
|
||||
},
|
||||
}
|
||||
|
||||
// Verify that on validation error, no error is returned, but the object is requeued
|
||||
resCreate, err := r.Reconcile(context.Background(), req)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, resCreate.RequeueAfter == 0)
|
||||
|
||||
var app v1alpha1.Application
|
||||
|
||||
// make sure good app got created
|
||||
err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-cluster"}, &app)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, app.Name, "good-cluster")
|
||||
|
||||
// Update resource
|
||||
var retrievedApplicationSet v1alpha1.ApplicationSet
|
||||
err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "name"}, &retrievedApplicationSet)
|
||||
assert.Nil(t, err)
|
||||
|
||||
retrievedApplicationSet.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"}
|
||||
retrievedApplicationSet.Spec.Template.Labels = map[string]string{"label-key": "label-value"}
|
||||
|
||||
retrievedApplicationSet.Spec.Template.Spec.Source.Helm = &v1alpha1.ApplicationSourceHelm{
|
||||
Values: "global.test: test",
|
||||
}
|
||||
|
||||
err = r.Client.Update(context.TODO(), &retrievedApplicationSet)
|
||||
assert.Nil(t, err)
|
||||
|
||||
resUpdate, err := r.Reconcile(context.Background(), req)
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-cluster"}, &app)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, resUpdate.RequeueAfter == 0)
|
||||
assert.Equal(t, app.Name, "good-cluster")
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func TestUpdateNotPerformedWithSyncPolicyCreateOnly(t *testing.T) {
|
||||
|
||||
applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly
|
||||
|
||||
app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 1, true)
|
||||
|
||||
assert.Nil(t, app.Spec.Source.Helm)
|
||||
assert.Nil(t, app.ObjectMeta.Annotations)
|
||||
}
|
||||
|
||||
func TestUpdateNotPerformedWithSyncPolicyCreateDelete(t *testing.T) {
|
||||
|
||||
applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateDelete
|
||||
|
||||
app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 1, true)
|
||||
|
||||
assert.Nil(t, app.Spec.Source.Helm)
|
||||
assert.Nil(t, app.ObjectMeta.Annotations)
|
||||
}
|
||||
|
||||
func TestUpdatePerformedWithSyncPolicyCreateUpdate(t *testing.T) {
|
||||
|
||||
applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateUpdate
|
||||
|
||||
app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 2, true)
|
||||
|
||||
assert.Equal(t, "global.test: test", app.Spec.Source.Helm.Values)
|
||||
assert.Equal(t, map[string]string{"annotation-key": "annotation-value"}, app.ObjectMeta.Annotations)
|
||||
assert.Equal(t, map[string]string{"label-key": "label-value"}, app.ObjectMeta.Labels)
|
||||
}
|
||||
|
||||
func TestUpdatePerformedWithSyncPolicySync(t *testing.T) {
|
||||
|
||||
applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicySync
|
||||
|
||||
app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 2, true)
|
||||
|
||||
assert.Equal(t, "global.test: test", app.Spec.Source.Helm.Values)
|
||||
assert.Equal(t, map[string]string{"annotation-key": "annotation-value"}, app.ObjectMeta.Annotations)
|
||||
assert.Equal(t, map[string]string{"label-key": "label-value"}, app.ObjectMeta.Labels)
|
||||
}
|
||||
|
||||
func TestUpdatePerformedWithSyncPolicyCreateOnlyAndAllowPolicyOverrideFalse(t *testing.T) {
|
||||
|
||||
applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly
|
||||
|
||||
app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 2, false)
|
||||
|
||||
assert.Equal(t, "global.test: test", app.Spec.Source.Helm.Values)
|
||||
assert.Equal(t, map[string]string{"annotation-key": "annotation-value"}, app.ObjectMeta.Annotations)
|
||||
assert.Equal(t, map[string]string{"label-key": "label-value"}, app.ObjectMeta.Labels)
|
||||
}
|
||||
|
||||
func applicationsDeleteSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alpha1.ApplicationsSyncPolicy, recordBuffer int, allowPolicyOverride bool) v1alpha1.ApplicationList {
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
err := v1alpha1.AddToScheme(scheme)
|
||||
assert.Nil(t, err)
|
||||
err = v1alpha1.AddToScheme(scheme)
|
||||
assert.Nil(t, err)
|
||||
|
||||
defaultProject := v1alpha1.AppProject{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "argocd"},
|
||||
Spec: v1alpha1.AppProjectSpec{SourceRepos: []string{"*"}, Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "https://good-cluster"}}},
|
||||
}
|
||||
appSet := v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Generators: []v1alpha1.ApplicationSetGenerator{
|
||||
{
|
||||
List: &v1alpha1.ListGenerator{
|
||||
Elements: []apiextensionsv1.JSON{{
|
||||
Raw: []byte(`{"cluster": "good-cluster","url": "https://good-cluster"}`),
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
SyncPolicy: &v1alpha1.ApplicationSetSyncPolicy{
|
||||
ApplicationsSync: &applicationsSyncPolicy,
|
||||
},
|
||||
Template: v1alpha1.ApplicationSetTemplate{
|
||||
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
|
||||
Name: "{{cluster}}",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Source: &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"},
|
||||
Project: "default",
|
||||
Destination: v1alpha1.ApplicationDestination{Server: "{{url}}"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
kubeclientset := kubefake.NewSimpleClientset()
|
||||
argoDBMock := dbmocks.ArgoDB{}
|
||||
argoObjs := []runtime.Object{&defaultProject}
|
||||
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).Build()
|
||||
goodCluster := v1alpha1.Cluster{Server: "https://good-cluster", Name: "good-cluster"}
|
||||
argoDBMock.On("GetCluster", mock.Anything, "https://good-cluster").Return(&goodCluster, nil)
|
||||
argoDBMock.On("ListClusters", mock.Anything).Return(&v1alpha1.ClusterList{Items: []v1alpha1.Cluster{
|
||||
goodCluster,
|
||||
}}, nil)
|
||||
|
||||
r := ApplicationSetReconciler{
|
||||
Client: client,
|
||||
Scheme: scheme,
|
||||
Renderer: &utils.Render{},
|
||||
Recorder: record.NewFakeRecorder(recordBuffer),
|
||||
Generators: map[string]generators.Generator{
|
||||
"List": generators.NewListGenerator(),
|
||||
},
|
||||
ArgoDB: &argoDBMock,
|
||||
ArgoCDNamespace: "argocd",
|
||||
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
|
||||
KubeClientset: kubeclientset,
|
||||
Policy: v1alpha1.ApplicationsSyncPolicySync,
|
||||
EnablePolicyOverride: allowPolicyOverride,
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "argocd",
|
||||
Name: "name",
|
||||
},
|
||||
}
|
||||
|
||||
// Verify that on validation error, no error is returned, but the object is requeued
|
||||
resCreate, err := r.Reconcile(context.Background(), req)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, resCreate.RequeueAfter == 0)
|
||||
|
||||
var app v1alpha1.Application
|
||||
|
||||
// make sure good app got created
|
||||
err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-cluster"}, &app)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, app.Name, "good-cluster")
|
||||
|
||||
// Update resource
|
||||
var retrievedApplicationSet v1alpha1.ApplicationSet
|
||||
err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "name"}, &retrievedApplicationSet)
|
||||
assert.Nil(t, err)
|
||||
retrievedApplicationSet.Spec.Generators = []v1alpha1.ApplicationSetGenerator{
|
||||
{
|
||||
List: &v1alpha1.ListGenerator{
|
||||
Elements: []apiextensionsv1.JSON{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err = r.Client.Update(context.TODO(), &retrievedApplicationSet)
|
||||
assert.Nil(t, err)
|
||||
|
||||
resUpdate, err := r.Reconcile(context.Background(), req)
|
||||
assert.Nil(t, err)
|
||||
|
||||
var apps v1alpha1.ApplicationList
|
||||
|
||||
err = r.Client.List(context.TODO(), &apps)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, resUpdate.RequeueAfter == 0)
|
||||
|
||||
return apps
|
||||
}
|
||||
|
||||
func TestDeleteNotPerformedWithSyncPolicyCreateOnly(t *testing.T) {
|
||||
|
||||
applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly
|
||||
|
||||
apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 1, true)
|
||||
|
||||
assert.Equal(t, "good-cluster", apps.Items[0].Name)
|
||||
}
|
||||
|
||||
func TestDeleteNotPerformedWithSyncPolicyCreateUpdate(t *testing.T) {
|
||||
|
||||
applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateUpdate
|
||||
|
||||
apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 2, true)
|
||||
|
||||
assert.Equal(t, "good-cluster", apps.Items[0].Name)
|
||||
}
|
||||
|
||||
func TestDeletePerformedWithSyncPolicyCreateDelete(t *testing.T) {
|
||||
|
||||
applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateDelete
|
||||
|
||||
apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, true)
|
||||
|
||||
assert.Equal(t, 0, len(apps.Items))
|
||||
}
|
||||
|
||||
func TestDeletePerformedWithSyncPolicySync(t *testing.T) {
|
||||
|
||||
applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicySync
|
||||
|
||||
apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, true)
|
||||
|
||||
assert.Equal(t, 0, len(apps.Items))
|
||||
}
|
||||
|
||||
func TestDeletePerformedWithSyncPolicyCreateOnlyAndAllowPolicyOverrideFalse(t *testing.T) {
|
||||
|
||||
applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly
|
||||
|
||||
apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, false)
|
||||
|
||||
assert.Equal(t, 0, len(apps.Items))
|
||||
}
|
||||
|
||||
// Test app generation from a go template application set using a pull request generator
|
||||
func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
@@ -2204,6 +2603,7 @@ func TestPolicies(t *testing.T) {
|
||||
"List": generators.NewListGenerator(),
|
||||
},
|
||||
ArgoDB: &argoDBMock,
|
||||
ArgoCDNamespace: "argocd",
|
||||
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
|
||||
KubeClientset: kubeclientset,
|
||||
Policy: policy,
|
||||
@@ -2277,55 +2677,104 @@ func TestSetApplicationSetApplicationStatus(t *testing.T) {
|
||||
err = v1alpha1.AddToScheme(scheme)
|
||||
assert.Nil(t, err)
|
||||
|
||||
appSet := v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Generators: []v1alpha1.ApplicationSetGenerator{
|
||||
{List: &v1alpha1.ListGenerator{
|
||||
Elements: []apiextensionsv1.JSON{{
|
||||
Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
|
||||
}},
|
||||
}},
|
||||
},
|
||||
Template: v1alpha1.ApplicationSetTemplate{},
|
||||
},
|
||||
}
|
||||
|
||||
appStatuses := []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "my-application",
|
||||
LastTransitionTime: &metav1.Time{},
|
||||
Message: "testing SetApplicationSetApplicationStatus to Healthy",
|
||||
Status: "Healthy",
|
||||
},
|
||||
}
|
||||
|
||||
kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
|
||||
argoDBMock := dbmocks.ArgoDB{}
|
||||
argoObjs := []runtime.Object{}
|
||||
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).Build()
|
||||
|
||||
r := ApplicationSetReconciler{
|
||||
Client: client,
|
||||
Scheme: scheme,
|
||||
Renderer: &utils.Render{},
|
||||
Recorder: record.NewFakeRecorder(1),
|
||||
Generators: map[string]generators.Generator{
|
||||
"List": generators.NewListGenerator(),
|
||||
for _, cc := range []struct {
|
||||
name string
|
||||
appSet v1alpha1.ApplicationSet
|
||||
appStatuses []v1alpha1.ApplicationSetApplicationStatus
|
||||
expectedAppStatuses []v1alpha1.ApplicationSetApplicationStatus
|
||||
}{
|
||||
{
|
||||
name: "sets a single appstatus",
|
||||
appSet: v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Generators: []v1alpha1.ApplicationSetGenerator{
|
||||
{List: &v1alpha1.ListGenerator{
|
||||
Elements: []apiextensionsv1.JSON{{
|
||||
Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
|
||||
}},
|
||||
}},
|
||||
},
|
||||
Template: v1alpha1.ApplicationSetTemplate{},
|
||||
},
|
||||
},
|
||||
appStatuses: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
Message: "testing SetApplicationSetApplicationStatus to Healthy",
|
||||
Status: "Healthy",
|
||||
},
|
||||
},
|
||||
expectedAppStatuses: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
Message: "testing SetApplicationSetApplicationStatus to Healthy",
|
||||
Status: "Healthy",
|
||||
},
|
||||
},
|
||||
},
|
||||
ArgoDB: &argoDBMock,
|
||||
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
|
||||
KubeClientset: kubeclientset,
|
||||
{
|
||||
name: "removes an appstatus",
|
||||
appSet: v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Generators: []v1alpha1.ApplicationSetGenerator{
|
||||
{List: &v1alpha1.ListGenerator{
|
||||
Elements: []apiextensionsv1.JSON{{
|
||||
Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
|
||||
}},
|
||||
}},
|
||||
},
|
||||
Template: v1alpha1.ApplicationSetTemplate{},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
Message: "testing SetApplicationSetApplicationStatus to Healthy",
|
||||
Status: "Healthy",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
appStatuses: []v1alpha1.ApplicationSetApplicationStatus{},
|
||||
expectedAppStatuses: nil,
|
||||
},
|
||||
} {
|
||||
|
||||
t.Run(cc.name, func(t *testing.T) {
|
||||
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&cc.appSet).Build()
|
||||
|
||||
r := ApplicationSetReconciler{
|
||||
Client: client,
|
||||
Scheme: scheme,
|
||||
Renderer: &utils.Render{},
|
||||
Recorder: record.NewFakeRecorder(1),
|
||||
Generators: map[string]generators.Generator{
|
||||
"List": generators.NewListGenerator(),
|
||||
},
|
||||
ArgoDB: &argoDBMock,
|
||||
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
|
||||
KubeClientset: kubeclientset,
|
||||
}
|
||||
|
||||
err = r.setAppSetApplicationStatus(context.TODO(), &cc.appSet, cc.appStatuses)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, cc.expectedAppStatuses, cc.appSet.Status.ApplicationStatus)
|
||||
})
|
||||
}
|
||||
|
||||
err = r.setAppSetApplicationStatus(context.TODO(), &appSet, appStatuses)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Len(t, appSet.Status.ApplicationStatus, 1)
|
||||
}
|
||||
|
||||
func TestBuildAppDependencyList(t *testing.T) {
|
||||
@@ -2792,7 +3241,7 @@ func TestBuildAppDependencyList(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple 'NotIn' selectors only match Applications with all labels",
|
||||
name: "multiple 'NotIn' selectors remove Applications with mising labels on any match",
|
||||
appSet: v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
@@ -2846,10 +3295,88 @@ func TestBuildAppDependencyList(t *testing.T) {
|
||||
},
|
||||
},
|
||||
expectedList: [][]string{
|
||||
{"app-qa1"},
|
||||
{},
|
||||
},
|
||||
expectedStepMap: map[string]int{},
|
||||
},
|
||||
{
|
||||
name: "multiple 'NotIn' selectors filter all matching Applications",
|
||||
appSet: v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{
|
||||
{
|
||||
Key: "region",
|
||||
Operator: "NotIn",
|
||||
Values: []string{
|
||||
"us-east-2",
|
||||
},
|
||||
},
|
||||
{
|
||||
Key: "env",
|
||||
Operator: "NotIn",
|
||||
Values: []string{
|
||||
"qa",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
apps: []v1alpha1.Application{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-qa1",
|
||||
Labels: map[string]string{
|
||||
"env": "qa",
|
||||
"region": "us-east-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-qa2",
|
||||
Labels: map[string]string{
|
||||
"env": "qa",
|
||||
"region": "us-east-2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-prod1",
|
||||
Labels: map[string]string{
|
||||
"env": "prod",
|
||||
"region": "us-east-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-prod2",
|
||||
Labels: map[string]string{
|
||||
"env": "prod",
|
||||
"region": "us-east-2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedList: [][]string{
|
||||
{"app-prod1"},
|
||||
},
|
||||
expectedStepMap: map[string]int{
|
||||
"app-qa1": 0,
|
||||
"app-prod1": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -4065,6 +4592,63 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "progresses a pending application with a successful sync <1s ago to progressing",
|
||||
appSet: v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
LastTransitionTime: &metav1.Time{
|
||||
Time: time.Now(),
|
||||
},
|
||||
Message: "",
|
||||
Status: "Pending",
|
||||
Step: "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
apps: []v1alpha1.Application{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app1",
|
||||
},
|
||||
Status: v1alpha1.ApplicationStatus{
|
||||
Health: v1alpha1.HealthStatus{
|
||||
Status: health.HealthStatusDegraded,
|
||||
},
|
||||
OperationState: &v1alpha1.OperationState{
|
||||
Phase: common.OperationSucceeded,
|
||||
StartedAt: metav1.Time{
|
||||
Time: time.Now().Add(time.Duration(-1) * time.Second),
|
||||
},
|
||||
},
|
||||
Sync: v1alpha1.SyncStatus{
|
||||
Status: v1alpha1.SyncStatusCodeSynced,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
Message: "Application resource completed a sync successfully, updating status from Pending to Progressing.",
|
||||
Status: "Progressing",
|
||||
Step: "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "does not progresses a pending application with an old successful sync to progressing",
|
||||
appSet: v1alpha1.ApplicationSet{
|
||||
@@ -4083,7 +4667,7 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
{
|
||||
Application: "app1",
|
||||
LastTransitionTime: &metav1.Time{
|
||||
Time: time.Now().Add(time.Duration(-1) * time.Minute),
|
||||
Time: time.Now(),
|
||||
},
|
||||
Message: "Application moved to Pending status, watching for the Application resource to start Progressing.",
|
||||
Status: "Pending",
|
||||
@@ -4104,7 +4688,7 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
OperationState: &v1alpha1.OperationState{
|
||||
Phase: common.OperationSucceeded,
|
||||
StartedAt: metav1.Time{
|
||||
Time: time.Now().Add(time.Duration(-2) * time.Minute),
|
||||
Time: time.Now().Add(time.Duration(-11) * time.Second),
|
||||
},
|
||||
},
|
||||
Sync: v1alpha1.SyncStatus{
|
||||
@@ -4122,6 +4706,63 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "removes the appStatus for applications that no longer exist",
|
||||
appSet: v1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
Message: "Application has pending changes, setting status to Waiting.",
|
||||
Status: "Waiting",
|
||||
Step: "1",
|
||||
},
|
||||
{
|
||||
Application: "app2",
|
||||
Message: "Application has pending changes, setting status to Waiting.",
|
||||
Status: "Waiting",
|
||||
Step: "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
apps: []v1alpha1.Application{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app1",
|
||||
},
|
||||
Status: v1alpha1.ApplicationStatus{
|
||||
Health: v1alpha1.HealthStatus{
|
||||
Status: health.HealthStatusHealthy,
|
||||
},
|
||||
OperationState: &v1alpha1.OperationState{
|
||||
Phase: common.OperationSucceeded,
|
||||
},
|
||||
Sync: v1alpha1.SyncStatus{
|
||||
Status: v1alpha1.SyncStatusCodeSynced,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
Message: "Application resource is already Healthy, updating status from Waiting to Healthy.",
|
||||
Status: "Healthy",
|
||||
Step: "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
|
||||
t.Run(cc.name, func(t *testing.T) {
|
||||
|
||||
@@ -5,9 +5,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/generators"
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
|
||||
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
@@ -17,6 +14,10 @@ import (
|
||||
kubefake "k8s.io/client-go/kubernetes/fake"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/generators"
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
|
||||
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
|
||||
func TestRequeueAfter(t *testing.T) {
|
||||
@@ -59,9 +60,9 @@ func TestRequeueAfter(t *testing.T) {
|
||||
"List": generators.NewListGenerator(),
|
||||
"Clusters": generators.NewClusterGenerator(k8sClient, ctx, appClientset, "argocd"),
|
||||
"Git": generators.NewGitGenerator(mockServer),
|
||||
"SCMProvider": generators.NewSCMProviderGenerator(fake.NewClientBuilder().WithObjects(&corev1.Secret{}).Build(), generators.SCMAuthProviders{}),
|
||||
"SCMProvider": generators.NewSCMProviderGenerator(fake.NewClientBuilder().WithObjects(&corev1.Secret{}).Build(), generators.SCMAuthProviders{}, "", []string{""}),
|
||||
"ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, fakeDynClient, appClientset, "argocd"),
|
||||
"PullRequest": generators.NewPullRequestGenerator(k8sClient, generators.SCMAuthProviders{}),
|
||||
"PullRequest": generators.NewPullRequestGenerator(k8sClient, generators.SCMAuthProviders{}, "", []string{""}),
|
||||
}
|
||||
|
||||
nestedGenerators := map[string]generators.Generator{
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: ApplicationSet
|
||||
metadata:
|
||||
name: guestbook
|
||||
spec:
|
||||
goTemplate: true
|
||||
generators:
|
||||
- list:
|
||||
elements:
|
||||
- cluster: engineering-dev
|
||||
url: https://kubernetes.default.svc
|
||||
foo: bar
|
||||
# Update foo value with foo: bar
|
||||
# Application engineering-prod-guestbook labels will still be baz
|
||||
# Delete this element
|
||||
# Application engineering-prod-guestbook will be kept
|
||||
- cluster: engineering-prod
|
||||
url: https://kubernetes.default.svc
|
||||
foo: baz
|
||||
template:
|
||||
metadata:
|
||||
name: '{{.cluster}}-guestbook'
|
||||
labels:
|
||||
foo: '{{.foo}}'
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/argoproj/argo-cd.git
|
||||
targetRevision: HEAD
|
||||
path: applicationset/examples/list-generator/guestbook/{{.cluster}}
|
||||
destination:
|
||||
server: '{{.url}}'
|
||||
namespace: guestbook
|
||||
syncPolicy:
|
||||
applicationsSync: create-only
|
||||
@@ -0,0 +1,35 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: ApplicationSet
|
||||
metadata:
|
||||
name: guestbook
|
||||
spec:
|
||||
goTemplate: true
|
||||
generators:
|
||||
- list:
|
||||
elements:
|
||||
- cluster: engineering-dev
|
||||
url: https://kubernetes.default.svc
|
||||
foo: bar
|
||||
# Update foo value with foo: bar
|
||||
# Application engineering-prod-guestbook labels will change to foo: bar
|
||||
# Delete this element
|
||||
# Application engineering-prod-guestbook will be kept
|
||||
- cluster: engineering-prod
|
||||
url: https://kubernetes.default.svc
|
||||
foo: baz
|
||||
template:
|
||||
metadata:
|
||||
name: '{{.cluster}}-guestbook'
|
||||
labels:
|
||||
foo: '{{.foo}}'
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/argoproj/argo-cd.git
|
||||
targetRevision: HEAD
|
||||
path: applicationset/examples/list-generator/guestbook/{{.cluster}}
|
||||
destination:
|
||||
server: '{{.url}}'
|
||||
namespace: guestbook
|
||||
syncPolicy:
|
||||
applicationsSync: create-update
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: guestbook-ui
|
||||
spec:
|
||||
replicas: 1
|
||||
revisionHistoryLimit: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: guestbook-ui
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: guestbook-ui
|
||||
spec:
|
||||
containers:
|
||||
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
|
||||
name: guestbook-ui
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: guestbook-ui
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
selector:
|
||||
app: guestbook-ui
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: guestbook-ui
|
||||
spec:
|
||||
replicas: 1
|
||||
revisionHistoryLimit: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: guestbook-ui
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: guestbook-ui
|
||||
spec:
|
||||
containers:
|
||||
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
|
||||
name: guestbook-ui
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: guestbook-ui
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
selector:
|
||||
app: guestbook-ui
|
||||
@@ -4,6 +4,7 @@ metadata:
|
||||
name: guestbook
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- clusters: {}
|
||||
template:
|
||||
|
||||
@@ -4,6 +4,7 @@ metadata:
|
||||
name: book-import
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- clusterDecisionResource:
|
||||
configMapRef: ocm-placement
|
||||
|
||||
@@ -8,6 +8,7 @@ metadata:
|
||||
name: guestbook
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- clusters: {}
|
||||
template:
|
||||
|
||||
@@ -27,6 +27,7 @@ metadata:
|
||||
name: cluster-addons
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- git:
|
||||
repoURL: https://github.com/infra-team/cluster-deployments.git
|
||||
|
||||
@@ -38,6 +38,7 @@ metadata:
|
||||
name: guestbook
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- git:
|
||||
repoURL: https://github.com/infra-team/cluster-deployments.git
|
||||
|
||||
@@ -51,6 +51,7 @@ metadata:
|
||||
name: guestbook
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- git:
|
||||
repoURL: https://github.com/infra-team/cluster-deployments.git
|
||||
|
||||
@@ -5,6 +5,7 @@ metadata:
|
||||
name: guestbook
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- list:
|
||||
elements:
|
||||
|
||||
@@ -8,6 +8,7 @@ metadata:
|
||||
name: guestbook
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- list:
|
||||
elements:
|
||||
|
||||
@@ -5,6 +5,7 @@ metadata:
|
||||
namespace: argocd
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- git:
|
||||
repoURL: https://github.com/argoproj/argo-cd.git
|
||||
|
||||
@@ -5,6 +5,7 @@ metadata:
|
||||
namespace: argocd
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- git:
|
||||
repoURL: https://github.com/argoproj/argo-cd.git
|
||||
|
||||
@@ -4,6 +4,7 @@ metadata:
|
||||
name: guestbook
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- git:
|
||||
repoURL: https://github.com/argoproj/argo-cd.git
|
||||
|
||||
@@ -4,6 +4,7 @@ metadata:
|
||||
name: guestbook
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- list:
|
||||
elements:
|
||||
|
||||
@@ -8,6 +8,7 @@ metadata:
|
||||
name: cluster-git
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- matrix:
|
||||
generators:
|
||||
|
||||
@@ -8,6 +8,7 @@ metadata:
|
||||
name: list-git
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- matrix:
|
||||
generators:
|
||||
|
||||
@@ -5,6 +5,7 @@ metadata:
|
||||
namespace: argocd
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- matrix:
|
||||
generators:
|
||||
|
||||
@@ -13,6 +13,7 @@ metadata:
|
||||
name: matrix-and-union-in-matrix
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- matrix:
|
||||
generators:
|
||||
|
||||
@@ -4,6 +4,7 @@ metadata:
|
||||
name: merge-clusters-and-list
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- merge:
|
||||
mergeKeys:
|
||||
|
||||
@@ -4,6 +4,7 @@ metadata:
|
||||
name: merge-two-matrixes
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- merge:
|
||||
mergeKeys:
|
||||
|
||||
@@ -4,6 +4,7 @@ metadata:
|
||||
name: myapp
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- pullRequest:
|
||||
github:
|
||||
|
||||
@@ -4,6 +4,7 @@ metadata:
|
||||
name: guestbook
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- scmProvider:
|
||||
github:
|
||||
|
||||
@@ -8,6 +8,7 @@ metadata:
|
||||
name: guestbook
|
||||
spec:
|
||||
goTemplate: true
|
||||
goTemplateOptions: ["missingkey=error"]
|
||||
generators:
|
||||
- list:
|
||||
elements:
|
||||
|
||||
@@ -61,8 +61,7 @@ func (g *ClusterGenerator) GetTemplate(appSetGenerator *argoappsetv1alpha1.Appli
|
||||
return &appSetGenerator.Clusters.Template
|
||||
}
|
||||
|
||||
func (g *ClusterGenerator) GenerateParams(
|
||||
appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator, appSet *argoappsetv1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
|
||||
func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator, appSet *argoappsetv1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
|
||||
|
||||
if appSetGenerator == nil {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
@@ -109,7 +108,7 @@ func (g *ClusterGenerator) GenerateParams(
|
||||
params["nameNormalized"] = cluster.Name
|
||||
params["server"] = cluster.Server
|
||||
|
||||
err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet)
|
||||
err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -149,7 +148,7 @@ func (g *ClusterGenerator) GenerateParams(
|
||||
}
|
||||
}
|
||||
|
||||
err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet)
|
||||
err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -162,44 +161,6 @@ func (g *ClusterGenerator) GenerateParams(
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func appendTemplatedValues(clusterValues map[string]string, params map[string]interface{}, appSet *argoappsetv1alpha1.ApplicationSet) error {
|
||||
// We create a local map to ensure that we do not fall victim to a billion-laughs attack. We iterate through the
|
||||
// cluster values map and only replace values in said map if it has already been whitelisted in the params map.
|
||||
// Once we iterate through all the cluster values we can then safely merge the `tmp` map into the main params map.
|
||||
tmp := map[string]interface{}{}
|
||||
|
||||
for key, value := range clusterValues {
|
||||
result, err := replaceTemplatedString(value, params, appSet)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error replacing templated String: %w", err)
|
||||
}
|
||||
|
||||
if appSet.Spec.GoTemplate {
|
||||
if tmp["values"] == nil {
|
||||
tmp["values"] = map[string]string{}
|
||||
}
|
||||
tmp["values"].(map[string]string)[key] = result
|
||||
} else {
|
||||
tmp[fmt.Sprintf("values.%s", key)] = result
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range tmp {
|
||||
params[key] = value
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func replaceTemplatedString(value string, params map[string]interface{}, appSet *argoappsetv1alpha1.ApplicationSet) (string, error) {
|
||||
replacedTmplStr, err := render.Replace(value, params, appSet.Spec.GoTemplate)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return replacedTmplStr, nil
|
||||
}
|
||||
|
||||
func (g *ClusterGenerator) getSecretsByClusterName(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator) (map[string]corev1.Secret, error) {
|
||||
// List all Clusters:
|
||||
clusterSecretList := &corev1.SecretList{}
|
||||
|
||||
@@ -3,6 +3,7 @@ package generators
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -15,8 +16,6 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
const resourceApiVersion = "mallard.io/v1"
|
||||
|
||||
@@ -4,9 +4,10 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/jeremywohl/flatten"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/utils"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
|
||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
@@ -26,7 +27,10 @@ type TransformResult struct {
|
||||
|
||||
// Transform a spec generator to list of paramSets and a template
|
||||
func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, allGenerators map[string]Generator, baseTemplate argoprojiov1alpha1.ApplicationSetTemplate, appSet *argoprojiov1alpha1.ApplicationSet, genParams map[string]interface{}) ([]TransformResult, error) {
|
||||
selector, err := metav1.LabelSelectorAsSelector(requestedGenerator.Selector)
|
||||
// This is a custom version of the `LabelSelectorAsSelector` that is in k8s.io/apimachinery. This has been copied
|
||||
// verbatim from that package, with the difference that we do not have any restrictions on label values. This is done
|
||||
// so that, among other things, we can match on cluster urls.
|
||||
selector, err := utils.LabelSelectorAsSelector(requestedGenerator.Selector)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing label selector: %w", err)
|
||||
}
|
||||
@@ -49,7 +53,7 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
|
||||
}
|
||||
var params []map[string]interface{}
|
||||
if len(genParams) != 0 {
|
||||
tempInterpolatedGenerator, err := InterpolateGenerator(&requestedGenerator, genParams, appSet.Spec.GoTemplate)
|
||||
tempInterpolatedGenerator, err := InterpolateGenerator(&requestedGenerator, genParams, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
|
||||
interpolatedGenerator = &tempInterpolatedGenerator
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("genParams", genParams).
|
||||
@@ -71,8 +75,17 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
|
||||
}
|
||||
var filterParams []map[string]interface{}
|
||||
for _, param := range params {
|
||||
flatParam, err := flattenParameters(param)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("generator", g).
|
||||
Error("error flattening params")
|
||||
if firstError == nil {
|
||||
firstError = err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if requestedGenerator.Selector != nil && !selector.Matches(labels.Set(keepOnlyStringValues(param))) {
|
||||
if requestedGenerator.Selector != nil && !selector.Matches(labels.Set(flatParam)) {
|
||||
continue
|
||||
}
|
||||
filterParams = append(filterParams, param)
|
||||
@@ -87,18 +100,6 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
|
||||
return res, firstError
|
||||
}
|
||||
|
||||
func keepOnlyStringValues(in map[string]interface{}) map[string]string {
|
||||
var out map[string]string = map[string]string{}
|
||||
|
||||
for key, value := range in {
|
||||
if _, ok := value.(string); ok {
|
||||
out[key] = value.(string)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func GetRelevantGenerators(requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, generators map[string]Generator) []Generator {
|
||||
var res []Generator
|
||||
|
||||
@@ -121,6 +122,20 @@ func GetRelevantGenerators(requestedGenerator *argoprojiov1alpha1.ApplicationSet
|
||||
return res
|
||||
}
|
||||
|
||||
func flattenParameters(in map[string]interface{}) (map[string]string, error) {
|
||||
flat, err := flatten.Flatten(in, "", flatten.DotStyle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := make(map[string]string, len(flat))
|
||||
for k, v := range flat {
|
||||
out[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func mergeGeneratorTemplate(g Generator, requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetTemplate argoprojiov1alpha1.ApplicationSetTemplate) (argoprojiov1alpha1.ApplicationSetTemplate, error) {
|
||||
// Make a copy of the value from `GetTemplate()` before merge, rather than copying directly into
|
||||
// the provided parameter (which will touch the original resource object returned by client-go)
|
||||
@@ -133,13 +148,26 @@ func mergeGeneratorTemplate(g Generator, requestedGenerator *argoprojiov1alpha1.
|
||||
|
||||
// InterpolateGenerator allows interpolating the matrix's 2nd child generator with values from the 1st child generator
|
||||
// "params" parameter is an array, where each index corresponds to a generator. Each index contains a map w/ that generator's parameters.
|
||||
func InterpolateGenerator(requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, params map[string]interface{}, useGoTemplate bool) (argoprojiov1alpha1.ApplicationSetGenerator, error) {
|
||||
func InterpolateGenerator(requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, params map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (argoprojiov1alpha1.ApplicationSetGenerator, error) {
|
||||
render := utils.Render{}
|
||||
interpolatedGenerator, err := render.RenderGeneratorParams(requestedGenerator, params, useGoTemplate)
|
||||
interpolatedGenerator, err := render.RenderGeneratorParams(requestedGenerator, params, useGoTemplate, goTemplateOptions)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("interpolatedGenerator", interpolatedGenerator).Error("error interpolating generator with other generator's parameter")
|
||||
return *interpolatedGenerator, err
|
||||
return argoprojiov1alpha1.ApplicationSetGenerator{}, err
|
||||
}
|
||||
|
||||
return *interpolatedGenerator, nil
|
||||
}
|
||||
|
||||
// Fixes https://github.com/argoproj/argo-cd/issues/11982 while ensuring backwards compatibility.
|
||||
// This is only a short-term solution and should be removed in a future major version.
|
||||
func dropDisabledNestedSelectors(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator) bool {
|
||||
var foundSelector bool
|
||||
for i := range generators {
|
||||
if generators[i].Selector != nil {
|
||||
foundSelector = true
|
||||
generators[i].Selector = nil
|
||||
}
|
||||
}
|
||||
return foundSelector
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
|
||||
|
||||
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
@@ -73,7 +74,9 @@ func TestMatchValues(t *testing.T) {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "set",
|
||||
},
|
||||
Spec: argov1alpha1.ApplicationSetSpec{},
|
||||
Spec: argov1alpha1.ApplicationSetSpec{
|
||||
GoTemplate: false,
|
||||
},
|
||||
}
|
||||
|
||||
results, err := Transform(argov1alpha1.ApplicationSetGenerator{
|
||||
@@ -92,6 +95,158 @@ func TestMatchValues(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchValuesGoTemplate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
elements []apiextensionsv1.JSON
|
||||
selector *metav1.LabelSelector
|
||||
expected []map[string]interface{}
|
||||
}{
|
||||
{
|
||||
name: "no filter",
|
||||
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
|
||||
selector: &metav1.LabelSelector{},
|
||||
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url"}},
|
||||
},
|
||||
{
|
||||
name: "nil",
|
||||
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
|
||||
selector: nil,
|
||||
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url"}},
|
||||
},
|
||||
{
|
||||
name: "values.foo should be foo but is ignore element",
|
||||
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}},
|
||||
selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"values.foo": "foo",
|
||||
},
|
||||
},
|
||||
expected: []map[string]interface{}{},
|
||||
},
|
||||
{
|
||||
name: "values.foo should be bar",
|
||||
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}},
|
||||
selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"values.foo": "bar",
|
||||
},
|
||||
},
|
||||
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url", "values": map[string]interface{}{"foo": "bar"}}},
|
||||
},
|
||||
{
|
||||
name: "values.0 should be bar",
|
||||
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":["bar"]}`)}},
|
||||
selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"values.0": "bar",
|
||||
},
|
||||
},
|
||||
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url", "values": []interface{}{"bar"}}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
var listGenerator = NewListGenerator()
|
||||
var data = map[string]Generator{
|
||||
"List": listGenerator,
|
||||
}
|
||||
|
||||
applicationSetInfo := argov1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "set",
|
||||
},
|
||||
Spec: argov1alpha1.ApplicationSetSpec{
|
||||
GoTemplate: true,
|
||||
},
|
||||
}
|
||||
|
||||
results, err := Transform(argov1alpha1.ApplicationSetGenerator{
|
||||
Selector: testCase.selector,
|
||||
List: &argov1alpha1.ListGenerator{
|
||||
Elements: testCase.elements,
|
||||
Template: emptyTemplate(),
|
||||
}},
|
||||
data,
|
||||
emptyTemplate(),
|
||||
&applicationSetInfo, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, testCase.expected, results[0].Params)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransForm(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
selector *metav1.LabelSelector
|
||||
expected []map[string]interface{}
|
||||
}{
|
||||
{
|
||||
name: "server filter",
|
||||
selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"server": "https://production-01.example.com"},
|
||||
},
|
||||
expected: []map[string]interface{}{{
|
||||
"metadata.annotations.foo.argoproj.io": "production",
|
||||
"metadata.labels.argocd.argoproj.io/secret-type": "cluster",
|
||||
"metadata.labels.environment": "production",
|
||||
"metadata.labels.org": "bar",
|
||||
"name": "production_01/west",
|
||||
"nameNormalized": "production-01-west",
|
||||
"server": "https://production-01.example.com",
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "server filter with long url",
|
||||
selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"server": "https://some-really-long-url-that-will-exceed-63-characters.com"},
|
||||
},
|
||||
expected: []map[string]interface{}{{
|
||||
"metadata.annotations.foo.argoproj.io": "production",
|
||||
"metadata.labels.argocd.argoproj.io/secret-type": "cluster",
|
||||
"metadata.labels.environment": "production",
|
||||
"metadata.labels.org": "bar",
|
||||
"name": "some-really-long-server-url",
|
||||
"nameNormalized": "some-really-long-server-url",
|
||||
"server": "https://some-really-long-url-that-will-exceed-63-characters.com",
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
testGenerators := map[string]Generator{
|
||||
"Clusters": getMockClusterGenerator(),
|
||||
}
|
||||
|
||||
applicationSetInfo := argov1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "set",
|
||||
},
|
||||
Spec: argov1alpha1.ApplicationSetSpec{},
|
||||
}
|
||||
|
||||
results, err := Transform(
|
||||
argov1alpha1.ApplicationSetGenerator{
|
||||
Selector: testCase.selector,
|
||||
Clusters: &argov1alpha1.ClusterGenerator{
|
||||
Selector: metav1.LabelSelector{},
|
||||
Template: argov1alpha1.ApplicationSetTemplate{},
|
||||
Values: nil,
|
||||
}},
|
||||
testGenerators,
|
||||
emptyTemplate(),
|
||||
&applicationSetInfo, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, testCase.expected, results[0].Params)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func emptyTemplate() argov1alpha1.ApplicationSetTemplate {
|
||||
return argov1alpha1.ApplicationSetTemplate{
|
||||
Spec: argov1alpha1.ApplicationSpec{
|
||||
@@ -150,8 +305,35 @@ func getMockClusterGenerator() Generator {
|
||||
},
|
||||
Type: corev1.SecretType("Opaque"),
|
||||
},
|
||||
&corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Secret",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "some-really-long-server-url",
|
||||
Namespace: "namespace",
|
||||
Labels: map[string]string{
|
||||
"argocd.argoproj.io/secret-type": "cluster",
|
||||
"environment": "production",
|
||||
"org": "bar",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"foo.argoproj.io": "production",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"config": []byte("{}"),
|
||||
"name": []byte("some-really-long-server-url"),
|
||||
"server": []byte("https://some-really-long-url-that-will-exceed-63-characters.com"),
|
||||
},
|
||||
Type: corev1.SecretType("Opaque"),
|
||||
},
|
||||
}
|
||||
runtimeClusters := []runtime.Object{}
|
||||
for _, clientCluster := range clusters {
|
||||
runtimeClusters = append(runtimeClusters, clientCluster)
|
||||
}
|
||||
appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
|
||||
|
||||
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
|
||||
@@ -232,7 +414,7 @@ func TestInterpolateGenerator(t *testing.T) {
|
||||
"path[1]": "p2",
|
||||
"path.basenameNormalized": "app3",
|
||||
}
|
||||
interpolatedGenerator, err := InterpolateGenerator(requestedGenerator, gitGeneratorParams, false)
|
||||
interpolatedGenerator, err := InterpolateGenerator(requestedGenerator, gitGeneratorParams, false, nil)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator")
|
||||
return
|
||||
@@ -257,7 +439,7 @@ func TestInterpolateGenerator(t *testing.T) {
|
||||
clusterGeneratorParams := map[string]interface{}{
|
||||
"name": "production_01/west", "server": "https://production-01.example.com",
|
||||
}
|
||||
interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, false)
|
||||
interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, false, nil)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator")
|
||||
return
|
||||
@@ -285,7 +467,7 @@ func TestInterpolateGenerator_go(t *testing.T) {
|
||||
"segments": []string{"p1", "p2", "app3"},
|
||||
},
|
||||
}
|
||||
interpolatedGenerator, err := InterpolateGenerator(requestedGenerator, gitGeneratorParams, true)
|
||||
interpolatedGenerator, err := InterpolateGenerator(requestedGenerator, gitGeneratorParams, true, nil)
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator")
|
||||
@@ -311,7 +493,7 @@ func TestInterpolateGenerator_go(t *testing.T) {
|
||||
clusterGeneratorParams := map[string]interface{}{
|
||||
"name": "production_01/west", "server": "https://production-01.example.com",
|
||||
}
|
||||
interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, true)
|
||||
interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, true, nil)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator")
|
||||
return
|
||||
@@ -319,3 +501,60 @@ func TestInterpolateGenerator_go(t *testing.T) {
|
||||
assert.Equal(t, "production_01/west", interpolatedGenerator.Git.Files[0].Path)
|
||||
assert.Equal(t, "https://production-01.example.com", interpolatedGenerator.Git.Files[1].Path)
|
||||
}
|
||||
|
||||
func TestInterpolateGeneratorError(t *testing.T) {
|
||||
type args struct {
|
||||
requestedGenerator *argov1alpha1.ApplicationSetGenerator
|
||||
params map[string]interface{}
|
||||
useGoTemplate bool
|
||||
goTemplateOptions []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want argov1alpha1.ApplicationSetGenerator
|
||||
expectedErrStr string
|
||||
}{
|
||||
{name: "Empty Gen", args: args{
|
||||
requestedGenerator: nil,
|
||||
params: nil,
|
||||
useGoTemplate: false,
|
||||
goTemplateOptions: nil,
|
||||
}, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: "generator is empty"},
|
||||
{name: "No Params", args: args{
|
||||
requestedGenerator: &argov1alpha1.ApplicationSetGenerator{},
|
||||
params: map[string]interface{}{},
|
||||
useGoTemplate: false,
|
||||
goTemplateOptions: nil,
|
||||
}, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: ""},
|
||||
{name: "Error templating", args: args{
|
||||
requestedGenerator: &argov1alpha1.ApplicationSetGenerator{Git: &argov1alpha1.GitGenerator{
|
||||
RepoURL: "foo",
|
||||
Files: []argov1alpha1.GitFileGeneratorItem{{Path: "bar/"}},
|
||||
Revision: "main",
|
||||
Values: map[string]string{
|
||||
"git_test": "{{ toPrettyJson . }}",
|
||||
"selection": "{{ default .override .test }}",
|
||||
"resolved": "{{ index .rmap (default .override .test) }}",
|
||||
},
|
||||
}},
|
||||
params: map[string]interface{}{
|
||||
"name": "in-cluster",
|
||||
"override": "foo",
|
||||
},
|
||||
useGoTemplate: true,
|
||||
goTemplateOptions: []string{},
|
||||
}, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: "failed to replace parameters in generator: failed to execute go template {{ index .rmap (default .override .test) }}: template: :1:3: executing \"\" at <index .rmap (default .override .test)>: error calling index: index of untyped nil"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := InterpolateGenerator(tt.args.requestedGenerator, tt.args.params, tt.args.useGoTemplate, tt.args.goTemplateOptions)
|
||||
if tt.expectedErrStr != "" {
|
||||
assert.EqualError(t, err, tt.expectedErrStr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
assert.Equalf(t, tt.want, got, "InterpolateGenerator(%v, %v, %v, %v)", tt.args.requestedGenerator, tt.args.params, tt.args.useGoTemplate, tt.args.goTemplateOptions)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,12 +56,14 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
}
|
||||
|
||||
noRevisionCache := appSet.RefreshRequired()
|
||||
|
||||
var err error
|
||||
var res []map[string]interface{}
|
||||
if len(appSetGenerator.Git.Directories) != 0 {
|
||||
res, err = g.generateParamsForGitDirectories(appSetGenerator, appSet.Spec.GoTemplate)
|
||||
res, err = g.generateParamsForGitDirectories(appSetGenerator, noRevisionCache, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
|
||||
} else if len(appSetGenerator.Git.Files) != 0 {
|
||||
res, err = g.generateParamsForGitFiles(appSetGenerator, appSet.Spec.GoTemplate)
|
||||
res, err = g.generateParamsForGitFiles(appSetGenerator, noRevisionCache, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
|
||||
} else {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
}
|
||||
@@ -72,10 +74,10 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool) ([]map[string]interface{}, error) {
|
||||
func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache bool, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
|
||||
|
||||
// Directories, not files
|
||||
allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision)
|
||||
allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, noRevisionCache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -90,17 +92,20 @@ func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoproj
|
||||
|
||||
requestedApps := g.filterApps(appSetGenerator.Git.Directories, allPaths)
|
||||
|
||||
res := g.generateParamsFromApps(requestedApps, appSetGenerator, useGoTemplate)
|
||||
res, err := g.generateParamsFromApps(requestedApps, appSetGenerator, useGoTemplate, goTemplateOptions)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate params from apps: %w", err)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool) ([]map[string]interface{}, error) {
|
||||
func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache bool, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, 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, requestedPath.Path)
|
||||
files, err := g.repos.GetFiles(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, requestedPath.Path, noRevisionCache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -122,7 +127,7 @@ func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1al
|
||||
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], useGoTemplate, appSetGenerator.Git.PathParamPrefix)
|
||||
paramsArray, err := g.generateParamsFromGitFile(path, allFiles[path], appSetGenerator.Git.Values, useGoTemplate, goTemplateOptions, appSetGenerator.Git.PathParamPrefix)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to process file '%s': %v", path, err)
|
||||
}
|
||||
@@ -132,7 +137,7 @@ func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1al
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []byte, useGoTemplate bool, pathParamPrefix string) ([]map[string]interface{}, error) {
|
||||
func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []byte, values map[string]string, useGoTemplate bool, goTemplateOptions []string, pathParamPrefix string) ([]map[string]interface{}, error) {
|
||||
objectsFound := []map[string]interface{}{}
|
||||
|
||||
// First, we attempt to parse as an array
|
||||
@@ -195,6 +200,11 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
|
||||
}
|
||||
}
|
||||
|
||||
err := appendTemplatedValues(values, params, useGoTemplate, goTemplateOptions)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to append templated values: %w", err)
|
||||
}
|
||||
|
||||
res = append(res, params)
|
||||
}
|
||||
|
||||
@@ -229,7 +239,7 @@ func (g *GitGenerator) filterApps(Directories []argoprojiov1alpha1.GitDirectoryG
|
||||
return res
|
||||
}
|
||||
|
||||
func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool) []map[string]interface{} {
|
||||
func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
|
||||
res := make([]map[string]interface{}, len(requestedApps))
|
||||
for i, a := range requestedApps {
|
||||
|
||||
@@ -261,8 +271,13 @@ func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGene
|
||||
}
|
||||
}
|
||||
|
||||
err := appendTemplatedValues(appSetGenerator.Git.Values, params, useGoTemplate, goTemplateOptions)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to append templated values: %w", err)
|
||||
}
|
||||
|
||||
res[i] = params
|
||||
}
|
||||
|
||||
return res
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@@ -4,20 +4,21 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
|
||||
|
||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
|
||||
|
||||
func Test_generateParamsFromGitFile(t *testing.T) {
|
||||
values := map[string]string{}
|
||||
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
|
||||
foo:
|
||||
bar: baz
|
||||
`), false, "")
|
||||
`), values, false, nil, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -36,10 +37,11 @@ foo:
|
||||
}
|
||||
|
||||
func Test_generatePrefixedParamsFromGitFile(t *testing.T) {
|
||||
values := map[string]string{}
|
||||
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
|
||||
foo:
|
||||
bar: baz
|
||||
`), false, "myRepo")
|
||||
`), values, false, nil, "myRepo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -58,10 +60,11 @@ foo:
|
||||
}
|
||||
|
||||
func Test_generateParamsFromGitFileGoTemplate(t *testing.T) {
|
||||
values := map[string]string{}
|
||||
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
|
||||
foo:
|
||||
bar: baz
|
||||
`), true, "")
|
||||
`), values, true, nil, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -86,10 +89,11 @@ foo:
|
||||
}
|
||||
|
||||
func Test_generatePrefixedParamsFromGitFileGoTemplate(t *testing.T) {
|
||||
values := map[string]string{}
|
||||
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
|
||||
foo:
|
||||
bar: baz
|
||||
`), true, "myRepo")
|
||||
`), values, true, nil, "myRepo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -123,6 +127,7 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
|
||||
pathParamPrefix string
|
||||
repoApps []string
|
||||
repoError error
|
||||
values map[string]string
|
||||
expected []map[string]interface{}
|
||||
expectedError error
|
||||
}{
|
||||
@@ -213,6 +218,25 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Value variable interpolation",
|
||||
directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}, {Path: "*/*"}},
|
||||
repoApps: []string{
|
||||
"app1",
|
||||
"p1/app2",
|
||||
},
|
||||
repoError: nil,
|
||||
values: map[string]string{
|
||||
"foo": "bar",
|
||||
"aaa": "{{ path[0] }}",
|
||||
"no-op": "{{ this-does-not-exist }}",
|
||||
},
|
||||
expected: []map[string]interface{}{
|
||||
{"values.foo": "bar", "values.no-op": "{{ this-does-not-exist }}", "values.aaa": "app1", "path": "app1", "path.basename": "app1", "path[0]": "app1", "path.basenameNormalized": "app1"},
|
||||
{"values.foo": "bar", "values.no-op": "{{ this-does-not-exist }}", "values.aaa": "p1", "path": "p1/app2", "path.basename": "app2", "path[0]": "p1", "path[1]": "app2", "path.basenameNormalized": "app2"},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "handles empty response from repo server",
|
||||
directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
|
||||
@@ -239,7 +263,7 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
|
||||
|
||||
argoCDServiceMock := mocks.Repos{}
|
||||
|
||||
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
|
||||
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
|
||||
|
||||
var gitGenerator = NewGitGenerator(&argoCDServiceMock)
|
||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||
@@ -253,6 +277,7 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
|
||||
Revision: "Revision",
|
||||
Directories: testCaseCopy.directories,
|
||||
PathParamPrefix: testCaseCopy.pathParamPrefix,
|
||||
Values: testCaseCopy.values,
|
||||
},
|
||||
}},
|
||||
},
|
||||
@@ -534,7 +559,7 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
|
||||
|
||||
argoCDServiceMock := mocks.Repos{}
|
||||
|
||||
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
|
||||
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
|
||||
|
||||
var gitGenerator = NewGitGenerator(&argoCDServiceMock)
|
||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||
@@ -579,6 +604,7 @@ func TestGitGenerateParamsFromFiles(t *testing.T) {
|
||||
repoFileContents 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]interface{}
|
||||
expectedError error
|
||||
}{
|
||||
@@ -642,6 +668,74 @@ func TestGitGenerateParamsFromFiles(t *testing.T) {
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Value variable interpolation",
|
||||
files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.json"}},
|
||||
repoFileContents: 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
|
||||
}`),
|
||||
"cluster-config/staging/config.json": []byte(`{
|
||||
"cluster": {
|
||||
"owner": "foo.bar@example.com",
|
||||
"name": "staging",
|
||||
"address": "https://kubernetes.default.svc"
|
||||
}
|
||||
}`),
|
||||
},
|
||||
repoPathsError: nil,
|
||||
values: map[string]string{
|
||||
"aaa": "{{ cluster.owner }}",
|
||||
"no-op": "{{ this-does-not-exist }}",
|
||||
},
|
||||
expected: []map[string]interface{}{
|
||||
{
|
||||
"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",
|
||||
"values.aaa": "john.doe@example.com",
|
||||
"values.no-op": "{{ this-does-not-exist }}",
|
||||
},
|
||||
{
|
||||
"cluster.owner": "foo.bar@example.com",
|
||||
"cluster.name": "staging",
|
||||
"cluster.address": "https://kubernetes.default.svc",
|
||||
"path": "cluster-config/staging",
|
||||
"path.basename": "staging",
|
||||
"path[0]": "cluster-config",
|
||||
"path[1]": "staging",
|
||||
"path.basenameNormalized": "staging",
|
||||
"path.filename": "config.json",
|
||||
"path.filenameNormalized": "config.json",
|
||||
"values.aaa": "foo.bar@example.com",
|
||||
"values.no-op": "{{ this-does-not-exist }}",
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "handles error during getting repo paths",
|
||||
files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.json"}},
|
||||
@@ -824,7 +918,7 @@ cluster:
|
||||
t.Parallel()
|
||||
|
||||
argoCDServiceMock := mocks.Repos{}
|
||||
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
||||
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
|
||||
|
||||
var gitGenerator = NewGitGenerator(&argoCDServiceMock)
|
||||
@@ -838,6 +932,7 @@ cluster:
|
||||
RepoURL: "RepoURL",
|
||||
Revision: "Revision",
|
||||
Files: testCaseCopy.files,
|
||||
Values: testCaseCopy.values,
|
||||
},
|
||||
}},
|
||||
},
|
||||
@@ -1173,7 +1268,7 @@ cluster:
|
||||
t.Parallel()
|
||||
|
||||
argoCDServiceMock := mocks.Repos{}
|
||||
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
||||
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
|
||||
|
||||
var gitGenerator = NewGitGenerator(&argoCDServiceMock)
|
||||
|
||||
@@ -5,8 +5,9 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
|
||||
var _ Generator = (*ListGenerator)(nil)
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/utils"
|
||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var _ Generator = (*MatrixGenerator)(nil)
|
||||
@@ -84,10 +86,22 @@ func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Appli
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if matrixGen != nil && !appSet.Spec.ApplyNestedSelectors {
|
||||
foundSelector := dropDisabledNestedSelectors(matrixGen.Generators)
|
||||
if foundSelector {
|
||||
log.Warnf("AppSet '%v' defines selector on nested matrix generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selectors", appSet.Name)
|
||||
}
|
||||
}
|
||||
mergeGen, err := getMergeGenerator(appSetBaseGenerator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mergeGen != nil && !appSet.Spec.ApplyNestedSelectors {
|
||||
foundSelector := dropDisabledNestedSelectors(mergeGen.Generators)
|
||||
if foundSelector {
|
||||
log.Warnf("AppSet '%v' defines selector on nested merge generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selectors", appSet.Name)
|
||||
}
|
||||
}
|
||||
|
||||
t, err := Transform(
|
||||
argoprojiov1alpha1.ApplicationSetGenerator{
|
||||
@@ -97,6 +111,7 @@ func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Appli
|
||||
SCMProvider: appSetBaseGenerator.SCMProvider,
|
||||
ClusterDecisionResource: appSetBaseGenerator.ClusterDecisionResource,
|
||||
PullRequest: appSetBaseGenerator.PullRequest,
|
||||
Plugin: appSetBaseGenerator.Plugin,
|
||||
Matrix: matrixGen,
|
||||
Merge: mergeGen,
|
||||
Selector: appSetBaseGenerator.Selector,
|
||||
@@ -135,6 +150,7 @@ func (m *MatrixGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Ap
|
||||
Clusters: r.Clusters,
|
||||
Git: r.Git,
|
||||
PullRequest: r.PullRequest,
|
||||
Plugin: r.Plugin,
|
||||
Matrix: matrixGen,
|
||||
Merge: mergeGen,
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -14,6 +13,8 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
@@ -30,7 +31,7 @@ func TestMatrixGenerate(t *testing.T) {
|
||||
}
|
||||
|
||||
listGenerator := &argoprojiov1alpha1.ListGenerator{
|
||||
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url"}`)}},
|
||||
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url", "templated": "test-{{path.basenameNormalized}}"}`)}},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
@@ -50,8 +51,8 @@ func TestMatrixGenerate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
expected: []map[string]interface{}{
|
||||
{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "cluster": "Cluster", "url": "Url"},
|
||||
{"path": "app2", "path.basename": "app2", "path.basenameNormalized": "app2", "cluster": "Cluster", "url": "Url"},
|
||||
{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "cluster": "Cluster", "url": "Url", "templated": "test-app1"},
|
||||
{"path": "app2", "path.basename": "app2", "path.basenameNormalized": "app2", "cluster": "Cluster", "url": "Url", "templated": "test-app2"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -848,7 +849,7 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
|
||||
}
|
||||
|
||||
listGenerator := &argoprojiov1alpha1.ListGenerator{
|
||||
Elements: []apiextensionsv1.JSON{},
|
||||
Elements: []apiextensionsv1.JSON{},
|
||||
ElementsYaml: "{{ .foo.bar | toJson }}",
|
||||
}
|
||||
|
||||
@@ -870,60 +871,59 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
|
||||
},
|
||||
expected: []map[string]interface{}{
|
||||
{
|
||||
"chart": "a",
|
||||
"version": "1",
|
||||
"chart": "a",
|
||||
"version": "1",
|
||||
"foo": map[string]interface{}{
|
||||
"bar": []interface{}{
|
||||
map[string]interface{}{
|
||||
"chart": "a",
|
||||
"chart": "a",
|
||||
"version": "1",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"chart": "b",
|
||||
"chart": "b",
|
||||
"version": "2",
|
||||
},
|
||||
},
|
||||
},
|
||||
"path": map[string]interface{}{
|
||||
"basename": "dir",
|
||||
"basename": "dir",
|
||||
"basenameNormalized": "dir",
|
||||
"filename": "file_name.yaml",
|
||||
"filename": "file_name.yaml",
|
||||
"filenameNormalized": "file-name.yaml",
|
||||
"path": "path/dir",
|
||||
"segments": []string {
|
||||
"path": "path/dir",
|
||||
"segments": []string{
|
||||
"path",
|
||||
"dir",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"chart": "b",
|
||||
"version": "2",
|
||||
"chart": "b",
|
||||
"version": "2",
|
||||
"foo": map[string]interface{}{
|
||||
"bar": []interface{}{
|
||||
map[string]interface{}{
|
||||
"chart": "a",
|
||||
"chart": "a",
|
||||
"version": "1",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"chart": "b",
|
||||
"chart": "b",
|
||||
"version": "2",
|
||||
},
|
||||
},
|
||||
},
|
||||
"path": map[string]interface{}{
|
||||
"basename": "dir",
|
||||
"basename": "dir",
|
||||
"basenameNormalized": "dir",
|
||||
"filename": "file_name.yaml",
|
||||
"filename": "file_name.yaml",
|
||||
"filenameNormalized": "file-name.yaml",
|
||||
"path": "path/dir",
|
||||
"segments": []string {
|
||||
"path": "path/dir",
|
||||
"segments": []string{
|
||||
"path",
|
||||
"dir",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -952,27 +952,26 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
|
||||
"foo": map[string]interface{}{
|
||||
"bar": []interface{}{
|
||||
map[string]interface{}{
|
||||
"chart": "a",
|
||||
"chart": "a",
|
||||
"version": "1",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"chart": "b",
|
||||
"chart": "b",
|
||||
"version": "2",
|
||||
},
|
||||
},
|
||||
},
|
||||
"path": map[string]interface{}{
|
||||
"basename": "dir",
|
||||
"basename": "dir",
|
||||
"basenameNormalized": "dir",
|
||||
"filename": "file_name.yaml",
|
||||
"filename": "file_name.yaml",
|
||||
"filenameNormalized": "file-name.yaml",
|
||||
"path": "path/dir",
|
||||
"segments": []string {
|
||||
"path": "path/dir",
|
||||
"segments": []string{
|
||||
"path",
|
||||
"dir",
|
||||
},
|
||||
},
|
||||
|
||||
}}, nil)
|
||||
genMock.On("GetTemplate", &gitGeneratorSpec).
|
||||
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
|
||||
@@ -1055,7 +1054,7 @@ func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
|
||||
}
|
||||
|
||||
repoServiceMock := &mocks.Repos{}
|
||||
repoServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(map[string][]byte{
|
||||
repoServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(map[string][]byte{
|
||||
"some/path.json": []byte("test: content"),
|
||||
}, nil)
|
||||
gitGenerator := NewGitGenerator(repoServiceMock)
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/utils"
|
||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var _ Generator = (*MergeGenerator)(nil)
|
||||
@@ -141,10 +143,22 @@ func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Applic
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if matrixGen != nil && !appSet.Spec.ApplyNestedSelectors {
|
||||
foundSelector := dropDisabledNestedSelectors(matrixGen.Generators)
|
||||
if foundSelector {
|
||||
log.Warnf("AppSet '%v' defines selector on nested matrix generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selector", appSet.Name)
|
||||
}
|
||||
}
|
||||
mergeGen, err := getMergeGenerator(appSetBaseGenerator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mergeGen != nil && !appSet.Spec.ApplyNestedSelectors {
|
||||
foundSelector := dropDisabledNestedSelectors(mergeGen.Generators)
|
||||
if foundSelector {
|
||||
log.Warnf("AppSet '%v' defines selector on nested merge generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selector", appSet.Name)
|
||||
}
|
||||
}
|
||||
|
||||
t, err := Transform(
|
||||
argoprojiov1alpha1.ApplicationSetGenerator{
|
||||
@@ -154,6 +168,7 @@ func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Applic
|
||||
SCMProvider: appSetBaseGenerator.SCMProvider,
|
||||
ClusterDecisionResource: appSetBaseGenerator.ClusterDecisionResource,
|
||||
PullRequest: appSetBaseGenerator.PullRequest,
|
||||
Plugin: appSetBaseGenerator.Plugin,
|
||||
Matrix: matrixGen,
|
||||
Merge: mergeGen,
|
||||
Selector: appSetBaseGenerator.Selector,
|
||||
@@ -190,6 +205,7 @@ func (m *MergeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.App
|
||||
Clusters: r.Clusters,
|
||||
Git: r.Git,
|
||||
PullRequest: r.PullRequest,
|
||||
Plugin: r.Plugin,
|
||||
Matrix: matrixGen,
|
||||
Merge: mergeGen,
|
||||
}
|
||||
|
||||
211
applicationset/generators/plugin.go
Normal file
211
applicationset/generators/plugin.go
Normal file
@@ -0,0 +1,211 @@
|
||||
package generators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/util/settings"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/services/plugin"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultPluginRequeueAfterSeconds = 30 * time.Minute
|
||||
)
|
||||
|
||||
var _ Generator = (*PluginGenerator)(nil)
|
||||
|
||||
type PluginGenerator struct {
|
||||
client client.Client
|
||||
ctx context.Context
|
||||
clientset kubernetes.Interface
|
||||
namespace string
|
||||
}
|
||||
|
||||
func NewPluginGenerator(client client.Client, ctx context.Context, clientset kubernetes.Interface, namespace string) Generator {
|
||||
g := &PluginGenerator{
|
||||
client: client,
|
||||
ctx: ctx,
|
||||
clientset: clientset,
|
||||
namespace: namespace,
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *PluginGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
|
||||
// Return a requeue default of 30 minutes, if no default is specified.
|
||||
|
||||
if appSetGenerator.Plugin.RequeueAfterSeconds != nil {
|
||||
return time.Duration(*appSetGenerator.Plugin.RequeueAfterSeconds) * time.Second
|
||||
}
|
||||
|
||||
return DefaultPluginRequeueAfterSeconds
|
||||
}
|
||||
|
||||
func (g *PluginGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
|
||||
return &appSetGenerator.Plugin.Template
|
||||
}
|
||||
|
||||
func (g *PluginGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
|
||||
|
||||
if appSetGenerator == nil {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
}
|
||||
|
||||
if appSetGenerator.Plugin == nil {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
providerConfig := appSetGenerator.Plugin
|
||||
|
||||
pluginClient, err := g.getPluginFromGenerator(ctx, applicationSetInfo.Name, providerConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list, err := pluginClient.List(ctx, providerConfig.Input.Parameters)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listing params: %w", err)
|
||||
}
|
||||
|
||||
res, err := g.generateParams(appSetGenerator, applicationSetInfo, list.Output.Parameters, appSetGenerator.Plugin.Input.Parameters, applicationSetInfo.Spec.GoTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (g *PluginGenerator) getPluginFromGenerator(ctx context.Context, appSetName string, generatorConfig *argoprojiov1alpha1.PluginGenerator) (*plugin.Service, error) {
|
||||
cm, err := g.getConfigMap(ctx, generatorConfig.ConfigMapRef.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching ConfigMap: %w", err)
|
||||
}
|
||||
token, err := g.getToken(ctx, cm["token"])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching Secret token: %v", err)
|
||||
}
|
||||
|
||||
var requestTimeout int
|
||||
requestTimeoutStr, ok := cm["requestTimeout"]
|
||||
if ok {
|
||||
requestTimeout, err = strconv.Atoi(requestTimeoutStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error set requestTimeout : %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
pluginClient, err := plugin.NewPluginService(ctx, appSetName, cm["baseUrl"], token, requestTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pluginClient, nil
|
||||
}
|
||||
|
||||
func (g *PluginGenerator) generateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, objectsFound []map[string]interface{}, pluginParams argoprojiov1alpha1.PluginParameters, useGoTemplate bool) ([]map[string]interface{}, error) {
|
||||
res := []map[string]interface{}{}
|
||||
|
||||
for _, objectFound := range objectsFound {
|
||||
|
||||
params := map[string]interface{}{}
|
||||
|
||||
if useGoTemplate {
|
||||
for k, v := range objectFound {
|
||||
params[k] = v
|
||||
}
|
||||
} else {
|
||||
flat, err := flatten.Flatten(objectFound, "", flatten.DotStyle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range flat {
|
||||
params[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
}
|
||||
|
||||
params["generator"] = map[string]interface{}{
|
||||
"input": map[string]argoprojiov1alpha1.PluginParameters{
|
||||
"parameters": pluginParams,
|
||||
},
|
||||
}
|
||||
|
||||
err := appendTemplatedValues(appSetGenerator.Plugin.Values, params, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = append(res, params)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (g *PluginGenerator) getToken(ctx context.Context, tokenRef string) (string, error) {
|
||||
|
||||
if tokenRef == "" || !strings.HasPrefix(tokenRef, "$") {
|
||||
return "", fmt.Errorf("token is empty, or does not reference a secret key starting with '$': %v", tokenRef)
|
||||
}
|
||||
|
||||
secretName, tokenKey := plugin.ParseSecretKey(tokenRef)
|
||||
|
||||
secret := &corev1.Secret{}
|
||||
err := g.client.Get(
|
||||
ctx,
|
||||
client.ObjectKey{
|
||||
Name: secretName,
|
||||
Namespace: g.namespace,
|
||||
},
|
||||
secret)
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error fetching secret %s/%s: %v", g.namespace, secretName, err)
|
||||
}
|
||||
|
||||
secretValues := make(map[string]string, len(secret.Data))
|
||||
|
||||
for k, v := range secret.Data {
|
||||
secretValues[k] = string(v)
|
||||
}
|
||||
|
||||
token := settings.ReplaceStringSecret(tokenKey, secretValues)
|
||||
|
||||
return token, err
|
||||
}
|
||||
|
||||
func (g *PluginGenerator) getConfigMap(ctx context.Context, configMapRef string) (map[string]string, error) {
|
||||
cm := &corev1.ConfigMap{}
|
||||
err := g.client.Get(
|
||||
ctx,
|
||||
client.ObjectKey{
|
||||
Name: configMapRef,
|
||||
Namespace: g.namespace,
|
||||
},
|
||||
cm)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
baseUrl, ok := cm.Data["baseUrl"]
|
||||
if !ok || baseUrl == "" {
|
||||
return nil, fmt.Errorf("baseUrl not found in ConfigMap")
|
||||
}
|
||||
|
||||
token, ok := cm.Data["token"]
|
||||
if !ok || token == "" {
|
||||
return nil, fmt.Errorf("token not found in ConfigMap")
|
||||
}
|
||||
|
||||
return cm.Data, nil
|
||||
}
|
||||
705
applicationset/generators/plugin_test.go
Normal file
705
applicationset/generators/plugin_test.go
Normal file
@@ -0,0 +1,705 @@
|
||||
package generators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
v1 "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"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/services/plugin"
|
||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
|
||||
func TestPluginGenerateParams(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
configmap *v1.ConfigMap
|
||||
secret *v1.Secret
|
||||
inputParameters map[string]apiextensionsv1.JSON
|
||||
values map[string]string
|
||||
gotemplate bool
|
||||
expected []map[string]interface{}
|
||||
content []byte
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "simple case",
|
||||
configmap: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "first-plugin-cm",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"baseUrl": "http://127.0.0.1",
|
||||
"token": "$plugin.token",
|
||||
},
|
||||
},
|
||||
secret: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "argocd-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"plugin.token": []byte("my-secret"),
|
||||
},
|
||||
},
|
||||
inputParameters: map[string]apiextensionsv1.JSON{
|
||||
"pkey1": {Raw: []byte(`"val1"`)},
|
||||
"pkey2": {Raw: []byte(`"val2"`)},
|
||||
},
|
||||
gotemplate: false,
|
||||
content: []byte(`{"output": {
|
||||
"parameters": [{
|
||||
"key1": "val1",
|
||||
"key2": {
|
||||
"key2_1": "val2_1",
|
||||
"key2_2": {
|
||||
"key2_2_1": "val2_2_1"
|
||||
}
|
||||
},
|
||||
"key3": 123
|
||||
}]
|
||||
}}`),
|
||||
expected: []map[string]interface{}{
|
||||
{
|
||||
"key1": "val1",
|
||||
"key2.key2_1": "val2_1",
|
||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
||||
"key3": "123",
|
||||
"generator": map[string]interface{}{
|
||||
"input": argoprojiov1alpha1.PluginInput{
|
||||
Parameters: argoprojiov1alpha1.PluginParameters{
|
||||
"pkey1": {Raw: []byte(`"val1"`)},
|
||||
"pkey2": {Raw: []byte(`"val2"`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "simple case with values",
|
||||
configmap: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "first-plugin-cm",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"baseUrl": "http://127.0.0.1",
|
||||
"token": "$plugin.token",
|
||||
},
|
||||
},
|
||||
secret: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "argocd-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"plugin.token": []byte("my-secret"),
|
||||
},
|
||||
},
|
||||
inputParameters: map[string]apiextensionsv1.JSON{
|
||||
"pkey1": {Raw: []byte(`"val1"`)},
|
||||
"pkey2": {Raw: []byte(`"val2"`)},
|
||||
},
|
||||
values: map[string]string{
|
||||
"valuekey1": "valuevalue1",
|
||||
"valuekey2": "templated-{{key1}}",
|
||||
},
|
||||
gotemplate: false,
|
||||
content: []byte(`{"output": {
|
||||
"parameters": [{
|
||||
"key1": "val1",
|
||||
"key2": {
|
||||
"key2_1": "val2_1",
|
||||
"key2_2": {
|
||||
"key2_2_1": "val2_2_1"
|
||||
}
|
||||
},
|
||||
"key3": 123
|
||||
}]
|
||||
}}`),
|
||||
expected: []map[string]interface{}{
|
||||
{
|
||||
"key1": "val1",
|
||||
"key2.key2_1": "val2_1",
|
||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
||||
"key3": "123",
|
||||
"values.valuekey1": "valuevalue1",
|
||||
"values.valuekey2": "templated-val1",
|
||||
"generator": map[string]interface{}{
|
||||
"input": argoprojiov1alpha1.PluginInput{
|
||||
Parameters: argoprojiov1alpha1.PluginParameters{
|
||||
"pkey1": {Raw: []byte(`"val1"`)},
|
||||
"pkey2": {Raw: []byte(`"val2"`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "simple case with gotemplate",
|
||||
configmap: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "first-plugin-cm",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"baseUrl": "http://127.0.0.1",
|
||||
"token": "$plugin.token",
|
||||
},
|
||||
},
|
||||
secret: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "argocd-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"plugin.token": []byte("my-secret"),
|
||||
},
|
||||
},
|
||||
inputParameters: map[string]apiextensionsv1.JSON{
|
||||
"pkey1": {Raw: []byte(`"val1"`)},
|
||||
"pkey2": {Raw: []byte(`"val2"`)},
|
||||
},
|
||||
gotemplate: true,
|
||||
content: []byte(`{"output": {
|
||||
"parameters": [{
|
||||
"key1": "val1",
|
||||
"key2": {
|
||||
"key2_1": "val2_1",
|
||||
"key2_2": {
|
||||
"key2_2_1": "val2_2_1"
|
||||
}
|
||||
},
|
||||
"key3": 123
|
||||
}]
|
||||
}}`),
|
||||
expected: []map[string]interface{}{
|
||||
{
|
||||
"key1": "val1",
|
||||
"key2": map[string]interface{}{
|
||||
"key2_1": "val2_1",
|
||||
"key2_2": map[string]interface{}{
|
||||
"key2_2_1": "val2_2_1",
|
||||
},
|
||||
},
|
||||
"key3": float64(123),
|
||||
"generator": map[string]interface{}{
|
||||
"input": argoprojiov1alpha1.PluginInput{
|
||||
Parameters: argoprojiov1alpha1.PluginParameters{
|
||||
"pkey1": {Raw: []byte(`"val1"`)},
|
||||
"pkey2": {Raw: []byte(`"val2"`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "simple case with appended params",
|
||||
configmap: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "first-plugin-cm",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"baseUrl": "http://127.0.0.1",
|
||||
"token": "$plugin.token",
|
||||
},
|
||||
},
|
||||
secret: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "argocd-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"plugin.token": []byte("my-secret"),
|
||||
},
|
||||
},
|
||||
inputParameters: map[string]apiextensionsv1.JSON{
|
||||
"pkey1": {Raw: []byte(`"val1"`)},
|
||||
"pkey2": {Raw: []byte(`"val2"`)},
|
||||
},
|
||||
gotemplate: false,
|
||||
content: []byte(`{"output": {"parameters": [{
|
||||
"key1": "val1",
|
||||
"key2": {
|
||||
"key2_1": "val2_1",
|
||||
"key2_2": {
|
||||
"key2_2_1": "val2_2_1"
|
||||
}
|
||||
},
|
||||
"key3": 123,
|
||||
"pkey2": "valplugin"
|
||||
}]}}`),
|
||||
expected: []map[string]interface{}{
|
||||
{
|
||||
"key1": "val1",
|
||||
"key2.key2_1": "val2_1",
|
||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
||||
"key3": "123",
|
||||
"pkey2": "valplugin",
|
||||
"generator": map[string]interface{}{
|
||||
"input": argoprojiov1alpha1.PluginInput{
|
||||
Parameters: argoprojiov1alpha1.PluginParameters{
|
||||
"pkey1": {Raw: []byte(`"val1"`)},
|
||||
"pkey2": {Raw: []byte(`"val2"`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "no params",
|
||||
configmap: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "first-plugin-cm",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"baseUrl": "http://127.0.0.1",
|
||||
"token": "$plugin.token",
|
||||
},
|
||||
},
|
||||
secret: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "argocd-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"plugin.token": []byte("my-secret"),
|
||||
},
|
||||
},
|
||||
inputParameters: argoprojiov1alpha1.PluginParameters{},
|
||||
gotemplate: false,
|
||||
content: []byte(`{"output": {
|
||||
"parameters": [{
|
||||
"key1": "val1",
|
||||
"key2": {
|
||||
"key2_1": "val2_1",
|
||||
"key2_2": {
|
||||
"key2_2_1": "val2_2_1"
|
||||
}
|
||||
},
|
||||
"key3": 123
|
||||
}]
|
||||
}}`),
|
||||
expected: []map[string]interface{}{
|
||||
{
|
||||
"key1": "val1",
|
||||
"key2.key2_1": "val2_1",
|
||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
||||
"key3": "123",
|
||||
"generator": map[string]interface{}{
|
||||
"input": map[string]map[string]interface{}{
|
||||
"parameters": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "empty return",
|
||||
configmap: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "first-plugin-cm",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"baseUrl": "http://127.0.0.1",
|
||||
"token": "$plugin.token",
|
||||
},
|
||||
},
|
||||
secret: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "argocd-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"plugin.token": []byte("my-secret"),
|
||||
},
|
||||
},
|
||||
inputParameters: map[string]apiextensionsv1.JSON{},
|
||||
gotemplate: false,
|
||||
content: []byte(`{"input": {"parameters": []}}`),
|
||||
expected: []map[string]interface{}{},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "wrong return",
|
||||
configmap: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "first-plugin-cm",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"baseUrl": "http://127.0.0.1",
|
||||
"token": "$plugin.token",
|
||||
},
|
||||
},
|
||||
secret: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "argocd-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"plugin.token": []byte("my-secret"),
|
||||
},
|
||||
},
|
||||
inputParameters: map[string]apiextensionsv1.JSON{},
|
||||
gotemplate: false,
|
||||
content: []byte(`wrong body ...`),
|
||||
expected: []map[string]interface{}{},
|
||||
expectedError: fmt.Errorf("error listing params: error get api 'set': invalid character 'w' looking for beginning of value: wrong body ..."),
|
||||
},
|
||||
{
|
||||
name: "external secret",
|
||||
configmap: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "first-plugin-cm",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"baseUrl": "http://127.0.0.1",
|
||||
"token": "$plugin-secret:plugin.token",
|
||||
},
|
||||
},
|
||||
secret: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "plugin-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"plugin.token": []byte("my-secret"),
|
||||
},
|
||||
},
|
||||
inputParameters: map[string]apiextensionsv1.JSON{
|
||||
"pkey1": {Raw: []byte(`"val1"`)},
|
||||
"pkey2": {Raw: []byte(`"val2"`)},
|
||||
},
|
||||
gotemplate: false,
|
||||
content: []byte(`{"output": {"parameters": [{
|
||||
"key1": "val1",
|
||||
"key2": {
|
||||
"key2_1": "val2_1",
|
||||
"key2_2": {
|
||||
"key2_2_1": "val2_2_1"
|
||||
}
|
||||
},
|
||||
"key3": 123,
|
||||
"pkey2": "valplugin"
|
||||
}]}}`),
|
||||
expected: []map[string]interface{}{
|
||||
{
|
||||
"key1": "val1",
|
||||
"key2.key2_1": "val2_1",
|
||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
||||
"key3": "123",
|
||||
"pkey2": "valplugin",
|
||||
"generator": map[string]interface{}{
|
||||
"input": argoprojiov1alpha1.PluginInput{
|
||||
Parameters: argoprojiov1alpha1.PluginParameters{
|
||||
"pkey1": {Raw: []byte(`"val1"`)},
|
||||
"pkey2": {Raw: []byte(`"val2"`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "no secret",
|
||||
configmap: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "first-plugin-cm",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"baseUrl": "http://127.0.0.1",
|
||||
"token": "$plugin.token",
|
||||
},
|
||||
},
|
||||
secret: &v1.Secret{},
|
||||
inputParameters: map[string]apiextensionsv1.JSON{
|
||||
"pkey1": {Raw: []byte(`"val1"`)},
|
||||
"pkey2": {Raw: []byte(`"val2"`)},
|
||||
},
|
||||
gotemplate: false,
|
||||
content: []byte(`{"output": {
|
||||
"parameters": [{
|
||||
"key1": "val1",
|
||||
"key2": {
|
||||
"key2_1": "val2_1",
|
||||
"key2_2": {
|
||||
"key2_2_1": "val2_2_1"
|
||||
}
|
||||
},
|
||||
"key3": 123
|
||||
}]
|
||||
}}`),
|
||||
expected: []map[string]interface{}{
|
||||
{
|
||||
"key1": "val1",
|
||||
"key2.key2_1": "val2_1",
|
||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
||||
"key3": "123",
|
||||
"generator": map[string]interface{}{
|
||||
"input": argoprojiov1alpha1.PluginInput{
|
||||
Parameters: argoprojiov1alpha1.PluginParameters{
|
||||
"pkey1": {Raw: []byte(`"val1"`)},
|
||||
"pkey2": {Raw: []byte(`"val2"`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: fmt.Errorf("error fetching Secret token: error fetching secret default/argocd-secret: secrets \"argocd-secret\" not found"),
|
||||
},
|
||||
{
|
||||
name: "no configmap",
|
||||
configmap: &v1.ConfigMap{},
|
||||
secret: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "argocd-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"plugin.token": []byte("my-secret"),
|
||||
},
|
||||
},
|
||||
inputParameters: map[string]apiextensionsv1.JSON{
|
||||
"pkey1": {Raw: []byte(`"val1"`)},
|
||||
"pkey2": {Raw: []byte(`"val2"`)},
|
||||
},
|
||||
gotemplate: false,
|
||||
content: []byte(`{"output": {
|
||||
"parameters": [{
|
||||
"key1": "val1",
|
||||
"key2": {
|
||||
"key2_1": "val2_1",
|
||||
"key2_2": {
|
||||
"key2_2_1": "val2_2_1"
|
||||
}
|
||||
},
|
||||
"key3": 123
|
||||
}]
|
||||
}}`),
|
||||
expected: []map[string]interface{}{
|
||||
{
|
||||
"key1": "val1",
|
||||
"key2.key2_1": "val2_1",
|
||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
||||
"key3": "123",
|
||||
"generator": map[string]interface{}{
|
||||
"input": argoprojiov1alpha1.PluginInput{
|
||||
Parameters: argoprojiov1alpha1.PluginParameters{
|
||||
"pkey1": {Raw: []byte(`"val1"`)},
|
||||
"pkey2": {Raw: []byte(`"val2"`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: fmt.Errorf("error fetching ConfigMap: configmaps \"\" not found"),
|
||||
},
|
||||
{
|
||||
name: "no baseUrl",
|
||||
configmap: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "first-plugin-cm",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"token": "$plugin.token",
|
||||
},
|
||||
},
|
||||
secret: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "argocd-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"plugin.token": []byte("my-secret"),
|
||||
},
|
||||
},
|
||||
inputParameters: map[string]apiextensionsv1.JSON{
|
||||
"pkey1": {Raw: []byte(`"val1"`)},
|
||||
"pkey2": {Raw: []byte(`"val2"`)},
|
||||
},
|
||||
gotemplate: false,
|
||||
content: []byte(`{"output": {
|
||||
"parameters": [{
|
||||
"key1": "val1",
|
||||
"key2": {
|
||||
"key2_1": "val2_1",
|
||||
"key2_2": {
|
||||
"key2_2_1": "val2_2_1"
|
||||
}
|
||||
},
|
||||
"key3": 123
|
||||
}]
|
||||
}}`),
|
||||
expected: []map[string]interface{}{
|
||||
{
|
||||
"key1": "val1",
|
||||
"key2.key2_1": "val2_1",
|
||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
||||
"key3": "123",
|
||||
"generator": map[string]interface{}{
|
||||
"input": argoprojiov1alpha1.PluginInput{
|
||||
Parameters: argoprojiov1alpha1.PluginParameters{
|
||||
"pkey1": {Raw: []byte(`"val1"`)},
|
||||
"pkey2": {Raw: []byte(`"val2"`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: fmt.Errorf("error fetching ConfigMap: baseUrl not found in ConfigMap"),
|
||||
},
|
||||
{
|
||||
name: "no token",
|
||||
configmap: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "first-plugin-cm",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"baseUrl": "http://127.0.0.1",
|
||||
},
|
||||
},
|
||||
secret: &v1.Secret{},
|
||||
inputParameters: map[string]apiextensionsv1.JSON{
|
||||
"pkey1": {Raw: []byte(`"val1"`)},
|
||||
"pkey2": {Raw: []byte(`"val2"`)},
|
||||
},
|
||||
gotemplate: false,
|
||||
content: []byte(`{"output": {
|
||||
"parameters": [{
|
||||
"key1": "val1",
|
||||
"key2": {
|
||||
"key2_1": "val2_1",
|
||||
"key2_2": {
|
||||
"key2_2_1": "val2_2_1"
|
||||
}
|
||||
},
|
||||
"key3": 123
|
||||
}]
|
||||
}}`),
|
||||
expected: []map[string]interface{}{
|
||||
{
|
||||
"key1": "val1",
|
||||
"key2.key2_1": "val2_1",
|
||||
"key2.key2_2.key2_2_1": "val2_2_1",
|
||||
"key3": "123",
|
||||
"generator": map[string]interface{}{
|
||||
"input": argoprojiov1alpha1.PluginInput{
|
||||
Parameters: argoprojiov1alpha1.PluginParameters{
|
||||
"pkey1": {Raw: []byte(`"val1"`)},
|
||||
"pkey2": {Raw: []byte(`"val2"`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: fmt.Errorf("error fetching ConfigMap: token not found in ConfigMap"),
|
||||
},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
for _, testCase := range testCases {
|
||||
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
|
||||
generatorConfig := argoprojiov1alpha1.ApplicationSetGenerator{
|
||||
Plugin: &argoprojiov1alpha1.PluginGenerator{
|
||||
ConfigMapRef: argoprojiov1alpha1.PluginConfigMapRef{Name: testCase.configmap.Name},
|
||||
Input: argoprojiov1alpha1.PluginInput{
|
||||
Parameters: testCase.inputParameters,
|
||||
},
|
||||
Values: testCase.values,
|
||||
},
|
||||
}
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
_, tokenKey := plugin.ParseSecretKey(testCase.configmap.Data["token"])
|
||||
expectedToken := testCase.secret.Data[strings.Replace(tokenKey, "$", "", -1)]
|
||||
if authHeader != "Bearer "+string(expectedToken) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err := w.Write(testCase.content)
|
||||
if err != nil {
|
||||
assert.NoError(t, fmt.Errorf("Error Write %v", err))
|
||||
}
|
||||
})
|
||||
|
||||
fakeServer := httptest.NewServer(handler)
|
||||
|
||||
defer fakeServer.Close()
|
||||
|
||||
if _, ok := testCase.configmap.Data["baseUrl"]; ok {
|
||||
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()
|
||||
|
||||
var pluginGenerator = NewPluginGenerator(fakeClientWithCache, ctx, fakeClient, "default")
|
||||
|
||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "set",
|
||||
},
|
||||
Spec: argoprojiov1alpha1.ApplicationSetSpec{
|
||||
GoTemplate: testCase.gotemplate,
|
||||
},
|
||||
}
|
||||
|
||||
got, err := pluginGenerator.GenerateParams(&generatorConfig, &applicationSetInfo)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
if testCase.expectedError != nil {
|
||||
assert.EqualError(t, err, testCase.expectedError.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
expectedJson, err := json.Marshal(testCase.expected)
|
||||
require.NoError(t, err)
|
||||
gotJson, err := json.Marshal(got)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, string(expectedJson), string(gotJson))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -25,12 +25,16 @@ type PullRequestGenerator struct {
|
||||
client client.Client
|
||||
selectServiceProviderFunc func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error)
|
||||
auth SCMAuthProviders
|
||||
scmRootCAPath string
|
||||
allowedSCMProviders []string
|
||||
}
|
||||
|
||||
func NewPullRequestGenerator(client client.Client, auth SCMAuthProviders) Generator {
|
||||
func NewPullRequestGenerator(client client.Client, auth SCMAuthProviders, scmRootCAPath string, allowedScmProviders []string) Generator {
|
||||
g := &PullRequestGenerator{
|
||||
client: client,
|
||||
auth: auth,
|
||||
client: client,
|
||||
auth: auth,
|
||||
scmRootCAPath: scmRootCAPath,
|
||||
allowedSCMProviders: allowedScmProviders,
|
||||
}
|
||||
g.selectServiceProviderFunc = g.selectServiceProvider
|
||||
return g
|
||||
@@ -96,12 +100,14 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
|
||||
}
|
||||
|
||||
paramMap := map[string]interface{}{
|
||||
"number": strconv.Itoa(pull.Number),
|
||||
"branch": pull.Branch,
|
||||
"branch_slug": slug.Make(pull.Branch),
|
||||
"head_sha": pull.HeadSHA,
|
||||
"head_short_sha": pull.HeadSHA[:shortSHALength],
|
||||
"head_short_sha_7": pull.HeadSHA[:shortSHALength7],
|
||||
"number": strconv.Itoa(pull.Number),
|
||||
"branch": pull.Branch,
|
||||
"branch_slug": slug.Make(pull.Branch),
|
||||
"target_branch": pull.TargetBranch,
|
||||
"target_branch_slug": slug.Make(pull.TargetBranch),
|
||||
"head_sha": pull.HeadSHA,
|
||||
"head_short_sha": pull.HeadSHA[:shortSHALength],
|
||||
"head_short_sha_7": pull.HeadSHA[:shortSHALength7],
|
||||
}
|
||||
|
||||
// PR lables will only be supported for Go Template appsets, since fasttemplate will be deprecated.
|
||||
@@ -116,18 +122,27 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
|
||||
// selectServiceProvider selects the provider to get pull requests from the configuration
|
||||
func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, generatorConfig *argoprojiov1alpha1.PullRequestGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
|
||||
if generatorConfig.Github != nil {
|
||||
if !ScmProviderAllowed(applicationSetInfo, generatorConfig.Github.API, g.allowedSCMProviders) {
|
||||
return nil, fmt.Errorf("scm provider not allowed: %s", generatorConfig.Github.API)
|
||||
}
|
||||
return g.github(ctx, generatorConfig.Github, applicationSetInfo)
|
||||
}
|
||||
if generatorConfig.GitLab != nil {
|
||||
providerConfig := generatorConfig.GitLab
|
||||
if !ScmProviderAllowed(applicationSetInfo, providerConfig.API, g.allowedSCMProviders) {
|
||||
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.API)
|
||||
}
|
||||
token, err := g.getSecretRef(ctx, providerConfig.TokenRef, applicationSetInfo.Namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching Secret token: %v", err)
|
||||
}
|
||||
return pullrequest.NewGitLabService(ctx, token, providerConfig.API, providerConfig.Project, providerConfig.Labels, providerConfig.PullRequestState)
|
||||
return pullrequest.NewGitLabService(ctx, token, providerConfig.API, providerConfig.Project, providerConfig.Labels, providerConfig.PullRequestState, g.scmRootCAPath, providerConfig.Insecure)
|
||||
}
|
||||
if generatorConfig.Gitea != nil {
|
||||
providerConfig := generatorConfig.Gitea
|
||||
if !ScmProviderAllowed(applicationSetInfo, providerConfig.API, g.allowedSCMProviders) {
|
||||
return nil, fmt.Errorf("scm provider not allowed: %s", generatorConfig.Gitea.API)
|
||||
}
|
||||
token, err := g.getSecretRef(ctx, providerConfig.TokenRef, applicationSetInfo.Namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching Secret token: %v", err)
|
||||
@@ -136,6 +151,9 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
|
||||
}
|
||||
if generatorConfig.BitbucketServer != nil {
|
||||
providerConfig := generatorConfig.BitbucketServer
|
||||
if !ScmProviderAllowed(applicationSetInfo, providerConfig.API, g.allowedSCMProviders) {
|
||||
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.API)
|
||||
}
|
||||
if providerConfig.BasicAuth != nil {
|
||||
password, err := g.getSecretRef(ctx, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace)
|
||||
if err != nil {
|
||||
@@ -146,6 +164,32 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
|
||||
return pullrequest.NewBitbucketServiceNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.Repo)
|
||||
}
|
||||
}
|
||||
if generatorConfig.Bitbucket != nil {
|
||||
providerConfig := generatorConfig.Bitbucket
|
||||
if providerConfig.BearerToken != nil {
|
||||
appToken, err := g.getSecretRef(ctx, providerConfig.BearerToken.TokenRef, applicationSetInfo.Namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching Secret Bearer token: %v", err)
|
||||
}
|
||||
return pullrequest.NewBitbucketCloudServiceBearerToken(providerConfig.API, appToken, providerConfig.Owner, providerConfig.Repo)
|
||||
} else if providerConfig.BasicAuth != nil {
|
||||
password, err := g.getSecretRef(ctx, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching Secret token: %v", err)
|
||||
}
|
||||
return pullrequest.NewBitbucketCloudServiceBasicAuth(providerConfig.API, providerConfig.BasicAuth.Username, password, providerConfig.Owner, providerConfig.Repo)
|
||||
} else {
|
||||
return pullrequest.NewBitbucketCloudServiceNoAuth(providerConfig.API, providerConfig.Owner, providerConfig.Repo)
|
||||
}
|
||||
}
|
||||
if generatorConfig.AzureDevOps != nil {
|
||||
providerConfig := generatorConfig.AzureDevOps
|
||||
token, err := g.getSecretRef(ctx, providerConfig.TokenRef, applicationSetInfo.Namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching Secret token: %v", err)
|
||||
}
|
||||
return pullrequest.NewAzureDevOpsService(ctx, token, providerConfig.API, providerConfig.Organization, providerConfig.Project, providerConfig.Repo, providerConfig.Labels)
|
||||
}
|
||||
return nil, fmt.Errorf("no Pull Request provider implementation configured")
|
||||
}
|
||||
|
||||
|
||||
@@ -28,9 +28,10 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
|
||||
ctx,
|
||||
[]*pullrequest.PullRequest{
|
||||
&pullrequest.PullRequest{
|
||||
Number: 1,
|
||||
Branch: "branch1",
|
||||
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
Number: 1,
|
||||
Branch: "branch1",
|
||||
TargetBranch: "master",
|
||||
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
@@ -38,12 +39,14 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
|
||||
},
|
||||
expected: []map[string]interface{}{
|
||||
{
|
||||
"number": "1",
|
||||
"branch": "branch1",
|
||||
"branch_slug": "branch1",
|
||||
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
"head_short_sha": "089d92cb",
|
||||
"head_short_sha_7": "089d92c",
|
||||
"number": "1",
|
||||
"branch": "branch1",
|
||||
"branch_slug": "branch1",
|
||||
"target_branch": "master",
|
||||
"target_branch_slug": "master",
|
||||
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
"head_short_sha": "089d92cb",
|
||||
"head_short_sha_7": "089d92c",
|
||||
},
|
||||
},
|
||||
expectedErr: nil,
|
||||
@@ -54,9 +57,10 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
|
||||
ctx,
|
||||
[]*pullrequest.PullRequest{
|
||||
&pullrequest.PullRequest{
|
||||
Number: 2,
|
||||
Branch: "feat/areally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
|
||||
HeadSHA: "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
|
||||
Number: 2,
|
||||
Branch: "feat/areally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
|
||||
TargetBranch: "feat/anotherreally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
|
||||
HeadSHA: "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
@@ -64,12 +68,14 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
|
||||
},
|
||||
expected: []map[string]interface{}{
|
||||
{
|
||||
"number": "2",
|
||||
"branch": "feat/areally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
|
||||
"branch_slug": "feat-areally-long-pull-request-name-to-test-argo",
|
||||
"head_sha": "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
|
||||
"head_short_sha": "9b34ff5b",
|
||||
"head_short_sha_7": "9b34ff5",
|
||||
"number": "2",
|
||||
"branch": "feat/areally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
|
||||
"branch_slug": "feat-areally-long-pull-request-name-to-test-argo",
|
||||
"target_branch": "feat/anotherreally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
|
||||
"target_branch_slug": "feat-anotherreally-long-pull-request-name-to-test",
|
||||
"head_sha": "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
|
||||
"head_short_sha": "9b34ff5b",
|
||||
"head_short_sha_7": "9b34ff5",
|
||||
},
|
||||
},
|
||||
expectedErr: nil,
|
||||
@@ -80,9 +86,10 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
|
||||
ctx,
|
||||
[]*pullrequest.PullRequest{
|
||||
&pullrequest.PullRequest{
|
||||
Number: 1,
|
||||
Branch: "a-very-short-sha",
|
||||
HeadSHA: "abcd",
|
||||
Number: 1,
|
||||
Branch: "a-very-short-sha",
|
||||
TargetBranch: "master",
|
||||
HeadSHA: "abcd",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
@@ -90,12 +97,14 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
|
||||
},
|
||||
expected: []map[string]interface{}{
|
||||
{
|
||||
"number": "1",
|
||||
"branch": "a-very-short-sha",
|
||||
"branch_slug": "a-very-short-sha",
|
||||
"head_sha": "abcd",
|
||||
"head_short_sha": "abcd",
|
||||
"head_short_sha_7": "abcd",
|
||||
"number": "1",
|
||||
"branch": "a-very-short-sha",
|
||||
"branch_slug": "a-very-short-sha",
|
||||
"target_branch": "master",
|
||||
"target_branch_slug": "master",
|
||||
"head_sha": "abcd",
|
||||
"head_short_sha": "abcd",
|
||||
"head_short_sha_7": "abcd",
|
||||
},
|
||||
},
|
||||
expectedErr: nil,
|
||||
@@ -117,10 +126,11 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
|
||||
ctx,
|
||||
[]*pullrequest.PullRequest{
|
||||
&pullrequest.PullRequest{
|
||||
Number: 1,
|
||||
Branch: "branch1",
|
||||
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
Labels: []string{"preview"},
|
||||
Number: 1,
|
||||
Branch: "branch1",
|
||||
TargetBranch: "master",
|
||||
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
Labels: []string{"preview"},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
@@ -128,13 +138,15 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
|
||||
},
|
||||
expected: []map[string]interface{}{
|
||||
{
|
||||
"number": "1",
|
||||
"branch": "branch1",
|
||||
"branch_slug": "branch1",
|
||||
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
"head_short_sha": "089d92cb",
|
||||
"head_short_sha_7": "089d92c",
|
||||
"labels": []string{"preview"},
|
||||
"number": "1",
|
||||
"branch": "branch1",
|
||||
"branch_slug": "branch1",
|
||||
"target_branch": "master",
|
||||
"target_branch_slug": "master",
|
||||
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
"head_short_sha": "089d92cb",
|
||||
"head_short_sha_7": "089d92c",
|
||||
"labels": []string{"preview"},
|
||||
},
|
||||
},
|
||||
expectedErr: nil,
|
||||
@@ -151,10 +163,11 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
|
||||
ctx,
|
||||
[]*pullrequest.PullRequest{
|
||||
&pullrequest.PullRequest{
|
||||
Number: 1,
|
||||
Branch: "branch1",
|
||||
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
Labels: []string{"preview"},
|
||||
Number: 1,
|
||||
Branch: "branch1",
|
||||
TargetBranch: "master",
|
||||
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
Labels: []string{"preview"},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
@@ -162,12 +175,14 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
|
||||
},
|
||||
expected: []map[string]interface{}{
|
||||
{
|
||||
"number": "1",
|
||||
"branch": "branch1",
|
||||
"branch_slug": "branch1",
|
||||
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
"head_short_sha": "089d92cb",
|
||||
"head_short_sha_7": "089d92c",
|
||||
"number": "1",
|
||||
"branch": "branch1",
|
||||
"branch_slug": "branch1",
|
||||
"target_branch": "master",
|
||||
"target_branch_slug": "master",
|
||||
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
"head_short_sha": "089d92cb",
|
||||
"head_short_sha_7": "089d92c",
|
||||
},
|
||||
},
|
||||
expectedErr: nil,
|
||||
@@ -258,3 +273,80 @@ func TestPullRequestGetSecretRef(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowedSCMProviderPullRequest(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
providerConfig *argoprojiov1alpha1.PullRequestGenerator
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "Error Github",
|
||||
providerConfig: &argoprojiov1alpha1.PullRequestGenerator{
|
||||
Github: &argoprojiov1alpha1.PullRequestGeneratorGithub{
|
||||
API: "https://myservice.mynamespace.svc.cluster.local",
|
||||
},
|
||||
},
|
||||
expectedError: "failed to select pull request service provider: scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
|
||||
},
|
||||
{
|
||||
name: "Error Gitlab",
|
||||
providerConfig: &argoprojiov1alpha1.PullRequestGenerator{
|
||||
GitLab: &argoprojiov1alpha1.PullRequestGeneratorGitLab{
|
||||
API: "https://myservice.mynamespace.svc.cluster.local",
|
||||
},
|
||||
},
|
||||
expectedError: "failed to select pull request service provider: scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
|
||||
},
|
||||
{
|
||||
name: "Error Gitea",
|
||||
providerConfig: &argoprojiov1alpha1.PullRequestGenerator{
|
||||
Gitea: &argoprojiov1alpha1.PullRequestGeneratorGitea{
|
||||
API: "https://myservice.mynamespace.svc.cluster.local",
|
||||
},
|
||||
},
|
||||
expectedError: "failed to select pull request service provider: scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
|
||||
},
|
||||
{
|
||||
name: "Error Bitbucket",
|
||||
providerConfig: &argoprojiov1alpha1.PullRequestGenerator{
|
||||
BitbucketServer: &argoprojiov1alpha1.PullRequestGeneratorBitbucketServer{
|
||||
API: "https://myservice.mynamespace.svc.cluster.local",
|
||||
},
|
||||
},
|
||||
expectedError: "failed to select pull request service provider: scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range cases {
|
||||
testCaseCopy := testCase
|
||||
|
||||
t.Run(testCaseCopy.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pullRequestGenerator := NewPullRequestGenerator(nil, SCMAuthProviders{}, "", []string{
|
||||
"github.myorg.com",
|
||||
"gitlab.myorg.com",
|
||||
"gitea.myorg.com",
|
||||
"bitbucket.myorg.com",
|
||||
"azuredevops.myorg.com",
|
||||
})
|
||||
|
||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "set",
|
||||
},
|
||||
Spec: argoprojiov1alpha1.ApplicationSetSpec{
|
||||
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
|
||||
PullRequest: testCaseCopy.providerConfig,
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := pullRequestGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
|
||||
|
||||
assert.Error(t, err, "Must return an error")
|
||||
assert.Equal(t, testCaseCopy.expectedError, err.Error())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,12 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/services/github_app_auth"
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider"
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/utils"
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
|
||||
@@ -26,16 +29,20 @@ type SCMProviderGenerator struct {
|
||||
// Testing hooks.
|
||||
overrideProvider scm_provider.SCMProviderService
|
||||
SCMAuthProviders
|
||||
scmRootCAPath string
|
||||
allowedSCMProviders []string
|
||||
}
|
||||
|
||||
type SCMAuthProviders struct {
|
||||
GitHubApps github_app_auth.Credentials
|
||||
}
|
||||
|
||||
func NewSCMProviderGenerator(client client.Client, providers SCMAuthProviders) Generator {
|
||||
func NewSCMProviderGenerator(client client.Client, providers SCMAuthProviders, scmRootCAPath string, allowedSCMProviders []string) Generator {
|
||||
return &SCMProviderGenerator{
|
||||
client: client,
|
||||
SCMAuthProviders: providers,
|
||||
client: client,
|
||||
SCMAuthProviders: providers,
|
||||
scmRootCAPath: scmRootCAPath,
|
||||
allowedSCMProviders: allowedSCMProviders,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +65,26 @@ func (g *SCMProviderGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.A
|
||||
return &appSetGenerator.SCMProvider.Template
|
||||
}
|
||||
|
||||
func ScmProviderAllowed(applicationSetInfo *argoprojiov1alpha1.ApplicationSet, url string, allowedScmProviders []string) bool {
|
||||
if url == "" || len(allowedScmProviders) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, allowedScmProvider := range allowedScmProviders {
|
||||
if url == allowedScmProvider {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
common.SecurityField: common.SecurityMedium,
|
||||
"applicationset": applicationSetInfo.Name,
|
||||
"appSetNamespace": applicationSetInfo.Namespace,
|
||||
}).Debugf("attempted to use disallowed SCM %q", url)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
|
||||
if appSetGenerator == nil {
|
||||
return nil, EmptyAppSetGeneratorError
|
||||
@@ -75,21 +102,30 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
|
||||
if g.overrideProvider != nil {
|
||||
provider = g.overrideProvider
|
||||
} else if providerConfig.Github != nil {
|
||||
if !ScmProviderAllowed(applicationSetInfo, providerConfig.Github.API, g.allowedSCMProviders) {
|
||||
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.Github.API)
|
||||
}
|
||||
var err error
|
||||
provider, err = g.githubProvider(ctx, providerConfig.Github, applicationSetInfo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("scm provider: %w", err)
|
||||
}
|
||||
} else if providerConfig.Gitlab != nil {
|
||||
if !ScmProviderAllowed(applicationSetInfo, providerConfig.Gitlab.API, g.allowedSCMProviders) {
|
||||
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.Gitlab.API)
|
||||
}
|
||||
token, err := g.getSecretRef(ctx, providerConfig.Gitlab.TokenRef, applicationSetInfo.Namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching Gitlab token: %v", err)
|
||||
}
|
||||
provider, err = scm_provider.NewGitlabProvider(ctx, providerConfig.Gitlab.Group, token, providerConfig.Gitlab.API, providerConfig.Gitlab.AllBranches, providerConfig.Gitlab.IncludeSubgroups)
|
||||
provider, err = scm_provider.NewGitlabProvider(ctx, providerConfig.Gitlab.Group, token, providerConfig.Gitlab.API, providerConfig.Gitlab.AllBranches, providerConfig.Gitlab.IncludeSubgroups, providerConfig.Gitlab.Insecure, g.scmRootCAPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error initializing Gitlab service: %v", err)
|
||||
}
|
||||
} else if providerConfig.Gitea != nil {
|
||||
if !ScmProviderAllowed(applicationSetInfo, providerConfig.Gitea.API, g.allowedSCMProviders) {
|
||||
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.Gitea.API)
|
||||
}
|
||||
token, err := g.getSecretRef(ctx, providerConfig.Gitea.TokenRef, applicationSetInfo.Namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching Gitea token: %v", err)
|
||||
@@ -100,6 +136,9 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
|
||||
}
|
||||
} else if providerConfig.BitbucketServer != nil {
|
||||
providerConfig := providerConfig.BitbucketServer
|
||||
if !ScmProviderAllowed(applicationSetInfo, providerConfig.API, g.allowedSCMProviders) {
|
||||
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.API)
|
||||
}
|
||||
var scmError error
|
||||
if providerConfig.BasicAuth != nil {
|
||||
password, err := g.getSecretRef(ctx, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace)
|
||||
@@ -114,6 +153,9 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
|
||||
return nil, fmt.Errorf("error initializing Bitbucket Server service: %v", scmError)
|
||||
}
|
||||
} else if providerConfig.AzureDevOps != nil {
|
||||
if !ScmProviderAllowed(applicationSetInfo, providerConfig.AzureDevOps.API, g.allowedSCMProviders) {
|
||||
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.AzureDevOps.API)
|
||||
}
|
||||
token, err := g.getSecretRef(ctx, providerConfig.AzureDevOps.AccessTokenRef, applicationSetInfo.Namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching Azure Devops access token: %v", err)
|
||||
@@ -131,6 +173,12 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error initializing Bitbucket cloud service: %v", err)
|
||||
}
|
||||
} else if providerConfig.AWSCodeCommit != nil {
|
||||
var awsErr error
|
||||
provider, awsErr = scm_provider.NewAWSCodeCommitProvider(ctx, providerConfig.AWSCodeCommit.TagFilters, providerConfig.AWSCodeCommit.Role, providerConfig.AWSCodeCommit.Region, providerConfig.AWSCodeCommit.AllBranches)
|
||||
if awsErr != nil {
|
||||
return nil, fmt.Errorf("error initializing AWS codecommit service: %v", awsErr)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("no SCM provider implementation configured")
|
||||
}
|
||||
@@ -140,7 +188,7 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listing repos: %v", err)
|
||||
}
|
||||
params := make([]map[string]interface{}, 0, len(repos))
|
||||
paramsArray := make([]map[string]interface{}, 0, len(repos))
|
||||
var shortSHALength int
|
||||
var shortSHALength7 int
|
||||
for _, repo := range repos {
|
||||
@@ -154,7 +202,7 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
|
||||
shortSHALength7 = len(repo.SHA)
|
||||
}
|
||||
|
||||
params = append(params, map[string]interface{}{
|
||||
params := map[string]interface{}{
|
||||
"organization": repo.Organization,
|
||||
"repository": repo.Repository,
|
||||
"url": repo.URL,
|
||||
@@ -164,9 +212,16 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
|
||||
"short_sha_7": repo.SHA[:shortSHALength7],
|
||||
"labels": strings.Join(repo.Labels, ","),
|
||||
"branchNormalized": utils.SanitizeName(repo.Branch),
|
||||
})
|
||||
}
|
||||
|
||||
err := appendTemplatedValues(appSetGenerator.SCMProvider.Values, params, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to append templated values: %w", err)
|
||||
}
|
||||
|
||||
paramsArray = append(paramsArray, params)
|
||||
}
|
||||
return params, nil
|
||||
return paramsArray, nil
|
||||
}
|
||||
|
||||
func (g *SCMProviderGenerator) getSecretRef(ctx context.Context, ref *argoprojiov1alpha1.SecretRef, namespace string) (string, error) {
|
||||
|
||||
@@ -80,40 +80,209 @@ func TestSCMProviderGetSecretRef(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSCMProviderGenerateParams(t *testing.T) {
|
||||
mockProvider := &scm_provider.MockProvider{
|
||||
Repos: []*scm_provider.Repository{
|
||||
{
|
||||
Organization: "myorg",
|
||||
Repository: "repo1",
|
||||
URL: "git@github.com:myorg/repo1.git",
|
||||
Branch: "main",
|
||||
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
|
||||
Labels: []string{"prod", "staging"},
|
||||
cases := []struct {
|
||||
name string
|
||||
repos []*scm_provider.Repository
|
||||
values map[string]string
|
||||
expected []map[string]interface{}
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "Multiple repos with labels",
|
||||
repos: []*scm_provider.Repository{
|
||||
{
|
||||
Organization: "myorg",
|
||||
Repository: "repo1",
|
||||
URL: "git@github.com:myorg/repo1.git",
|
||||
Branch: "main",
|
||||
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
|
||||
Labels: []string{"prod", "staging"},
|
||||
},
|
||||
{
|
||||
Organization: "myorg",
|
||||
Repository: "repo2",
|
||||
URL: "git@github.com:myorg/repo2.git",
|
||||
Branch: "main",
|
||||
SHA: "59d0",
|
||||
},
|
||||
},
|
||||
{
|
||||
Organization: "myorg",
|
||||
Repository: "repo2",
|
||||
URL: "git@github.com:myorg/repo2.git",
|
||||
Branch: "main",
|
||||
SHA: "59d0",
|
||||
expected: []map[string]interface{}{
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "repo1",
|
||||
"url": "git@github.com:myorg/repo1.git",
|
||||
"branch": "main",
|
||||
"branchNormalized": "main",
|
||||
"sha": "0bc57212c3cbbec69d20b34c507284bd300def5b",
|
||||
"short_sha": "0bc57212",
|
||||
"short_sha_7": "0bc5721",
|
||||
"labels": "prod,staging",
|
||||
},
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "repo2",
|
||||
"url": "git@github.com:myorg/repo2.git",
|
||||
"branch": "main",
|
||||
"branchNormalized": "main",
|
||||
"sha": "59d0",
|
||||
"short_sha": "59d0",
|
||||
"short_sha_7": "59d0",
|
||||
"labels": "",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Value interpolation",
|
||||
repos: []*scm_provider.Repository{
|
||||
{
|
||||
Organization: "myorg",
|
||||
Repository: "repo3",
|
||||
URL: "git@github.com:myorg/repo3.git",
|
||||
Branch: "main",
|
||||
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
|
||||
Labels: []string{"prod", "staging"},
|
||||
},
|
||||
},
|
||||
values: map[string]string{
|
||||
"foo": "bar",
|
||||
"should_i_force_push_to": "{{ branch }}?",
|
||||
},
|
||||
expected: []map[string]interface{}{
|
||||
{
|
||||
"organization": "myorg",
|
||||
"repository": "repo3",
|
||||
"url": "git@github.com:myorg/repo3.git",
|
||||
"branch": "main",
|
||||
"branchNormalized": "main",
|
||||
"sha": "0bc57212c3cbbec69d20b34c507284bd300def5b",
|
||||
"short_sha": "0bc57212",
|
||||
"short_sha_7": "0bc5721",
|
||||
"labels": "prod,staging",
|
||||
"values.foo": "bar",
|
||||
"values.should_i_force_push_to": "main?",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
gen := &SCMProviderGenerator{overrideProvider: mockProvider}
|
||||
params, err := gen.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
|
||||
SCMProvider: &argoprojiov1alpha1.SCMProviderGenerator{},
|
||||
}, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, params, 2)
|
||||
assert.Equal(t, "myorg", params[0]["organization"])
|
||||
assert.Equal(t, "repo1", params[0]["repository"])
|
||||
assert.Equal(t, "git@github.com:myorg/repo1.git", params[0]["url"])
|
||||
assert.Equal(t, "main", params[0]["branch"])
|
||||
assert.Equal(t, "0bc57212c3cbbec69d20b34c507284bd300def5b", params[0]["sha"])
|
||||
assert.Equal(t, "0bc57212", params[0]["short_sha"])
|
||||
assert.Equal(t, "59d0", params[1]["short_sha"])
|
||||
assert.Equal(t, "0bc5721", params[0]["short_sha_7"])
|
||||
assert.Equal(t, "59d0", params[1]["short_sha_7"])
|
||||
assert.Equal(t, "prod,staging", params[0]["labels"])
|
||||
assert.Equal(t, "repo2", params[1]["repository"])
|
||||
|
||||
for _, testCase := range cases {
|
||||
testCaseCopy := testCase
|
||||
|
||||
t.Run(testCaseCopy.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mockProvider := &scm_provider.MockProvider{
|
||||
Repos: testCaseCopy.repos,
|
||||
}
|
||||
scmGenerator := &SCMProviderGenerator{overrideProvider: mockProvider}
|
||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "set",
|
||||
},
|
||||
Spec: argoprojiov1alpha1.ApplicationSetSpec{
|
||||
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
|
||||
SCMProvider: &argoprojiov1alpha1.SCMProviderGenerator{
|
||||
Values: testCaseCopy.values,
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
got, err := scmGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
|
||||
|
||||
if testCaseCopy.expectedError != nil {
|
||||
assert.EqualError(t, err, testCaseCopy.expectedError.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, testCaseCopy.expected, got)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowedSCMProvider(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
providerConfig *argoprojiov1alpha1.SCMProviderGenerator
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "Error Github",
|
||||
providerConfig: &argoprojiov1alpha1.SCMProviderGenerator{
|
||||
Github: &argoprojiov1alpha1.SCMProviderGeneratorGithub{
|
||||
API: "https://myservice.mynamespace.svc.cluster.local",
|
||||
},
|
||||
},
|
||||
expectedError: "scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
|
||||
},
|
||||
{
|
||||
name: "Error Gitlab",
|
||||
providerConfig: &argoprojiov1alpha1.SCMProviderGenerator{
|
||||
Gitlab: &argoprojiov1alpha1.SCMProviderGeneratorGitlab{
|
||||
API: "https://myservice.mynamespace.svc.cluster.local",
|
||||
},
|
||||
},
|
||||
expectedError: "scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
|
||||
},
|
||||
{
|
||||
name: "Error Gitea",
|
||||
providerConfig: &argoprojiov1alpha1.SCMProviderGenerator{
|
||||
Gitea: &argoprojiov1alpha1.SCMProviderGeneratorGitea{
|
||||
API: "https://myservice.mynamespace.svc.cluster.local",
|
||||
},
|
||||
},
|
||||
expectedError: "scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
|
||||
},
|
||||
{
|
||||
name: "Error Bitbucket",
|
||||
providerConfig: &argoprojiov1alpha1.SCMProviderGenerator{
|
||||
BitbucketServer: &argoprojiov1alpha1.SCMProviderGeneratorBitbucketServer{
|
||||
API: "https://myservice.mynamespace.svc.cluster.local",
|
||||
},
|
||||
},
|
||||
expectedError: "scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
|
||||
},
|
||||
{
|
||||
name: "Error AzureDevops",
|
||||
providerConfig: &argoprojiov1alpha1.SCMProviderGenerator{
|
||||
AzureDevOps: &argoprojiov1alpha1.SCMProviderGeneratorAzureDevOps{
|
||||
API: "https://myservice.mynamespace.svc.cluster.local",
|
||||
},
|
||||
},
|
||||
expectedError: "scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range cases {
|
||||
testCaseCopy := testCase
|
||||
|
||||
t.Run(testCaseCopy.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
scmGenerator := &SCMProviderGenerator{allowedSCMProviders: []string{
|
||||
"github.myorg.com",
|
||||
"gitlab.myorg.com",
|
||||
"gitea.myorg.com",
|
||||
"bitbucket.myorg.com",
|
||||
"azuredevops.myorg.com",
|
||||
}}
|
||||
|
||||
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "set",
|
||||
},
|
||||
Spec: argoprojiov1alpha1.ApplicationSetSpec{
|
||||
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
|
||||
SCMProvider: testCaseCopy.providerConfig,
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := scmGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
|
||||
|
||||
assert.Error(t, err, "Must return an error")
|
||||
assert.Equal(t, testCaseCopy.expectedError, err.Error())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
43
applicationset/generators/value_interpolation.go
Normal file
43
applicationset/generators/value_interpolation.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package generators
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func appendTemplatedValues(values map[string]string, params map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) error {
|
||||
// We create a local map to ensure that we do not fall victim to a billion-laughs attack. We iterate through the
|
||||
// cluster values map and only replace values in said map if it has already been allowlisted in the params map.
|
||||
// Once we iterate through all the cluster values we can then safely merge the `tmp` map into the main params map.
|
||||
tmp := map[string]interface{}{}
|
||||
|
||||
for key, value := range values {
|
||||
result, err := replaceTemplatedString(value, params, useGoTemplate, goTemplateOptions)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to replace templated string: %w", err)
|
||||
}
|
||||
|
||||
if useGoTemplate {
|
||||
if tmp["values"] == nil {
|
||||
tmp["values"] = map[string]string{}
|
||||
}
|
||||
tmp["values"].(map[string]string)[key] = result
|
||||
} else {
|
||||
tmp[fmt.Sprintf("values.%s", key)] = result
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range tmp {
|
||||
params[key] = value
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func replaceTemplatedString(value string, params map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (string, error) {
|
||||
replacedTmplStr, err := render.Replace(value, params, useGoTemplate, goTemplateOptions)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to replace templated string with rendered values: %w", err)
|
||||
}
|
||||
return replacedTmplStr, nil
|
||||
}
|
||||
125
applicationset/generators/value_interpolation_test.go
Normal file
125
applicationset/generators/value_interpolation_test.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package generators
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValueInterpolation(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
values map[string]string
|
||||
params map[string]interface{}
|
||||
expected map[string]interface{}
|
||||
}{
|
||||
{
|
||||
name: "Simple interpolation",
|
||||
values: map[string]string{
|
||||
"hello": "{{ world }}",
|
||||
},
|
||||
params: map[string]interface{}{
|
||||
"world": "world!",
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"world": "world!",
|
||||
"values.hello": "world!",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Non-existent",
|
||||
values: map[string]string{
|
||||
"non-existent": "{{ non-existent }}",
|
||||
},
|
||||
params: map[string]interface{}{},
|
||||
expected: map[string]interface{}{
|
||||
"values.non-existent": "{{ non-existent }}",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Billion laughs",
|
||||
values: map[string]string{
|
||||
"lol1": "lol",
|
||||
"lol2": "{{values.lol1}}{{values.lol1}}",
|
||||
"lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}",
|
||||
},
|
||||
params: map[string]interface{}{},
|
||||
expected: map[string]interface{}{
|
||||
"values.lol1": "lol",
|
||||
"values.lol2": "{{values.lol1}}{{values.lol1}}",
|
||||
"values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
err := appendTemplatedValues(testCase.values, testCase.params, false, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, testCase.expected, testCase.params)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueInterpolationWithGoTemplating(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
values map[string]string
|
||||
params map[string]interface{}
|
||||
expected map[string]interface{}
|
||||
}{
|
||||
{
|
||||
name: "Simple interpolation",
|
||||
values: map[string]string{
|
||||
"hello": "{{ .world }}",
|
||||
},
|
||||
params: map[string]interface{}{
|
||||
"world": "world!",
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"world": "world!",
|
||||
"values": map[string]string{
|
||||
"hello": "world!",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Non-existent to default",
|
||||
values: map[string]string{
|
||||
"non_existent": "{{ default \"bar\" .non_existent }}",
|
||||
},
|
||||
params: map[string]interface{}{},
|
||||
expected: map[string]interface{}{
|
||||
"values": map[string]string{
|
||||
"non_existent": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Billion laughs",
|
||||
values: map[string]string{
|
||||
"lol1": "lol",
|
||||
"lol2": "{{.values.lol1}}{{.values.lol1}}",
|
||||
"lol3": "{{.values.lol2}}{{.values.lol2}}{{.values.lol2}}",
|
||||
},
|
||||
params: map[string]interface{}{},
|
||||
expected: map[string]interface{}{
|
||||
"values": map[string]string{
|
||||
"lol1": "lol",
|
||||
"lol2": "<no value><no value>",
|
||||
"lol3": "<no value><no value><no value>",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
err := appendTemplatedValues(testCase.values, testCase.params, true, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, testCase.expected, testCase.params)
|
||||
})
|
||||
}
|
||||
}
|
||||
161
applicationset/services/internal/http/client.go
Normal file
161
applicationset/services/internal/http/client.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
userAgent = "argocd-applicationset"
|
||||
defaultTimeout = 30
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
// URL is the URL used for API requests.
|
||||
baseURL string
|
||||
|
||||
// UserAgent is the user agent to include in HTTP requests.
|
||||
UserAgent string
|
||||
|
||||
// Token is used to make authenticated API calls.
|
||||
token string
|
||||
|
||||
// Client is an HTTP client used to communicate with the API.
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
type ErrorResponse struct {
|
||||
Body []byte
|
||||
Response *http.Response
|
||||
Message string
|
||||
}
|
||||
|
||||
func NewClient(baseURL string, options ...ClientOptionFunc) (*Client, error) {
|
||||
client, err := newClient(baseURL, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func newClient(baseURL string, options ...ClientOptionFunc) (*Client, error) {
|
||||
c := &Client{baseURL: baseURL, UserAgent: userAgent}
|
||||
|
||||
// Configure the HTTP client.
|
||||
c.client = &http.Client{
|
||||
Timeout: time.Duration(defaultTimeout) * time.Second,
|
||||
}
|
||||
|
||||
// Apply any given client options.
|
||||
for _, fn := range options {
|
||||
if fn == nil {
|
||||
continue
|
||||
}
|
||||
if err := fn(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Client) NewRequest(method, path string, body interface{}, options []ClientOptionFunc) (*http.Request, error) {
|
||||
|
||||
// Make sure the given URL end with a slash
|
||||
if !strings.HasSuffix(c.baseURL, "/") {
|
||||
c.baseURL += "/"
|
||||
}
|
||||
|
||||
var buf io.ReadWriter
|
||||
if body != nil {
|
||||
buf = &bytes.Buffer{}
|
||||
enc := json.NewEncoder(buf)
|
||||
enc.SetEscapeHTML(false)
|
||||
err := enc.Encode(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, c.baseURL+path, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
if len(c.token) != 0 {
|
||||
req.Header.Set("Authorization", "Bearer "+c.token)
|
||||
}
|
||||
|
||||
if c.UserAgent != "" {
|
||||
req.Header.Set("User-Agent", c.UserAgent)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*http.Response, error) {
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err := CheckResponse(resp); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
switch v := v.(type) {
|
||||
case nil:
|
||||
case io.Writer:
|
||||
_, err = io.Copy(v, resp.Body)
|
||||
default:
|
||||
buf := new(bytes.Buffer)
|
||||
teeReader := io.TeeReader(resp.Body, buf)
|
||||
decErr := json.NewDecoder(teeReader).Decode(v)
|
||||
if decErr == io.EOF {
|
||||
decErr = nil // ignore EOF errors caused by empty response body
|
||||
}
|
||||
if decErr != nil {
|
||||
err = fmt.Errorf("%s: %s", decErr.Error(), buf.String())
|
||||
}
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// CheckResponse checks the API response for errors, and returns them if present.
|
||||
func CheckResponse(resp *http.Response) error {
|
||||
|
||||
if c := resp.StatusCode; 200 <= c && c <= 299 {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("API error with status code %d: %v", resp.StatusCode, err)
|
||||
}
|
||||
|
||||
var raw map[string]interface{}
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
return fmt.Errorf("API error with status code %d: %s", resp.StatusCode, string(data))
|
||||
}
|
||||
|
||||
message := ""
|
||||
if value, ok := raw["message"].(string); ok {
|
||||
message = value
|
||||
} else if value, ok := raw["error"].(string); ok {
|
||||
message = value
|
||||
}
|
||||
|
||||
return fmt.Errorf("API error with status code %d: %s", resp.StatusCode, message)
|
||||
}
|
||||
22
applicationset/services/internal/http/client_options.go
Normal file
22
applicationset/services/internal/http/client_options.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package http
|
||||
|
||||
import "time"
|
||||
|
||||
// ClientOptionFunc can be used to customize a new Restful API client.
|
||||
type ClientOptionFunc func(*Client) error
|
||||
|
||||
// WithToken is an option for NewClient to set token
|
||||
func WithToken(token string) ClientOptionFunc {
|
||||
return func(c *Client) error {
|
||||
c.token = token
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTimeout can be used to configure a custom timeout for requests.
|
||||
func WithTimeout(timeout int) ClientOptionFunc {
|
||||
return func(c *Client) error {
|
||||
c.client.Timeout = time.Duration(timeout) * time.Second
|
||||
return nil
|
||||
}
|
||||
}
|
||||
163
applicationset/services/internal/http/client_test.go
Normal file
163
applicationset/services/internal/http/client_test.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestClient(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := w.Write([]byte("Hello, World!"))
|
||||
if err != nil {
|
||||
assert.NoError(t, fmt.Errorf("Error Write %v", err))
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
var clientOptionFns []ClientOptionFunc
|
||||
_, err := NewClient(server.URL, clientOptionFns...)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientDo(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
for _, c := range []struct {
|
||||
name string
|
||||
params map[string]string
|
||||
content []byte
|
||||
fakeServer *httptest.Server
|
||||
clientOptionFns []ClientOptionFunc
|
||||
expected []map[string]interface{}
|
||||
expectedCode int
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "Simple",
|
||||
params: map[string]string{
|
||||
"pkey1": "val1",
|
||||
"pkey2": "val2",
|
||||
},
|
||||
fakeServer: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := w.Write([]byte(`[{
|
||||
"key1": "val1",
|
||||
"key2": {
|
||||
"key2_1": "val2_1",
|
||||
"key2_2": {
|
||||
"key2_2_1": "val2_2_1"
|
||||
}
|
||||
},
|
||||
"key3": 123
|
||||
}]`))
|
||||
if err != nil {
|
||||
assert.NoError(t, fmt.Errorf("Error Write %v", err))
|
||||
}
|
||||
})),
|
||||
clientOptionFns: nil,
|
||||
expected: []map[string]interface{}{
|
||||
{
|
||||
"key1": "val1",
|
||||
"key2": map[string]interface{}{
|
||||
"key2_1": "val2_1",
|
||||
"key2_2": map[string]interface{}{
|
||||
"key2_2_1": "val2_2_1",
|
||||
},
|
||||
},
|
||||
"key3": float64(123),
|
||||
},
|
||||
},
|
||||
expectedCode: 200,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "With Token",
|
||||
params: map[string]string{
|
||||
"pkey1": "val1",
|
||||
"pkey2": "val2",
|
||||
},
|
||||
fakeServer: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader != "Bearer "+string("test-token") {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := w.Write([]byte(`[{
|
||||
"key1": "val1",
|
||||
"key2": {
|
||||
"key2_1": "val2_1",
|
||||
"key2_2": {
|
||||
"key2_2_1": "val2_2_1"
|
||||
}
|
||||
},
|
||||
"key3": 123
|
||||
}]`))
|
||||
if err != nil {
|
||||
assert.NoError(t, fmt.Errorf("Error Write %v", err))
|
||||
}
|
||||
})),
|
||||
clientOptionFns: nil,
|
||||
expected: []map[string]interface{}(nil),
|
||||
expectedCode: 401,
|
||||
expectedError: fmt.Errorf("API error with status code 401: "),
|
||||
},
|
||||
} {
|
||||
cc := c
|
||||
t.Run(cc.name, func(t *testing.T) {
|
||||
defer cc.fakeServer.Close()
|
||||
|
||||
client, err := NewClient(cc.fakeServer.URL, cc.clientOptionFns...)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("NewClient returned unexpected error: %v", err)
|
||||
}
|
||||
|
||||
req, err := client.NewRequest("POST", "", cc.params, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("NewRequest returned unexpected error: %v", err)
|
||||
}
|
||||
|
||||
var data []map[string]interface{}
|
||||
|
||||
resp, err := client.Do(ctx, req, &data)
|
||||
|
||||
if cc.expectedError != nil {
|
||||
assert.EqualError(t, err, cc.expectedError.Error())
|
||||
} else {
|
||||
assert.Equal(t, resp.StatusCode, cc.expectedCode)
|
||||
assert.Equal(t, data, cc.expected)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckResponse(t *testing.T) {
|
||||
resp := &http.Response{
|
||||
StatusCode: http.StatusBadRequest,
|
||||
Body: io.NopCloser(bytes.NewBufferString(`{"error":"invalid_request","description":"Invalid token"}`)),
|
||||
}
|
||||
|
||||
err := CheckResponse(resp)
|
||||
if err == nil {
|
||||
t.Error("Expected an error, got nil")
|
||||
}
|
||||
|
||||
expected := "API error with status code 400: invalid_request"
|
||||
if err.Error() != expected {
|
||||
t.Errorf("Expected error '%s', got '%s'", expected, err.Error())
|
||||
}
|
||||
}
|
||||
@@ -13,25 +13,25 @@ type Repos struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// GetDirectories provides a mock function with given fields: ctx, repoURL, revision
|
||||
func (_m *Repos) GetDirectories(ctx context.Context, repoURL string, revision string) ([]string, error) {
|
||||
ret := _m.Called(ctx, repoURL, revision)
|
||||
// GetDirectories provides a mock function with given fields: ctx, repoURL, revision, noRevisionCache
|
||||
func (_m *Repos) GetDirectories(ctx context.Context, repoURL string, revision string, noRevisionCache bool) ([]string, error) {
|
||||
ret := _m.Called(ctx, repoURL, revision, noRevisionCache)
|
||||
|
||||
var r0 []string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]string, error)); ok {
|
||||
return rf(ctx, repoURL, revision)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, bool) ([]string, error)); ok {
|
||||
return rf(ctx, repoURL, revision, noRevisionCache)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) []string); ok {
|
||||
r0 = rf(ctx, repoURL, revision)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, bool) []string); ok {
|
||||
r0 = rf(ctx, repoURL, revision, noRevisionCache)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]string)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
|
||||
r1 = rf(ctx, repoURL, revision)
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, bool) error); ok {
|
||||
r1 = rf(ctx, repoURL, revision, noRevisionCache)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
@@ -39,25 +39,25 @@ func (_m *Repos) GetDirectories(ctx context.Context, repoURL string, revision st
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetFiles provides a mock function with given fields: ctx, repoURL, revision, pattern
|
||||
func (_m *Repos) GetFiles(ctx context.Context, repoURL string, revision string, pattern string) (map[string][]byte, error) {
|
||||
ret := _m.Called(ctx, repoURL, revision, pattern)
|
||||
// GetFiles provides a mock function with given fields: ctx, repoURL, revision, pattern, noRevisionCache
|
||||
func (_m *Repos) GetFiles(ctx context.Context, repoURL string, revision string, pattern string, noRevisionCache bool) (map[string][]byte, error) {
|
||||
ret := _m.Called(ctx, repoURL, revision, pattern, noRevisionCache)
|
||||
|
||||
var r0 map[string][]byte
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (map[string][]byte, error)); ok {
|
||||
return rf(ctx, repoURL, revision, pattern)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool) (map[string][]byte, error)); ok {
|
||||
return rf(ctx, repoURL, revision, pattern, noRevisionCache)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) map[string][]byte); ok {
|
||||
r0 = rf(ctx, repoURL, revision, pattern)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool) map[string][]byte); ok {
|
||||
r0 = rf(ctx, repoURL, revision, pattern, noRevisionCache)
|
||||
} 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) error); ok {
|
||||
r1 = rf(ctx, repoURL, revision, pattern)
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, bool) error); ok {
|
||||
r1 = rf(ctx, repoURL, revision, pattern, noRevisionCache)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
73
applicationset/services/plugin/plugin_service.go
Normal file
73
applicationset/services/plugin/plugin_service.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
internalhttp "github.com/argoproj/argo-cd/v2/applicationset/services/internal/http"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
|
||||
// ServiceRequest is the request object sent to the plugin service.
|
||||
type ServiceRequest struct {
|
||||
// ApplicationSetName is the appSetName of the ApplicationSet for which we're requesting parameters. Useful for logging in
|
||||
// the plugin service.
|
||||
ApplicationSetName string `json:"applicationSetName"`
|
||||
// Input is the map of parameters set in the ApplicationSet spec for this generator.
|
||||
Input v1alpha1.PluginInput `json:"input"`
|
||||
}
|
||||
|
||||
type Output struct {
|
||||
// Parameters is the list of parameter sets returned by the plugin.
|
||||
Parameters []map[string]interface{} `json:"parameters"`
|
||||
}
|
||||
|
||||
// ServiceResponse is the response object returned by the plugin service.
|
||||
type ServiceResponse struct {
|
||||
// Output is the map of outputs returned by the plugin.
|
||||
Output Output `json:"output"`
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
client *internalhttp.Client
|
||||
appSetName string
|
||||
}
|
||||
|
||||
func NewPluginService(ctx context.Context, appSetName string, baseURL string, token string, requestTimeout int) (*Service, error) {
|
||||
var clientOptionFns []internalhttp.ClientOptionFunc
|
||||
|
||||
clientOptionFns = append(clientOptionFns, internalhttp.WithToken(token))
|
||||
|
||||
if requestTimeout != 0 {
|
||||
clientOptionFns = append(clientOptionFns, internalhttp.WithTimeout(requestTimeout))
|
||||
}
|
||||
|
||||
client, err := internalhttp.NewClient(baseURL, clientOptionFns...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating plugin client: %v", err)
|
||||
}
|
||||
|
||||
return &Service{
|
||||
client: client,
|
||||
appSetName: appSetName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Service) List(ctx context.Context, parameters v1alpha1.PluginParameters) (*ServiceResponse, error) {
|
||||
req, err := p.client.NewRequest(http.MethodPost, "api/v1/getparams.execute", ServiceRequest{ApplicationSetName: p.appSetName, Input: v1alpha1.PluginInput{Parameters: parameters}}, nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewRequest returned unexpected error: %v", err)
|
||||
}
|
||||
|
||||
var data ServiceResponse
|
||||
|
||||
_, err = p.client.Do(ctx, req, &data)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error get api '%s': %v", p.appSetName, err)
|
||||
}
|
||||
|
||||
return &data, err
|
||||
}
|
||||
52
applicationset/services/plugin/plugin_service_test.go
Normal file
52
applicationset/services/plugin/plugin_service_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPlugin(t *testing.T) {
|
||||
expectedJSON := `{"parameters": [{"number":123,"digest":"sha256:942ae2dfd73088b54d7151a3c3fd5af038a51c50029bfcfd21f1e650d9579967"},{"number":456,"digest":"sha256:224e68cc69566e5cbbb76034b3c42cd2ed57c1a66720396e1c257794cb7d68c1"}]}`
|
||||
token := "0bc57212c3cbbec69d20b34c507284bd300def5b"
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader != "Bearer "+token {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
_, err := w.Write([]byte(expectedJSON))
|
||||
|
||||
if err != nil {
|
||||
assert.NoError(t, fmt.Errorf("Error Write %v", err))
|
||||
}
|
||||
})
|
||||
ts := httptest.NewServer(handler)
|
||||
defer ts.Close()
|
||||
|
||||
client, err := NewPluginService(context.Background(), "plugin-test", ts.URL, token, 0)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
data, err := client.List(context.Background(), nil)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
var expectedData ServiceResponse
|
||||
err = json.Unmarshal([]byte(expectedJSON), &expectedData)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, &expectedData, data)
|
||||
}
|
||||
21
applicationset/services/plugin/utils.go
Normal file
21
applicationset/services/plugin/utils.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
)
|
||||
|
||||
// ParseSecretKey retrieves secret appSetName if different from common ArgoCDSecretName.
|
||||
func ParseSecretKey(key string) (secretName string, tokenKey string) {
|
||||
if strings.Contains(key, ":") {
|
||||
parts := strings.Split(key, ":")
|
||||
secretName = parts[0][1:]
|
||||
tokenKey = fmt.Sprintf("$%s", parts[1])
|
||||
} else {
|
||||
secretName = common.ArgoCDSecretName
|
||||
tokenKey = key
|
||||
}
|
||||
return secretName, tokenKey
|
||||
}
|
||||
17
applicationset/services/plugin/utils_test.go
Normal file
17
applicationset/services/plugin/utils_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseSecretKey(t *testing.T) {
|
||||
secretName, tokenKey := ParseSecretKey("#my-secret:my-token")
|
||||
assert.Equal(t, "my-secret", secretName)
|
||||
assert.Equal(t, "$my-token", tokenKey)
|
||||
|
||||
secretName, tokenKey = ParseSecretKey("#my-secret")
|
||||
assert.Equal(t, "argocd-secret", secretName)
|
||||
assert.Equal(t, "#my-secret", tokenKey)
|
||||
}
|
||||
145
applicationset/services/pull_request/azure_devops.go
Normal file
145
applicationset/services/pull_request/azure_devops.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package pull_request
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops"
|
||||
core "github.com/microsoft/azure-devops-go-api/azuredevops/core"
|
||||
git "github.com/microsoft/azure-devops-go-api/azuredevops/git"
|
||||
)
|
||||
|
||||
const AZURE_DEVOPS_DEFAULT_URL = "https://dev.azure.com"
|
||||
|
||||
type AzureDevOpsClientFactory interface {
|
||||
// Returns an Azure Devops Client interface.
|
||||
GetClient(ctx context.Context) (git.Client, error)
|
||||
}
|
||||
|
||||
type devopsFactoryImpl struct {
|
||||
connection *azuredevops.Connection
|
||||
}
|
||||
|
||||
func (factory *devopsFactoryImpl) GetClient(ctx context.Context) (git.Client, error) {
|
||||
gitClient, err := git.NewClient(ctx, factory.connection)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get new Azure DevOps git client for pull request generator: %w", err)
|
||||
}
|
||||
return gitClient, nil
|
||||
}
|
||||
|
||||
type AzureDevOpsService struct {
|
||||
clientFactory AzureDevOpsClientFactory
|
||||
project string
|
||||
repo string
|
||||
labels []string
|
||||
}
|
||||
|
||||
var _ PullRequestService = (*AzureDevOpsService)(nil)
|
||||
var _ AzureDevOpsClientFactory = &devopsFactoryImpl{}
|
||||
|
||||
func NewAzureDevOpsService(ctx context.Context, token, url, organization, project, repo string, labels []string) (PullRequestService, error) {
|
||||
organizationUrl := buildURL(url, organization)
|
||||
|
||||
var connection *azuredevops.Connection
|
||||
if token == "" {
|
||||
connection = azuredevops.NewAnonymousConnection(organizationUrl)
|
||||
} else {
|
||||
connection = azuredevops.NewPatConnection(organizationUrl, token)
|
||||
}
|
||||
|
||||
return &AzureDevOpsService{
|
||||
clientFactory: &devopsFactoryImpl{connection: connection},
|
||||
project: project,
|
||||
repo: repo,
|
||||
labels: labels,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *AzureDevOpsService) List(ctx context.Context) ([]*PullRequest, error) {
|
||||
client, err := a.clientFactory.GetClient(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get Azure DevOps client: %w", err)
|
||||
}
|
||||
|
||||
args := git.GetPullRequestsByProjectArgs{
|
||||
Project: &a.project,
|
||||
SearchCriteria: &git.GitPullRequestSearchCriteria{},
|
||||
}
|
||||
|
||||
azurePullRequests, err := client.GetPullRequestsByProject(ctx, args)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get pull requests by project: %w", err)
|
||||
}
|
||||
|
||||
pullRequests := []*PullRequest{}
|
||||
|
||||
for _, pr := range *azurePullRequests {
|
||||
if pr.Repository == nil ||
|
||||
pr.Repository.Name == nil ||
|
||||
pr.PullRequestId == nil ||
|
||||
pr.SourceRefName == nil ||
|
||||
pr.LastMergeSourceCommit == nil ||
|
||||
pr.LastMergeSourceCommit.CommitId == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
azureDevOpsLabels := convertLabels(pr.Labels)
|
||||
if !containAzureDevOpsLabels(a.labels, azureDevOpsLabels) {
|
||||
continue
|
||||
}
|
||||
|
||||
if *pr.Repository.Name == a.repo {
|
||||
pullRequests = append(pullRequests, &PullRequest{
|
||||
Number: *pr.PullRequestId,
|
||||
Branch: strings.Replace(*pr.SourceRefName, "refs/heads/", "", 1),
|
||||
HeadSHA: *pr.LastMergeSourceCommit.CommitId,
|
||||
Labels: azureDevOpsLabels,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return pullRequests, nil
|
||||
}
|
||||
|
||||
// convertLabels converts WebApiTagDefinitions to strings
|
||||
func convertLabels(tags *[]core.WebApiTagDefinition) []string {
|
||||
if tags == nil {
|
||||
return []string{}
|
||||
}
|
||||
labelStrings := make([]string, len(*tags))
|
||||
for i, label := range *tags {
|
||||
labelStrings[i] = *label.Name
|
||||
}
|
||||
return labelStrings
|
||||
}
|
||||
|
||||
// containAzureDevOpsLabels returns true if gotLabels contains expectedLabels
|
||||
func containAzureDevOpsLabels(expectedLabels []string, gotLabels []string) bool {
|
||||
for _, expected := range expectedLabels {
|
||||
found := false
|
||||
for _, got := range gotLabels {
|
||||
if expected == got {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func buildURL(url, organization string) string {
|
||||
if url == "" {
|
||||
url = AZURE_DEVOPS_DEFAULT_URL
|
||||
}
|
||||
separator := ""
|
||||
if !strings.HasSuffix(url, "/") {
|
||||
separator = "/"
|
||||
}
|
||||
devOpsURL := fmt.Sprintf("%s%s%s", url, separator, organization)
|
||||
return devOpsURL
|
||||
}
|
||||
221
applicationset/services/pull_request/azure_devops_test.go
Normal file
221
applicationset/services/pull_request/azure_devops_test.go
Normal file
@@ -0,0 +1,221 @@
|
||||
package pull_request
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/microsoft/azure-devops-go-api/azuredevops/core"
|
||||
git "github.com/microsoft/azure-devops-go-api/azuredevops/git"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
azureMock "github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider/azure_devops/git/mocks"
|
||||
)
|
||||
|
||||
func createBoolPtr(x bool) *bool {
|
||||
return &x
|
||||
}
|
||||
|
||||
func createStringPtr(x string) *string {
|
||||
return &x
|
||||
}
|
||||
|
||||
func createIntPtr(x int) *int {
|
||||
return &x
|
||||
}
|
||||
|
||||
func createLabelsPtr(x []core.WebApiTagDefinition) *[]core.WebApiTagDefinition {
|
||||
return &x
|
||||
}
|
||||
|
||||
type AzureClientFactoryMock struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (m *AzureClientFactoryMock) GetClient(ctx context.Context) (git.Client, error) {
|
||||
args := m.mock.Called(ctx)
|
||||
|
||||
var client git.Client
|
||||
c := args.Get(0)
|
||||
if c != nil {
|
||||
client = c.(git.Client)
|
||||
}
|
||||
|
||||
var err error
|
||||
if len(args) > 1 {
|
||||
if e, ok := args.Get(1).(error); ok {
|
||||
err = e
|
||||
}
|
||||
}
|
||||
|
||||
return client, err
|
||||
}
|
||||
|
||||
func TestListPullRequest(t *testing.T) {
|
||||
teamProject := "myorg_project"
|
||||
repoName := "myorg_project_repo"
|
||||
pr_id := 123
|
||||
pr_head_sha := "cd4973d9d14a08ffe6b641a89a68891d6aac8056"
|
||||
ctx := context.Background()
|
||||
|
||||
pullRequestMock := []git.GitPullRequest{
|
||||
{
|
||||
PullRequestId: createIntPtr(pr_id),
|
||||
SourceRefName: createStringPtr("refs/heads/feature-branch"),
|
||||
LastMergeSourceCommit: &git.GitCommitRef{
|
||||
CommitId: createStringPtr(pr_head_sha),
|
||||
},
|
||||
Labels: &[]core.WebApiTagDefinition{},
|
||||
Repository: &git.GitRepository{
|
||||
Name: createStringPtr(repoName),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
args := git.GetPullRequestsByProjectArgs{
|
||||
Project: &teamProject,
|
||||
SearchCriteria: &git.GitPullRequestSearchCriteria{},
|
||||
}
|
||||
|
||||
gitClientMock := azureMock.Client{}
|
||||
clientFactoryMock := &AzureClientFactoryMock{mock: &mock.Mock{}}
|
||||
clientFactoryMock.mock.On("GetClient", mock.Anything).Return(&gitClientMock, nil)
|
||||
gitClientMock.On("GetPullRequestsByProject", ctx, args).Return(&pullRequestMock, nil)
|
||||
|
||||
provider := AzureDevOpsService{
|
||||
clientFactory: clientFactoryMock,
|
||||
project: teamProject,
|
||||
repo: repoName,
|
||||
labels: nil,
|
||||
}
|
||||
|
||||
list, err := provider.List(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(list))
|
||||
assert.Equal(t, "feature-branch", list[0].Branch)
|
||||
assert.Equal(t, pr_head_sha, list[0].HeadSHA)
|
||||
assert.Equal(t, pr_id, list[0].Number)
|
||||
}
|
||||
|
||||
func TestConvertLabes(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
gotLabels *[]core.WebApiTagDefinition
|
||||
expectedLabels []string
|
||||
}{
|
||||
{
|
||||
name: "empty labels",
|
||||
gotLabels: createLabelsPtr([]core.WebApiTagDefinition{}),
|
||||
expectedLabels: []string{},
|
||||
},
|
||||
{
|
||||
name: "nil labels",
|
||||
gotLabels: createLabelsPtr(nil),
|
||||
expectedLabels: []string{},
|
||||
},
|
||||
{
|
||||
name: "one label",
|
||||
gotLabels: createLabelsPtr([]core.WebApiTagDefinition{
|
||||
{Name: createStringPtr("label1"), Active: createBoolPtr(true)},
|
||||
}),
|
||||
expectedLabels: []string{"label1"},
|
||||
},
|
||||
{
|
||||
name: "two label",
|
||||
gotLabels: createLabelsPtr([]core.WebApiTagDefinition{
|
||||
{Name: createStringPtr("label1"), Active: createBoolPtr(true)},
|
||||
{Name: createStringPtr("label2"), Active: createBoolPtr(true)},
|
||||
}),
|
||||
expectedLabels: []string{"label1", "label2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := convertLabels(tc.gotLabels)
|
||||
assert.Equal(t, tc.expectedLabels, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainAzureDevOpsLabels(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
expectedLabels []string
|
||||
gotLabels []string
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "empty labels",
|
||||
expectedLabels: []string{},
|
||||
gotLabels: []string{},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "no matching labels",
|
||||
expectedLabels: []string{"label1", "label2"},
|
||||
gotLabels: []string{"label3", "label4"},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "some matching labels",
|
||||
expectedLabels: []string{"label1", "label2"},
|
||||
gotLabels: []string{"label1", "label3"},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "all matching labels",
|
||||
expectedLabels: []string{"label1", "label2"},
|
||||
gotLabels: []string{"label1", "label2"},
|
||||
expectedResult: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := containAzureDevOpsLabels(tc.expectedLabels, tc.gotLabels)
|
||||
assert.Equal(t, tc.expectedResult, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildURL(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
url string
|
||||
organization string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "Provided default URL and organization",
|
||||
url: "https://dev.azure.com/",
|
||||
organization: "myorganization",
|
||||
expected: "https://dev.azure.com/myorganization",
|
||||
},
|
||||
{
|
||||
name: "Provided default URL and organization without trailing slash",
|
||||
url: "https://dev.azure.com",
|
||||
organization: "myorganization",
|
||||
expected: "https://dev.azure.com/myorganization",
|
||||
},
|
||||
{
|
||||
name: "Provided no URL and organization",
|
||||
url: "",
|
||||
organization: "myorganization",
|
||||
expected: "https://dev.azure.com/myorganization",
|
||||
},
|
||||
{
|
||||
name: "Provided custom URL and organization",
|
||||
url: "https://azuredevops.mycompany.com/",
|
||||
organization: "myorganization",
|
||||
expected: "https://azuredevops.mycompany.com/myorganization",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := buildURL(tc.url, tc.organization)
|
||||
assert.Equal(t, result, tc.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
138
applicationset/services/pull_request/bitbucket_cloud.go
Normal file
138
applicationset/services/pull_request/bitbucket_cloud.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package pull_request
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/ktrysmt/go-bitbucket"
|
||||
)
|
||||
|
||||
type BitbucketCloudService struct {
|
||||
client *bitbucket.Client
|
||||
owner string
|
||||
repositorySlug string
|
||||
}
|
||||
|
||||
type BitbucketCloudPullRequest struct {
|
||||
ID int `json:"id"`
|
||||
Source BitbucketCloudPullRequestSource `json:"source"`
|
||||
}
|
||||
|
||||
type BitbucketCloudPullRequestSource struct {
|
||||
Branch BitbucketCloudPullRequestSourceBranch `json:"branch"`
|
||||
Commit BitbucketCloudPullRequestSourceCommit `json:"commit"`
|
||||
}
|
||||
|
||||
type BitbucketCloudPullRequestSourceBranch struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type BitbucketCloudPullRequestSourceCommit struct {
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
|
||||
type PullRequestResponse struct {
|
||||
Page int32 `json:"page"`
|
||||
Size int32 `json:"size"`
|
||||
Pagelen int32 `json:"pagelen"`
|
||||
Next string `json:"next"`
|
||||
Previous string `json:"previous"`
|
||||
Items []PullRequest `json:"values"`
|
||||
}
|
||||
|
||||
var _ PullRequestService = (*BitbucketCloudService)(nil)
|
||||
|
||||
func parseUrl(uri string) (*url.URL, error) {
|
||||
if uri == "" {
|
||||
uri = "https://api.bitbucket.org/2.0"
|
||||
}
|
||||
|
||||
url, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return url, nil
|
||||
}
|
||||
|
||||
func NewBitbucketCloudServiceBasicAuth(baseUrl, username, password, owner, repositorySlug string) (PullRequestService, error) {
|
||||
url, err := parseUrl(baseUrl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing base url of %s for %s/%s: %v", baseUrl, owner, repositorySlug, err)
|
||||
}
|
||||
|
||||
bitbucketClient := bitbucket.NewBasicAuth(username, password)
|
||||
bitbucketClient.SetApiBaseURL(*url)
|
||||
|
||||
return &BitbucketCloudService{
|
||||
client: bitbucketClient,
|
||||
owner: owner,
|
||||
repositorySlug: repositorySlug,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewBitbucketCloudServiceBearerToken(baseUrl, bearerToken, owner, repositorySlug string) (PullRequestService, error) {
|
||||
url, err := parseUrl(baseUrl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing base url of %s for %s/%s: %v", baseUrl, owner, repositorySlug, err)
|
||||
}
|
||||
|
||||
bitbucketClient := bitbucket.NewOAuthbearerToken(bearerToken)
|
||||
bitbucketClient.SetApiBaseURL(*url)
|
||||
|
||||
return &BitbucketCloudService{
|
||||
client: bitbucketClient,
|
||||
owner: owner,
|
||||
repositorySlug: repositorySlug,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewBitbucketCloudServiceNoAuth(baseUrl, owner, repositorySlug string) (PullRequestService, error) {
|
||||
// There is currently no method to explicitly not require auth
|
||||
return NewBitbucketCloudServiceBearerToken(baseUrl, "", owner, repositorySlug)
|
||||
}
|
||||
|
||||
func (b *BitbucketCloudService) List(_ context.Context) ([]*PullRequest, error) {
|
||||
opts := &bitbucket.PullRequestsOptions{
|
||||
Owner: b.owner,
|
||||
RepoSlug: b.repositorySlug,
|
||||
}
|
||||
|
||||
response, err := b.client.Repositories.PullRequests.Gets(opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listing pull requests for %s/%s: %v", b.owner, b.repositorySlug, err)
|
||||
}
|
||||
|
||||
resp, ok := response.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown type returned from bitbucket pull requests")
|
||||
}
|
||||
|
||||
repoArray, ok := resp["values"].([]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown type returned from response values")
|
||||
}
|
||||
|
||||
jsonStr, err := json.Marshal(repoArray)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshalling response body to json: %v", err)
|
||||
}
|
||||
|
||||
var pulls []BitbucketCloudPullRequest
|
||||
if err := json.Unmarshal(jsonStr, &pulls); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshalling json to type '[]BitbucketCloudPullRequest': %v", err)
|
||||
}
|
||||
|
||||
pullRequests := []*PullRequest{}
|
||||
for _, pull := range pulls {
|
||||
pullRequests = append(pullRequests, &PullRequest{
|
||||
Number: pull.ID,
|
||||
Branch: pull.Source.Branch.Name,
|
||||
HeadSHA: pull.Source.Commit.Hash,
|
||||
})
|
||||
}
|
||||
|
||||
return pullRequests, nil
|
||||
}
|
||||
410
applicationset/services/pull_request/bitbucket_cloud_test.go
Normal file
410
applicationset/services/pull_request/bitbucket_cloud_test.go
Normal file
@@ -0,0 +1,410 @@
|
||||
package pull_request
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
|
||||
func defaultHandlerCloud(t *testing.T) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
var err error
|
||||
switch r.RequestURI {
|
||||
case "/repositories/OWNER/REPO/pullrequests/":
|
||||
_, err = io.WriteString(w, `{
|
||||
"size": 1,
|
||||
"pagelen": 10,
|
||||
"page": 1,
|
||||
"values": [
|
||||
{
|
||||
"id": 101,
|
||||
"source": {
|
||||
"branch": {
|
||||
"name": "feature/foo-bar"
|
||||
},
|
||||
"commit": {
|
||||
"type": "commit",
|
||||
"hash": "1a8dd249c04a"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}`)
|
||||
default:
|
||||
t.Fail()
|
||||
}
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseUrlEmptyUrl(t *testing.T) {
|
||||
url, err := parseUrl("")
|
||||
bitbucketUrl, _ := url.Parse("https://api.bitbucket.org/2.0")
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, bitbucketUrl, url)
|
||||
}
|
||||
|
||||
func TestInvalidBaseUrlBasicAuthCloud(t *testing.T) {
|
||||
_, err := NewBitbucketCloudServiceBasicAuth("http:// example.org", "user", "password", "OWNER", "REPO")
|
||||
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestInvalidBaseUrlBearerTokenCloud(t *testing.T) {
|
||||
_, err := NewBitbucketCloudServiceBearerToken("http:// example.org", "TOKEN", "OWNER", "REPO")
|
||||
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestInvalidBaseUrlNoAuthCloud(t *testing.T) {
|
||||
_, err := NewBitbucketCloudServiceNoAuth("http:// example.org", "OWNER", "REPO")
|
||||
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestListPullRequestBearerTokenCloud(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "Bearer TOKEN", r.Header.Get("Authorization"))
|
||||
defaultHandlerCloud(t)(w, r)
|
||||
}))
|
||||
defer ts.Close()
|
||||
svc, err := NewBitbucketCloudServiceBearerToken(ts.URL, "TOKEN", "OWNER", "REPO")
|
||||
assert.NoError(t, err)
|
||||
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(pullRequests))
|
||||
assert.Equal(t, 101, pullRequests[0].Number)
|
||||
assert.Equal(t, "feature/foo-bar", pullRequests[0].Branch)
|
||||
assert.Equal(t, "1a8dd249c04a", pullRequests[0].HeadSHA)
|
||||
}
|
||||
|
||||
func TestListPullRequestNoAuthCloud(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Empty(t, r.Header.Get("Authorization"))
|
||||
defaultHandlerCloud(t)(w, r)
|
||||
}))
|
||||
defer ts.Close()
|
||||
svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
|
||||
assert.NoError(t, err)
|
||||
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(pullRequests))
|
||||
assert.Equal(t, 101, pullRequests[0].Number)
|
||||
assert.Equal(t, "feature/foo-bar", pullRequests[0].Branch)
|
||||
assert.Equal(t, "1a8dd249c04a", pullRequests[0].HeadSHA)
|
||||
}
|
||||
|
||||
func TestListPullRequestBasicAuthCloud(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "Basic dXNlcjpwYXNzd29yZA==", r.Header.Get("Authorization"))
|
||||
defaultHandlerCloud(t)(w, r)
|
||||
}))
|
||||
defer ts.Close()
|
||||
svc, err := NewBitbucketCloudServiceBasicAuth(ts.URL, "user", "password", "OWNER", "REPO")
|
||||
assert.NoError(t, err)
|
||||
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(pullRequests))
|
||||
assert.Equal(t, 101, pullRequests[0].Number)
|
||||
assert.Equal(t, "feature/foo-bar", pullRequests[0].Branch)
|
||||
assert.Equal(t, "1a8dd249c04a", pullRequests[0].HeadSHA)
|
||||
}
|
||||
|
||||
func TestListPullRequestPaginationCloud(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
var err error
|
||||
switch r.RequestURI {
|
||||
case "/repositories/OWNER/REPO/pullrequests/":
|
||||
_, err = io.WriteString(w, fmt.Sprintf(`{
|
||||
"size": 2,
|
||||
"pagelen": 1,
|
||||
"page": 1,
|
||||
"next": "http://%s/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=2",
|
||||
"values": [
|
||||
{
|
||||
"id": 101,
|
||||
"source": {
|
||||
"branch": {
|
||||
"name": "feature-101"
|
||||
},
|
||||
"commit": {
|
||||
"type": "commit",
|
||||
"hash": "1a8dd249c04a"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 102,
|
||||
"source": {
|
||||
"branch": {
|
||||
"name": "feature-102"
|
||||
},
|
||||
"commit": {
|
||||
"type": "commit",
|
||||
"hash": "4cf807e67a6d"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}`, r.Host))
|
||||
case "/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=2":
|
||||
_, err = io.WriteString(w, fmt.Sprintf(`{
|
||||
"size": 2,
|
||||
"pagelen": 1,
|
||||
"page": 2,
|
||||
"previous": "http://%s/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=1",
|
||||
"values": [
|
||||
{
|
||||
"id": 103,
|
||||
"source": {
|
||||
"branch": {
|
||||
"name": "feature-103"
|
||||
},
|
||||
"commit": {
|
||||
"type": "commit",
|
||||
"hash": "6344d9623e3b"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}`, r.Host))
|
||||
default:
|
||||
t.Fail()
|
||||
}
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
|
||||
assert.NoError(t, err)
|
||||
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 3, len(pullRequests))
|
||||
assert.Equal(t, PullRequest{
|
||||
Number: 101,
|
||||
Branch: "feature-101",
|
||||
HeadSHA: "1a8dd249c04a",
|
||||
}, *pullRequests[0])
|
||||
assert.Equal(t, PullRequest{
|
||||
Number: 102,
|
||||
Branch: "feature-102",
|
||||
HeadSHA: "4cf807e67a6d",
|
||||
}, *pullRequests[1])
|
||||
assert.Equal(t, PullRequest{
|
||||
Number: 103,
|
||||
Branch: "feature-103",
|
||||
HeadSHA: "6344d9623e3b",
|
||||
}, *pullRequests[2])
|
||||
}
|
||||
|
||||
func TestListResponseErrorCloud(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(500)
|
||||
}))
|
||||
defer ts.Close()
|
||||
svc, _ := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
|
||||
_, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestListResponseMalformedCloud(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch r.RequestURI {
|
||||
case "/repositories/OWNER/REPO/pullrequests/":
|
||||
_, err := io.WriteString(w, `[{
|
||||
"size": 1,
|
||||
"pagelen": 10,
|
||||
"page": 1,
|
||||
"values": [{ "id": 101 }]
|
||||
}]`)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
default:
|
||||
t.Fail()
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
svc, _ := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
|
||||
_, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestListResponseMalformedValuesCloud(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch r.RequestURI {
|
||||
case "/repositories/OWNER/REPO/pullrequests/":
|
||||
_, err := io.WriteString(w, `{
|
||||
"size": 1,
|
||||
"pagelen": 10,
|
||||
"page": 1,
|
||||
"values": { "id": 101 }
|
||||
}`)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
default:
|
||||
t.Fail()
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
svc, _ := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
|
||||
_, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestListResponseEmptyCloud(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch r.RequestURI {
|
||||
case "/repositories/OWNER/REPO/pullrequests/":
|
||||
_, err := io.WriteString(w, `{
|
||||
"size": 1,
|
||||
"pagelen": 10,
|
||||
"page": 1,
|
||||
"values": []
|
||||
}`)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
default:
|
||||
t.Fail()
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
|
||||
assert.NoError(t, err)
|
||||
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, pullRequests)
|
||||
}
|
||||
|
||||
func TestListPullRequestBranchMatchCloud(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
var err error
|
||||
switch r.RequestURI {
|
||||
case "/repositories/OWNER/REPO/pullrequests/":
|
||||
_, err = io.WriteString(w, fmt.Sprintf(`{
|
||||
"size": 2,
|
||||
"pagelen": 1,
|
||||
"page": 1,
|
||||
"next": "http://%s/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=2",
|
||||
"values": [
|
||||
{
|
||||
"id": 101,
|
||||
"source": {
|
||||
"branch": {
|
||||
"name": "feature-101"
|
||||
},
|
||||
"commit": {
|
||||
"type": "commit",
|
||||
"hash": "1a8dd249c04a"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 200,
|
||||
"source": {
|
||||
"branch": {
|
||||
"name": "feature-200"
|
||||
},
|
||||
"commit": {
|
||||
"type": "commit",
|
||||
"hash": "4cf807e67a6d"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}`, r.Host))
|
||||
case "/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=2":
|
||||
_, err = io.WriteString(w, fmt.Sprintf(`{
|
||||
"size": 2,
|
||||
"pagelen": 1,
|
||||
"page": 2,
|
||||
"previous": "http://%s/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=1",
|
||||
"values": [
|
||||
{
|
||||
"id": 102,
|
||||
"source": {
|
||||
"branch": {
|
||||
"name": "feature-102"
|
||||
},
|
||||
"commit": {
|
||||
"type": "commit",
|
||||
"hash": "6344d9623e3b"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}`, r.Host))
|
||||
default:
|
||||
t.Fail()
|
||||
}
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
regexp := `feature-1[\d]{2}`
|
||||
svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
|
||||
assert.NoError(t, err)
|
||||
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
|
||||
{
|
||||
BranchMatch: ®exp,
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, len(pullRequests))
|
||||
assert.Equal(t, PullRequest{
|
||||
Number: 101,
|
||||
Branch: "feature-101",
|
||||
HeadSHA: "1a8dd249c04a",
|
||||
}, *pullRequests[0])
|
||||
assert.Equal(t, PullRequest{
|
||||
Number: 102,
|
||||
Branch: "feature-102",
|
||||
HeadSHA: "6344d9623e3b",
|
||||
}, *pullRequests[1])
|
||||
|
||||
regexp = `.*2$`
|
||||
svc, err = NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
|
||||
assert.NoError(t, err)
|
||||
pullRequests, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
|
||||
{
|
||||
BranchMatch: ®exp,
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(pullRequests))
|
||||
assert.Equal(t, PullRequest{
|
||||
Number: 102,
|
||||
Branch: "feature-102",
|
||||
HeadSHA: "6344d9623e3b",
|
||||
}, *pullRequests[0])
|
||||
|
||||
regexp = `[\d{2}`
|
||||
svc, err = NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
|
||||
assert.NoError(t, err)
|
||||
_, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
|
||||
{
|
||||
BranchMatch: ®exp,
|
||||
},
|
||||
})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
@@ -66,10 +66,11 @@ func (b *BitbucketService) List(_ context.Context) ([]*PullRequest, error) {
|
||||
|
||||
for _, pull := range pulls {
|
||||
pullRequests = append(pullRequests, &PullRequest{
|
||||
Number: pull.ID,
|
||||
Branch: pull.FromRef.DisplayID, // ID: refs/heads/main DisplayID: main
|
||||
HeadSHA: pull.FromRef.LatestCommit, // This is not defined in the official docs, but works in practice
|
||||
Labels: []string{}, // Not supported by library
|
||||
Number: pull.ID,
|
||||
Branch: pull.FromRef.DisplayID, // ID: refs/heads/main DisplayID: main
|
||||
TargetBranch: pull.ToRef.DisplayID,
|
||||
HeadSHA: pull.FromRef.LatestCommit, // This is not defined in the official docs, but works in practice
|
||||
Labels: []string{}, // Not supported by library
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,11 @@ func defaultHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
|
||||
"values": [
|
||||
{
|
||||
"id": 101,
|
||||
"toRef": {
|
||||
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
|
||||
"displayId": "master",
|
||||
"id": "refs/heads/master"
|
||||
},
|
||||
"fromRef": {
|
||||
"id": "refs/heads/feature-ABC-123",
|
||||
"displayId": "feature-ABC-123",
|
||||
@@ -55,6 +60,7 @@ func TestListPullRequestNoAuth(t *testing.T) {
|
||||
assert.Equal(t, 1, len(pullRequests))
|
||||
assert.Equal(t, 101, pullRequests[0].Number)
|
||||
assert.Equal(t, "feature-ABC-123", pullRequests[0].Branch)
|
||||
assert.Equal(t, "master", pullRequests[0].TargetBranch)
|
||||
assert.Equal(t, "cb3cf2e4d1517c83e720d2585b9402dbef71f992", pullRequests[0].HeadSHA)
|
||||
}
|
||||
|
||||
@@ -71,6 +77,11 @@ func TestListPullRequestPagination(t *testing.T) {
|
||||
"values": [
|
||||
{
|
||||
"id": 101,
|
||||
"toRef": {
|
||||
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
|
||||
"displayId": "master",
|
||||
"id": "refs/heads/master"
|
||||
},
|
||||
"fromRef": {
|
||||
"id": "refs/heads/feature-101",
|
||||
"displayId": "feature-101",
|
||||
@@ -79,6 +90,11 @@ func TestListPullRequestPagination(t *testing.T) {
|
||||
},
|
||||
{
|
||||
"id": 102,
|
||||
"toRef": {
|
||||
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
|
||||
"displayId": "branch",
|
||||
"id": "refs/heads/branch"
|
||||
},
|
||||
"fromRef": {
|
||||
"id": "refs/heads/feature-102",
|
||||
"displayId": "feature-102",
|
||||
@@ -96,6 +112,11 @@ func TestListPullRequestPagination(t *testing.T) {
|
||||
"values": [
|
||||
{
|
||||
"id": 200,
|
||||
"toRef": {
|
||||
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
|
||||
"displayId": "master",
|
||||
"id": "refs/heads/master"
|
||||
},
|
||||
"fromRef": {
|
||||
"id": "refs/heads/feature-200",
|
||||
"displayId": "feature-200",
|
||||
@@ -119,22 +140,25 @@ func TestListPullRequestPagination(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 3, len(pullRequests))
|
||||
assert.Equal(t, PullRequest{
|
||||
Number: 101,
|
||||
Branch: "feature-101",
|
||||
HeadSHA: "ab3cf2e4d1517c83e720d2585b9402dbef71f992",
|
||||
Labels: []string{},
|
||||
Number: 101,
|
||||
Branch: "feature-101",
|
||||
TargetBranch: "master",
|
||||
HeadSHA: "ab3cf2e4d1517c83e720d2585b9402dbef71f992",
|
||||
Labels: []string{},
|
||||
}, *pullRequests[0])
|
||||
assert.Equal(t, PullRequest{
|
||||
Number: 102,
|
||||
Branch: "feature-102",
|
||||
HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992",
|
||||
Labels: []string{},
|
||||
Number: 102,
|
||||
Branch: "feature-102",
|
||||
TargetBranch: "branch",
|
||||
HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992",
|
||||
Labels: []string{},
|
||||
}, *pullRequests[1])
|
||||
assert.Equal(t, PullRequest{
|
||||
Number: 200,
|
||||
Branch: "feature-200",
|
||||
HeadSHA: "cb3cf2e4d1517c83e720d2585b9402dbef71f992",
|
||||
Labels: []string{},
|
||||
Number: 200,
|
||||
Branch: "feature-200",
|
||||
TargetBranch: "master",
|
||||
HeadSHA: "cb3cf2e4d1517c83e720d2585b9402dbef71f992",
|
||||
Labels: []string{},
|
||||
}, *pullRequests[2])
|
||||
}
|
||||
|
||||
@@ -231,6 +255,11 @@ func TestListPullRequestBranchMatch(t *testing.T) {
|
||||
"values": [
|
||||
{
|
||||
"id": 101,
|
||||
"toRef": {
|
||||
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
|
||||
"displayId": "master",
|
||||
"id": "refs/heads/master"
|
||||
},
|
||||
"fromRef": {
|
||||
"id": "refs/heads/feature-101",
|
||||
"displayId": "feature-101",
|
||||
@@ -239,6 +268,11 @@ func TestListPullRequestBranchMatch(t *testing.T) {
|
||||
},
|
||||
{
|
||||
"id": 102,
|
||||
"toRef": {
|
||||
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
|
||||
"displayId": "branch",
|
||||
"id": "refs/heads/branch"
|
||||
},
|
||||
"fromRef": {
|
||||
"id": "refs/heads/feature-102",
|
||||
"displayId": "feature-102",
|
||||
@@ -256,6 +290,11 @@ func TestListPullRequestBranchMatch(t *testing.T) {
|
||||
"values": [
|
||||
{
|
||||
"id": 200,
|
||||
"toRef": {
|
||||
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
|
||||
"displayId": "master",
|
||||
"id": "refs/heads/master"
|
||||
},
|
||||
"fromRef": {
|
||||
"id": "refs/heads/feature-200",
|
||||
"displayId": "feature-200",
|
||||
@@ -284,16 +323,18 @@ func TestListPullRequestBranchMatch(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, len(pullRequests))
|
||||
assert.Equal(t, PullRequest{
|
||||
Number: 101,
|
||||
Branch: "feature-101",
|
||||
HeadSHA: "ab3cf2e4d1517c83e720d2585b9402dbef71f992",
|
||||
Labels: []string{},
|
||||
Number: 101,
|
||||
Branch: "feature-101",
|
||||
TargetBranch: "master",
|
||||
HeadSHA: "ab3cf2e4d1517c83e720d2585b9402dbef71f992",
|
||||
Labels: []string{},
|
||||
}, *pullRequests[0])
|
||||
assert.Equal(t, PullRequest{
|
||||
Number: 102,
|
||||
Branch: "feature-102",
|
||||
HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992",
|
||||
Labels: []string{},
|
||||
Number: 102,
|
||||
Branch: "feature-102",
|
||||
TargetBranch: "branch",
|
||||
HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992",
|
||||
Labels: []string{},
|
||||
}, *pullRequests[1])
|
||||
|
||||
regexp = `.*2$`
|
||||
@@ -307,10 +348,11 @@ func TestListPullRequestBranchMatch(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(pullRequests))
|
||||
assert.Equal(t, PullRequest{
|
||||
Number: 102,
|
||||
Branch: "feature-102",
|
||||
HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992",
|
||||
Labels: []string{},
|
||||
Number: 102,
|
||||
Branch: "feature-102",
|
||||
TargetBranch: "branch",
|
||||
HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992",
|
||||
Labels: []string{},
|
||||
}, *pullRequests[0])
|
||||
|
||||
regexp = `[\d{2}`
|
||||
|
||||
@@ -26,11 +26,13 @@ func NewGiteaService(ctx context.Context, token, url, owner, repo string, insecu
|
||||
if insecure {
|
||||
cookieJar, _ := cookiejar.New(nil)
|
||||
|
||||
tr := http.DefaultTransport.(*http.Transport).Clone()
|
||||
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
|
||||
httpClient = &http.Client{
|
||||
Jar: cookieJar,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}}
|
||||
Jar: cookieJar,
|
||||
Transport: tr,
|
||||
}
|
||||
}
|
||||
client, err := gitea.NewClient(url, gitea.SetToken(token), gitea.SetHTTPClient(httpClient))
|
||||
if err != nil {
|
||||
@@ -54,10 +56,11 @@ func (g *GiteaService) List(ctx context.Context) ([]*PullRequest, error) {
|
||||
list := []*PullRequest{}
|
||||
for _, pr := range prs {
|
||||
list = append(list, &PullRequest{
|
||||
Number: int(pr.Index),
|
||||
Branch: pr.Head.Ref,
|
||||
HeadSHA: pr.Head.Sha,
|
||||
Labels: getGiteaPRLabelNames(pr.Labels),
|
||||
Number: int(pr.Index),
|
||||
Branch: pr.Head.Ref,
|
||||
TargetBranch: pr.Base.Ref,
|
||||
HeadSHA: pr.Head.Sha,
|
||||
Labels: getGiteaPRLabelNames(pr.Labels),
|
||||
})
|
||||
}
|
||||
return list, nil
|
||||
|
||||
@@ -256,6 +256,7 @@ func TestGiteaList(t *testing.T) {
|
||||
assert.Equal(t, len(prs), 1)
|
||||
assert.Equal(t, prs[0].Number, 1)
|
||||
assert.Equal(t, prs[0].Branch, "test")
|
||||
assert.Equal(t, prs[0].TargetBranch, "main")
|
||||
assert.Equal(t, prs[0].HeadSHA, "7bbaf62d92ddfafd9cc8b340c619abaec32bc09f")
|
||||
}
|
||||
|
||||
|
||||
@@ -65,10 +65,11 @@ func (g *GithubService) List(ctx context.Context) ([]*PullRequest, error) {
|
||||
continue
|
||||
}
|
||||
pullRequests = append(pullRequests, &PullRequest{
|
||||
Number: *pull.Number,
|
||||
Branch: *pull.Head.Ref,
|
||||
HeadSHA: *pull.Head.SHA,
|
||||
Labels: getGithubPRLabelNames(pull.Labels),
|
||||
Number: *pull.Number,
|
||||
Branch: *pull.Head.Ref,
|
||||
TargetBranch: *pull.Base.Ref,
|
||||
HeadSHA: *pull.Head.SHA,
|
||||
Labels: getGithubPRLabelNames(pull.Labels),
|
||||
})
|
||||
}
|
||||
if resp.NextPage == 0 {
|
||||
|
||||
@@ -3,8 +3,11 @@ package pull_request
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/utils"
|
||||
"github.com/hashicorp/go-retryablehttp"
|
||||
gitlab "github.com/xanzy/go-gitlab"
|
||||
)
|
||||
|
||||
@@ -17,7 +20,7 @@ type GitLabService struct {
|
||||
|
||||
var _ PullRequestService = (*GitLabService)(nil)
|
||||
|
||||
func NewGitLabService(ctx context.Context, token, url, project string, labels []string, pullRequestState string) (PullRequestService, error) {
|
||||
func NewGitLabService(ctx context.Context, token, url, project string, labels []string, pullRequestState string, scmRootCAPath string, insecure bool) (PullRequestService, error) {
|
||||
var clientOptionFns []gitlab.ClientOptionFunc
|
||||
|
||||
// Set a custom Gitlab base URL if one is provided
|
||||
@@ -29,6 +32,14 @@ func NewGitLabService(ctx context.Context, token, url, project string, labels []
|
||||
token = os.Getenv("GITLAB_TOKEN")
|
||||
}
|
||||
|
||||
tr := http.DefaultTransport.(*http.Transport).Clone()
|
||||
tr.TLSClientConfig = utils.GetTlsConfig(scmRootCAPath, insecure)
|
||||
|
||||
retryClient := retryablehttp.NewClient()
|
||||
retryClient.HTTPClient.Transport = tr
|
||||
|
||||
clientOptionFns = append(clientOptionFns, gitlab.WithHTTPClient(retryClient.HTTPClient))
|
||||
|
||||
client, err := gitlab.NewClient(token, clientOptionFns...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating Gitlab client: %v", err)
|
||||
@@ -69,10 +80,11 @@ func (g *GitLabService) List(ctx context.Context) ([]*PullRequest, error) {
|
||||
}
|
||||
for _, mr := range mrs {
|
||||
pullRequests = append(pullRequests, &PullRequest{
|
||||
Number: mr.IID,
|
||||
Branch: mr.SourceBranch,
|
||||
HeadSHA: mr.SHA,
|
||||
Labels: mr.Labels,
|
||||
Number: mr.IID,
|
||||
Branch: mr.SourceBranch,
|
||||
TargetBranch: mr.TargetBranch,
|
||||
HeadSHA: mr.SHA,
|
||||
Labels: mr.Labels,
|
||||
})
|
||||
}
|
||||
if resp.NextPage == 0 {
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestGitLabServiceCustomBaseURL(t *testing.T) {
|
||||
writeMRListResponse(t, w)
|
||||
})
|
||||
|
||||
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", nil, "")
|
||||
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", nil, "", "", false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = svc.List(context.Background())
|
||||
@@ -53,7 +53,7 @@ func TestGitLabServiceToken(t *testing.T) {
|
||||
writeMRListResponse(t, w)
|
||||
})
|
||||
|
||||
svc, err := NewGitLabService(context.Background(), "token-123", server.URL, "278964", nil, "")
|
||||
svc, err := NewGitLabService(context.Background(), "token-123", server.URL, "278964", nil, "", "", false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = svc.List(context.Background())
|
||||
@@ -72,7 +72,7 @@ func TestList(t *testing.T) {
|
||||
writeMRListResponse(t, w)
|
||||
})
|
||||
|
||||
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{}, "")
|
||||
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{}, "", "", false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
prs, err := svc.List(context.Background())
|
||||
@@ -80,6 +80,7 @@ func TestList(t *testing.T) {
|
||||
assert.Len(t, prs, 1)
|
||||
assert.Equal(t, prs[0].Number, 15442)
|
||||
assert.Equal(t, prs[0].Branch, "use-structured-logging-for-db-load-balancer")
|
||||
assert.Equal(t, prs[0].TargetBranch, "master")
|
||||
assert.Equal(t, prs[0].HeadSHA, "2fc4e8b972ff3208ec63b6143e34ad67ff343ad7")
|
||||
}
|
||||
|
||||
@@ -95,7 +96,7 @@ func TestListWithLabels(t *testing.T) {
|
||||
writeMRListResponse(t, w)
|
||||
})
|
||||
|
||||
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{"feature", "ready"}, "")
|
||||
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{"feature", "ready"}, "", "", false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = svc.List(context.Background())
|
||||
@@ -114,7 +115,7 @@ func TestListWithState(t *testing.T) {
|
||||
writeMRListResponse(t, w)
|
||||
})
|
||||
|
||||
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{}, "opened")
|
||||
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{}, "opened", "", false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = svc.List(context.Background())
|
||||
|
||||
@@ -10,6 +10,8 @@ type PullRequest struct {
|
||||
Number int
|
||||
// Branch is the name of the branch from which the pull request originated.
|
||||
Branch string
|
||||
// TargetBranch is the name of the target branch of the pull request.
|
||||
TargetBranch string
|
||||
// HeadSHA is the SHA of the HEAD from which the pull request originated.
|
||||
HeadSHA string
|
||||
// Labels of the pull request.
|
||||
@@ -22,5 +24,6 @@ type PullRequestService interface {
|
||||
}
|
||||
|
||||
type Filter struct {
|
||||
BranchMatch *regexp.Regexp
|
||||
BranchMatch *regexp.Regexp
|
||||
TargetBranchMatch *regexp.Regexp
|
||||
}
|
||||
|
||||
@@ -19,6 +19,12 @@ func compileFilters(filters []argoprojiov1alpha1.PullRequestGeneratorFilter) ([]
|
||||
return nil, fmt.Errorf("error compiling BranchMatch regexp %q: %v", *filter.BranchMatch, err)
|
||||
}
|
||||
}
|
||||
if filter.TargetBranchMatch != nil {
|
||||
outFilter.TargetBranchMatch, err = regexp.Compile(*filter.TargetBranchMatch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error compiling TargetBranchMatch regexp %q: %v", *filter.TargetBranchMatch, err)
|
||||
}
|
||||
}
|
||||
outFilters = append(outFilters, outFilter)
|
||||
}
|
||||
return outFilters, nil
|
||||
@@ -28,6 +34,9 @@ func matchFilter(pullRequest *PullRequest, filter *Filter) bool {
|
||||
if filter.BranchMatch != nil && !filter.BranchMatch.MatchString(pullRequest.Branch) {
|
||||
return false
|
||||
}
|
||||
if filter.TargetBranchMatch != nil && !filter.TargetBranchMatch.MatchString(pullRequest.TargetBranch) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -16,9 +16,10 @@ func TestFilterBranchMatchBadRegexp(t *testing.T) {
|
||||
context.Background(),
|
||||
[]*PullRequest{
|
||||
{
|
||||
Number: 1,
|
||||
Branch: "branch1",
|
||||
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
Number: 1,
|
||||
Branch: "branch1",
|
||||
TargetBranch: "master",
|
||||
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
@@ -37,24 +38,28 @@ func TestFilterBranchMatch(t *testing.T) {
|
||||
context.Background(),
|
||||
[]*PullRequest{
|
||||
{
|
||||
Number: 1,
|
||||
Branch: "one",
|
||||
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
Number: 1,
|
||||
Branch: "one",
|
||||
TargetBranch: "master",
|
||||
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
},
|
||||
{
|
||||
Number: 2,
|
||||
Branch: "two",
|
||||
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
Number: 2,
|
||||
Branch: "two",
|
||||
TargetBranch: "master",
|
||||
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
},
|
||||
{
|
||||
Number: 3,
|
||||
Branch: "three",
|
||||
HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
Number: 3,
|
||||
Branch: "three",
|
||||
TargetBranch: "master",
|
||||
HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
},
|
||||
{
|
||||
Number: 4,
|
||||
Branch: "four",
|
||||
HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
Number: 4,
|
||||
Branch: "four",
|
||||
TargetBranch: "master",
|
||||
HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
@@ -70,29 +75,75 @@ func TestFilterBranchMatch(t *testing.T) {
|
||||
assert.Equal(t, "two", pullRequests[0].Branch)
|
||||
}
|
||||
|
||||
func TestFilterTargetBranchMatch(t *testing.T) {
|
||||
provider, _ := NewFakeService(
|
||||
context.Background(),
|
||||
[]*PullRequest{
|
||||
{
|
||||
Number: 1,
|
||||
Branch: "one",
|
||||
TargetBranch: "master",
|
||||
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
},
|
||||
{
|
||||
Number: 2,
|
||||
Branch: "two",
|
||||
TargetBranch: "branch1",
|
||||
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
},
|
||||
{
|
||||
Number: 3,
|
||||
Branch: "three",
|
||||
TargetBranch: "branch2",
|
||||
HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
},
|
||||
{
|
||||
Number: 4,
|
||||
Branch: "four",
|
||||
TargetBranch: "branch3",
|
||||
HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
filters := []argoprojiov1alpha1.PullRequestGeneratorFilter{
|
||||
{
|
||||
TargetBranchMatch: strp("1"),
|
||||
},
|
||||
}
|
||||
pullRequests, err := ListPullRequests(context.Background(), provider, filters)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, pullRequests, 1)
|
||||
assert.Equal(t, "two", pullRequests[0].Branch)
|
||||
}
|
||||
|
||||
func TestMultiFilterOr(t *testing.T) {
|
||||
provider, _ := NewFakeService(
|
||||
context.Background(),
|
||||
[]*PullRequest{
|
||||
{
|
||||
Number: 1,
|
||||
Branch: "one",
|
||||
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
Number: 1,
|
||||
Branch: "one",
|
||||
TargetBranch: "master",
|
||||
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
},
|
||||
{
|
||||
Number: 2,
|
||||
Branch: "two",
|
||||
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
Number: 2,
|
||||
Branch: "two",
|
||||
TargetBranch: "master",
|
||||
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
},
|
||||
{
|
||||
Number: 3,
|
||||
Branch: "three",
|
||||
HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
Number: 3,
|
||||
Branch: "three",
|
||||
TargetBranch: "master",
|
||||
HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
},
|
||||
{
|
||||
Number: 4,
|
||||
Branch: "four",
|
||||
HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
Number: 4,
|
||||
Branch: "four",
|
||||
TargetBranch: "master",
|
||||
HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
@@ -113,19 +164,69 @@ func TestMultiFilterOr(t *testing.T) {
|
||||
assert.Equal(t, "four", pullRequests[2].Branch)
|
||||
}
|
||||
|
||||
func TestMultiFilterOrWithTargetBranchFilter(t *testing.T) {
|
||||
provider, _ := NewFakeService(
|
||||
context.Background(),
|
||||
[]*PullRequest{
|
||||
{
|
||||
Number: 1,
|
||||
Branch: "one",
|
||||
TargetBranch: "master",
|
||||
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
},
|
||||
{
|
||||
Number: 2,
|
||||
Branch: "two",
|
||||
TargetBranch: "branch1",
|
||||
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
},
|
||||
{
|
||||
Number: 3,
|
||||
Branch: "three",
|
||||
TargetBranch: "branch2",
|
||||
HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
},
|
||||
{
|
||||
Number: 4,
|
||||
Branch: "four",
|
||||
TargetBranch: "branch3",
|
||||
HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
filters := []argoprojiov1alpha1.PullRequestGeneratorFilter{
|
||||
{
|
||||
BranchMatch: strp("w"),
|
||||
TargetBranchMatch: strp("1"),
|
||||
},
|
||||
{
|
||||
BranchMatch: strp("r"),
|
||||
TargetBranchMatch: strp("3"),
|
||||
},
|
||||
}
|
||||
pullRequests, err := ListPullRequests(context.Background(), provider, filters)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, pullRequests, 2)
|
||||
assert.Equal(t, "two", pullRequests[0].Branch)
|
||||
assert.Equal(t, "four", pullRequests[1].Branch)
|
||||
}
|
||||
|
||||
func TestNoFilters(t *testing.T) {
|
||||
provider, _ := NewFakeService(
|
||||
context.Background(),
|
||||
[]*PullRequest{
|
||||
{
|
||||
Number: 1,
|
||||
Branch: "one",
|
||||
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
Number: 1,
|
||||
Branch: "one",
|
||||
TargetBranch: "master",
|
||||
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
},
|
||||
{
|
||||
Number: 2,
|
||||
Branch: "two",
|
||||
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
Number: 2,
|
||||
Branch: "two",
|
||||
TargetBranch: "master",
|
||||
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
|
||||
@@ -6,12 +6,13 @@ import (
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
repoapiclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/util/db"
|
||||
"github.com/argoproj/argo-cd/v2/util/git"
|
||||
"github.com/argoproj/argo-cd/v2/util/io"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/vektra/mockery/v2@v2.25.1 --name=RepositoryDB
|
||||
|
||||
// RepositoryDB Is a lean facade for ArgoDB,
|
||||
// Using a lean interface makes it easier to test the functionality of the git generator
|
||||
type RepositoryDB interface {
|
||||
@@ -19,40 +20,46 @@ type RepositoryDB interface {
|
||||
}
|
||||
|
||||
type argoCDService struct {
|
||||
repositoriesDB RepositoryDB
|
||||
storecreds git.CredsStore
|
||||
submoduleEnabled bool
|
||||
repoServerClientSet repoapiclient.Clientset
|
||||
repositoriesDB RepositoryDB
|
||||
storecreds git.CredsStore
|
||||
submoduleEnabled bool
|
||||
repoServerClientSet apiclient.Clientset
|
||||
newFileGlobbingEnabled bool
|
||||
}
|
||||
|
||||
//go:generate go run github.com/vektra/mockery/v2@v2.25.1 --name=Repos
|
||||
|
||||
type Repos interface {
|
||||
|
||||
// GetFiles returns content of files (not directories) within the target repo
|
||||
GetFiles(ctx context.Context, repoURL string, revision string, pattern string) (map[string][]byte, error)
|
||||
GetFiles(ctx context.Context, repoURL string, revision string, pattern string, noRevisionCache bool) (map[string][]byte, error)
|
||||
|
||||
// GetDirectories returns a list of directories (not files) within the target repo
|
||||
GetDirectories(ctx context.Context, repoURL string, revision string) ([]string, error)
|
||||
GetDirectories(ctx context.Context, repoURL string, revision string, noRevisionCache bool) ([]string, error)
|
||||
}
|
||||
|
||||
func NewArgoCDService(db db.ArgoDB, submoduleEnabled bool, repoClientset repoapiclient.Clientset) (Repos, error) {
|
||||
func NewArgoCDService(db db.ArgoDB, submoduleEnabled bool, repoClientset apiclient.Clientset, newFileGlobbingEnabled bool) (Repos, error) {
|
||||
return &argoCDService{
|
||||
repositoriesDB: db.(RepositoryDB),
|
||||
submoduleEnabled: submoduleEnabled,
|
||||
repoServerClientSet: repoClientset,
|
||||
repositoriesDB: db.(RepositoryDB),
|
||||
submoduleEnabled: submoduleEnabled,
|
||||
repoServerClientSet: repoClientset,
|
||||
newFileGlobbingEnabled: newFileGlobbingEnabled,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *argoCDService) GetFiles(ctx context.Context, repoURL string, revision string, pattern string) (map[string][]byte, error) {
|
||||
func (a *argoCDService) GetFiles(ctx context.Context, repoURL string, revision string, pattern string, noRevisionCache bool) (map[string][]byte, error) {
|
||||
repo, err := a.repositoriesDB.GetRepository(ctx, repoURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in GetRepository: %w", err)
|
||||
}
|
||||
|
||||
fileRequest := &apiclient.GitFilesRequest{
|
||||
Repo: repo,
|
||||
SubmoduleEnabled: a.submoduleEnabled,
|
||||
Revision: revision,
|
||||
Path: pattern,
|
||||
Repo: repo,
|
||||
SubmoduleEnabled: a.submoduleEnabled,
|
||||
Revision: revision,
|
||||
Path: pattern,
|
||||
NewGitFileGlobbingEnabled: a.newFileGlobbingEnabled,
|
||||
NoRevisionCache: noRevisionCache,
|
||||
}
|
||||
closer, client, err := a.repoServerClientSet.NewRepoServerClient()
|
||||
if err != nil {
|
||||
@@ -67,7 +74,7 @@ func (a *argoCDService) GetFiles(ctx context.Context, repoURL string, revision s
|
||||
return fileResponse.GetMap(), nil
|
||||
}
|
||||
|
||||
func (a *argoCDService) GetDirectories(ctx context.Context, repoURL string, revision string) ([]string, error) {
|
||||
func (a *argoCDService) GetDirectories(ctx context.Context, repoURL string, revision string, noRevisionCache bool) ([]string, error) {
|
||||
repo, err := a.repositoriesDB.GetRepository(ctx, repoURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in GetRepository: %w", err)
|
||||
@@ -77,6 +84,7 @@ func (a *argoCDService) GetDirectories(ctx context.Context, repoURL string, revi
|
||||
Repo: repo,
|
||||
SubmoduleEnabled: a.submoduleEnabled,
|
||||
Revision: revision,
|
||||
NoRevisionCache: noRevisionCache,
|
||||
}
|
||||
|
||||
closer, client, err := a.repoServerClientSet.NewRepoServerClient()
|
||||
|
||||
@@ -25,9 +25,10 @@ func TestGetDirectories(t *testing.T) {
|
||||
repoServerClientFuncs []func(*repo_mocks.RepoServerServiceClient)
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
repoURL string
|
||||
revision string
|
||||
ctx context.Context
|
||||
repoURL string
|
||||
revision string
|
||||
noRevisionCache bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -88,11 +89,11 @@ func TestGetDirectories(t *testing.T) {
|
||||
submoduleEnabled: tt.fields.submoduleEnabled,
|
||||
repoServerClientSet: &repo_mocks.Clientset{RepoServerServiceClient: mockRepoClient},
|
||||
}
|
||||
got, err := a.GetDirectories(tt.args.ctx, tt.args.repoURL, tt.args.revision)
|
||||
if !tt.wantErr(t, err, fmt.Sprintf("GetDirectories(%v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision)) {
|
||||
got, err := a.GetDirectories(tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.noRevisionCache)
|
||||
if !tt.wantErr(t, err, fmt.Sprintf("GetDirectories(%v, %v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.noRevisionCache)) {
|
||||
return
|
||||
}
|
||||
assert.Equalf(t, tt.want, got, "GetDirectories(%v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision)
|
||||
assert.Equalf(t, tt.want, got, "GetDirectories(%v, %v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.noRevisionCache)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -105,10 +106,11 @@ func TestGetFiles(t *testing.T) {
|
||||
repoServerClientFuncs []func(*repo_mocks.RepoServerServiceClient)
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
repoURL string
|
||||
revision string
|
||||
pattern string
|
||||
ctx context.Context
|
||||
repoURL string
|
||||
revision string
|
||||
pattern string
|
||||
noRevisionCache bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -175,17 +177,17 @@ func TestGetFiles(t *testing.T) {
|
||||
submoduleEnabled: tt.fields.submoduleEnabled,
|
||||
repoServerClientSet: &repo_mocks.Clientset{RepoServerServiceClient: mockRepoClient},
|
||||
}
|
||||
got, err := a.GetFiles(tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern)
|
||||
if !tt.wantErr(t, err, fmt.Sprintf("GetFiles(%v, %v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern)) {
|
||||
got, err := a.GetFiles(tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern, tt.args.noRevisionCache)
|
||||
if !tt.wantErr(t, err, fmt.Sprintf("GetFiles(%v, %v, %v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern, tt.args.noRevisionCache)) {
|
||||
return
|
||||
}
|
||||
assert.Equalf(t, tt.want, got, "GetFiles(%v, %v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern)
|
||||
assert.Equalf(t, tt.want, got, "GetFiles(%v, %v, %v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern, tt.args.noRevisionCache)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewArgoCDService(t *testing.T) {
|
||||
service, err := NewArgoCDService(&db_mocks.ArgoDB{}, false, &repo_mocks.Clientset{})
|
||||
service, err := NewArgoCDService(&db_mocks.ArgoDB{}, false, &repo_mocks.Clientset{}, false)
|
||||
assert.NoError(t, err, err)
|
||||
assert.NotNil(t, service)
|
||||
}
|
||||
|
||||
376
applicationset/services/scm_provider/aws_codecommit.go
Normal file
376
applicationset/services/scm_provider/aws_codecommit.go
Normal file
@@ -0,0 +1,376 @@
|
||||
package scm_provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
pathpkg "path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
application "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"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/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"
|
||||
)
|
||||
|
||||
const (
|
||||
resourceTypeCodeCommitRepository = "codecommit:repository"
|
||||
prefixGitUrlHttps = "https://git-codecommit."
|
||||
prefixGitUrlHttpsFIPS = "https://git-codecommit-fips."
|
||||
)
|
||||
|
||||
// AWSCodeCommitClient is a lean facade to the codecommitiface.CodeCommitAPI
|
||||
// it helps to reduce the mockery generated code.
|
||||
type AWSCodeCommitClient interface {
|
||||
ListRepositoriesWithContext(aws.Context, *codecommit.ListRepositoriesInput, ...request.Option) (*codecommit.ListRepositoriesOutput, error)
|
||||
GetRepositoryWithContext(aws.Context, *codecommit.GetRepositoryInput, ...request.Option) (*codecommit.GetRepositoryOutput, error)
|
||||
ListBranchesWithContext(aws.Context, *codecommit.ListBranchesInput, ...request.Option) (*codecommit.ListBranchesOutput, error)
|
||||
GetFolderWithContext(aws.Context, *codecommit.GetFolderInput, ...request.Option) (*codecommit.GetFolderOutput, error)
|
||||
}
|
||||
|
||||
// AWSTaggingClient is a lean facade to the resourcegroupstaggingapiiface.ResourceGroupsTaggingAPIAPI
|
||||
// it helps to reduce the mockery generated code.
|
||||
type AWSTaggingClient interface {
|
||||
GetResourcesWithContext(aws.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error)
|
||||
}
|
||||
|
||||
type AWSCodeCommitProvider struct {
|
||||
codeCommitClient AWSCodeCommitClient
|
||||
taggingClient AWSTaggingClient
|
||||
tagFilters []*application.TagFilter
|
||||
allBranches bool
|
||||
}
|
||||
|
||||
func NewAWSCodeCommitProvider(ctx context.Context, tagFilters []*application.TagFilter, role string, region string, allBranches bool) (*AWSCodeCommitProvider, error) {
|
||||
taggingClient, codeCommitClient, err := createAWSDiscoveryClients(ctx, role, region)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &AWSCodeCommitProvider{
|
||||
codeCommitClient: codeCommitClient,
|
||||
taggingClient: taggingClient,
|
||||
tagFilters: tagFilters,
|
||||
allBranches: allBranches,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *AWSCodeCommitProvider) ListRepos(ctx context.Context, cloneProtocol string) ([]*Repository, error) {
|
||||
repos := make([]*Repository, 0)
|
||||
|
||||
repoNames, err := p.listRepoNames(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list codecommit repository: %w", err)
|
||||
}
|
||||
|
||||
for _, repoName := range repoNames {
|
||||
repo, err := p.codeCommitClient.GetRepositoryWithContext(ctx, &codecommit.GetRepositoryInput{
|
||||
RepositoryName: aws.String(repoName),
|
||||
})
|
||||
if err != nil {
|
||||
// we don't want to skip at this point. It's a valid repo, we don't want to have flapping Application on an AWS outage.
|
||||
return nil, fmt.Errorf("failed to get codecommit repository: %w", err)
|
||||
}
|
||||
if repo == nil || repo.RepositoryMetadata == nil {
|
||||
// unlikely to happen, but just in case to protect nil pointer dereferences.
|
||||
log.Warnf("codecommit returned invalid response for repository %s, skipped", repoName)
|
||||
continue
|
||||
}
|
||||
if aws.StringValue(repo.RepositoryMetadata.DefaultBranch) == "" {
|
||||
// if a codecommit repo doesn't have default branch, it's uninitialized. not going to bother with it.
|
||||
log.Warnf("repository %s does not have default branch, skipped", repoName)
|
||||
continue
|
||||
}
|
||||
var url string
|
||||
switch cloneProtocol {
|
||||
// default to SSH if unspecified (i.e. if "").
|
||||
case "", "ssh":
|
||||
url = aws.StringValue(repo.RepositoryMetadata.CloneUrlSsh)
|
||||
case "https":
|
||||
url = aws.StringValue(repo.RepositoryMetadata.CloneUrlHttp)
|
||||
case "https-fips":
|
||||
url, err = getCodeCommitFIPSEndpoint(aws.StringValue(repo.RepositoryMetadata.CloneUrlHttp))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("https-fips is provided but repoUrl can't be transformed to FIPS endpoint: %w", err)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown clone protocol for codecommit %v", cloneProtocol)
|
||||
}
|
||||
repos = append(repos, &Repository{
|
||||
// there's no "organization" level at codecommit.
|
||||
// we are just using AWS accountId for now.
|
||||
Organization: aws.StringValue(repo.RepositoryMetadata.AccountId),
|
||||
Repository: aws.StringValue(repo.RepositoryMetadata.RepositoryName),
|
||||
URL: url,
|
||||
Branch: aws.StringValue(repo.RepositoryMetadata.DefaultBranch),
|
||||
// we could propagate repo tag keys, but without value not sure if it's any useful.
|
||||
Labels: []string{},
|
||||
RepositoryId: aws.StringValue(repo.RepositoryMetadata.RepositoryId),
|
||||
})
|
||||
}
|
||||
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
func (p *AWSCodeCommitProvider) RepoHasPath(ctx context.Context, repo *Repository, path string) (bool, error) {
|
||||
// we use GetFolder instead of GetFile here because GetFile always downloads the full blob which has scalability problem.
|
||||
// GetFolder is slightly less concerning.
|
||||
|
||||
path = toAbsolutePath(path)
|
||||
// shortcut: if it's root folder ('/'), we always return true.
|
||||
if path == "/" {
|
||||
return true, nil
|
||||
}
|
||||
// here we are sure it's not root folder, strip the suffix for easier comparison.
|
||||
path = strings.TrimSuffix(path, "/")
|
||||
|
||||
// we always get the parent folder, so we could support both submodule, file, symlink and folder cases.
|
||||
parentPath := pathpkg.Dir(path)
|
||||
basePath := pathpkg.Base(path)
|
||||
|
||||
input := &codecommit.GetFolderInput{
|
||||
CommitSpecifier: aws.String(repo.Branch),
|
||||
FolderPath: aws.String(parentPath),
|
||||
RepositoryName: aws.String(repo.Repository),
|
||||
}
|
||||
output, err := p.codeCommitClient.GetFolderWithContext(ctx, input)
|
||||
if err != nil {
|
||||
if hasAwsError(err,
|
||||
codecommit.ErrCodeRepositoryDoesNotExistException,
|
||||
codecommit.ErrCodeCommitDoesNotExistException,
|
||||
codecommit.ErrCodeFolderDoesNotExistException,
|
||||
) {
|
||||
return false, nil
|
||||
}
|
||||
// unhandled exception, propagate out
|
||||
return false, err
|
||||
}
|
||||
|
||||
// anything that matches.
|
||||
for _, submodule := range output.SubModules {
|
||||
if basePath == aws.StringValue(submodule.RelativePath) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
for _, subpath := range output.SubFolders {
|
||||
if basePath == aws.StringValue(subpath.RelativePath) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
for _, subpath := range output.Files {
|
||||
if basePath == aws.StringValue(subpath.RelativePath) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
for _, subpath := range output.SymbolicLinks {
|
||||
if basePath == aws.StringValue(subpath.RelativePath) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (p *AWSCodeCommitProvider) GetBranches(ctx context.Context, repo *Repository) ([]*Repository, error) {
|
||||
repos := make([]*Repository, 0)
|
||||
if !p.allBranches {
|
||||
output, err := p.codeCommitClient.GetRepositoryWithContext(ctx, &codecommit.GetRepositoryInput{
|
||||
RepositoryName: aws.String(repo.Repository),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repos = append(repos, &Repository{
|
||||
Organization: repo.Organization,
|
||||
Repository: repo.Repository,
|
||||
URL: repo.URL,
|
||||
Branch: aws.StringValue(output.RepositoryMetadata.DefaultBranch),
|
||||
RepositoryId: repo.RepositoryId,
|
||||
Labels: repo.Labels,
|
||||
// getting SHA of the branch requires a separate GetBranch call.
|
||||
// too expensive. for now, we just don't support it.
|
||||
// SHA: "",
|
||||
})
|
||||
} else {
|
||||
input := &codecommit.ListBranchesInput{
|
||||
RepositoryName: aws.String(repo.Repository),
|
||||
}
|
||||
for {
|
||||
output, err := p.codeCommitClient.ListBranchesWithContext(ctx, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, branch := range output.Branches {
|
||||
repos = append(repos, &Repository{
|
||||
Organization: repo.Organization,
|
||||
Repository: repo.Repository,
|
||||
URL: repo.URL,
|
||||
Branch: aws.StringValue(branch),
|
||||
RepositoryId: repo.RepositoryId,
|
||||
Labels: repo.Labels,
|
||||
// getting SHA of the branch requires a separate GetBranch call.
|
||||
// too expensive. for now, we just don't support it.
|
||||
// SHA: "",
|
||||
})
|
||||
}
|
||||
input.NextToken = output.NextToken
|
||||
if aws.StringValue(output.NextToken) == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
func (p *AWSCodeCommitProvider) listRepoNames(ctx context.Context) ([]string, error) {
|
||||
tagFilters := p.getTagFilters()
|
||||
repoNames := make([]string, 0)
|
||||
var err error
|
||||
|
||||
if len(tagFilters) < 1 {
|
||||
log.Debugf("no tag filer, calling codecommit api to list repos")
|
||||
listReposInput := &codecommit.ListRepositoriesInput{}
|
||||
var output *codecommit.ListRepositoriesOutput
|
||||
for {
|
||||
output, err = p.codeCommitClient.ListRepositoriesWithContext(ctx, listReposInput)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
for _, repo := range output.Repositories {
|
||||
repoNames = append(repoNames, aws.StringValue(repo.RepositoryName))
|
||||
}
|
||||
listReposInput.NextToken = output.NextToken
|
||||
if aws.StringValue(output.NextToken) == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Debugf("tag filer is specified, calling tagging api to list repos")
|
||||
discoveryInput := &resourcegroupstaggingapi.GetResourcesInput{
|
||||
ResourceTypeFilters: aws.StringSlice([]string{resourceTypeCodeCommitRepository}),
|
||||
TagFilters: tagFilters,
|
||||
}
|
||||
var output *resourcegroupstaggingapi.GetResourcesOutput
|
||||
for {
|
||||
output, err = p.taggingClient.GetResourcesWithContext(ctx, discoveryInput)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
for _, resource := range output.ResourceTagMappingList {
|
||||
repoArn := aws.StringValue(resource.ResourceARN)
|
||||
log.Debugf("discovered codecommit repo with arn %s", repoArn)
|
||||
repoName, extractErr := getCodeCommitRepoName(repoArn)
|
||||
if extractErr != nil {
|
||||
log.Warnf("discovered codecommit repoArn %s cannot be parsed due to %v", repoArn, err)
|
||||
continue
|
||||
}
|
||||
repoNames = append(repoNames, repoName)
|
||||
}
|
||||
discoveryInput.PaginationToken = output.PaginationToken
|
||||
if aws.StringValue(output.PaginationToken) == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return repoNames, err
|
||||
}
|
||||
|
||||
func (p *AWSCodeCommitProvider) getTagFilters() []*resourcegroupstaggingapi.TagFilter {
|
||||
filters := make(map[string]*resourcegroupstaggingapi.TagFilter)
|
||||
for _, tagFilter := range p.tagFilters {
|
||||
filter, hasKey := filters[tagFilter.Key]
|
||||
if !hasKey {
|
||||
filter = &resourcegroupstaggingapi.TagFilter{
|
||||
Key: aws.String(tagFilter.Key),
|
||||
}
|
||||
filters[tagFilter.Key] = filter
|
||||
}
|
||||
if tagFilter.Value != "" {
|
||||
filter.Values = append(filter.Values, aws.String(tagFilter.Value))
|
||||
}
|
||||
}
|
||||
return maps.Values(filters)
|
||||
}
|
||||
|
||||
func getCodeCommitRepoName(repoArn string) (string, error) {
|
||||
parsedArn, err := arn.Parse(repoArn)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse codecommit repository ARN: %w", err)
|
||||
}
|
||||
// see: https://docs.aws.amazon.com/codecommit/latest/userguide/auth-and-access-control-permissions-reference.html
|
||||
// arn:aws:codecommit:region:account-id:repository-name
|
||||
return parsedArn.Resource, nil
|
||||
}
|
||||
|
||||
// getCodeCommitFIPSEndpoint transforms provided https:// codecommit URL to a FIPS-compliant endpoint.
|
||||
// note that the specified region must support FIPS, otherwise the returned URL won't be reachable
|
||||
// see: https://docs.aws.amazon.com/codecommit/latest/userguide/regions.html#regions-git
|
||||
func getCodeCommitFIPSEndpoint(repoUrl string) (string, error) {
|
||||
if strings.HasPrefix(repoUrl, prefixGitUrlHttpsFIPS) {
|
||||
log.Debugf("provided repoUrl %s is already a fips endpoint", repoUrl)
|
||||
return repoUrl, nil
|
||||
}
|
||||
if !strings.HasPrefix(repoUrl, prefixGitUrlHttps) {
|
||||
return "", fmt.Errorf("the provided https endpoint isn't recognized, cannot be transformed to FIPS endpoint: %s", repoUrl)
|
||||
}
|
||||
// we already have the prefix, so we guarantee to replace exactly the prefix only.
|
||||
return strings.Replace(repoUrl, prefixGitUrlHttps, prefixGitUrlHttpsFIPS, 1), nil
|
||||
}
|
||||
|
||||
func hasAwsError(err error, codes ...string) bool {
|
||||
if awsErr, ok := err.(awserr.Error); ok {
|
||||
return slices.Contains(codes, awsErr.Code())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// toAbsolutePath transforms a path input to absolute path, as required by AWS CodeCommit
|
||||
// see https://docs.aws.amazon.com/codecommit/latest/APIReference/API_GetFolder.html
|
||||
func toAbsolutePath(path string) string {
|
||||
if filepath.IsAbs(path) {
|
||||
return path
|
||||
}
|
||||
return filepath.ToSlash(filepath.Join("/", path))
|
||||
}
|
||||
|
||||
func createAWSDiscoveryClients(_ context.Context, role string, region string) (*resourcegroupstaggingapi.ResourceGroupsTaggingAPI, *codecommit.CodeCommit, error) {
|
||||
podSession, err := session.NewSession()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error creating new AWS pod session: %w", err)
|
||||
}
|
||||
discoverySession := podSession
|
||||
// assume role if provided - this allows cross account CodeCommit repo discovery.
|
||||
if role != "" {
|
||||
log.Debugf("role %s is provided for AWS CodeCommit discovery", role)
|
||||
assumeRoleCreds := stscreds.NewCredentials(podSession, role)
|
||||
discoverySession, err = session.NewSession(&aws.Config{
|
||||
Credentials: assumeRoleCreds,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error creating new AWS discovery session: %s", err)
|
||||
}
|
||||
} else {
|
||||
log.Debugf("role is not provided for AWS CodeCommit discovery, using pod role")
|
||||
}
|
||||
// use region explicitly if provided - this allows cross region CodeCommit repo discovery.
|
||||
if region != "" {
|
||||
log.Debugf("region %s is provided for AWS CodeCommit discovery", region)
|
||||
discoverySession = discoverySession.Copy(&aws.Config{
|
||||
Region: aws.String(region),
|
||||
})
|
||||
} else {
|
||||
log.Debugf("region is not provided for AWS CodeCommit discovery, using pod region")
|
||||
}
|
||||
|
||||
taggingClient := resourcegroupstaggingapi.New(discoverySession)
|
||||
codeCommitClient := codecommit.New(discoverySession)
|
||||
|
||||
return taggingClient, codeCommitClient, nil
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
// Code generated by mockery v2.26.1. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
codecommit "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
|
||||
}
|
||||
|
||||
type AWSCodeCommitClient_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *AWSCodeCommitClient) EXPECT() *AWSCodeCommitClient_Expecter {
|
||||
return &AWSCodeCommitClient_Expecter{mock: &_m.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...)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
// - _a0 context.Context
|
||||
// - _a1 *codecommit.GetFolderInput
|
||||
// - _a2 ...request.Option
|
||||
func (_e *AWSCodeCommitClient_Expecter) GetFolderWithContext(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *AWSCodeCommitClient_GetFolderWithContext_Call {
|
||||
return &AWSCodeCommitClient_GetFolderWithContext_Call{Call: _e.mock.On("GetFolderWithContext",
|
||||
append([]interface{}{_a0, _a1}, _a2...)...)}
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_GetFolderWithContext_Call) Run(run func(_a0 context.Context, _a1 *codecommit.GetFolderInput, _a2 ...request.Option)) *AWSCodeCommitClient_GetFolderWithContext_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
variadicArgs := make([]request.Option, len(args)-2)
|
||||
for i, a := range args[2:] {
|
||||
if a != nil {
|
||||
variadicArgs[i] = a.(request.Option)
|
||||
}
|
||||
}
|
||||
run(args[0].(context.Context), args[1].(*codecommit.GetFolderInput), variadicArgs...)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_GetFolderWithContext_Call) Return(_a0 *codecommit.GetFolderOutput, _a1 error) *AWSCodeCommitClient_GetFolderWithContext_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_GetFolderWithContext_Call) RunAndReturn(run func(context.Context, *codecommit.GetFolderInput, ...request.Option) (*codecommit.GetFolderOutput, error)) *AWSCodeCommitClient_GetFolderWithContext_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// 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...)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
// - _a0 context.Context
|
||||
// - _a1 *codecommit.GetRepositoryInput
|
||||
// - _a2 ...request.Option
|
||||
func (_e *AWSCodeCommitClient_Expecter) GetRepositoryWithContext(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
|
||||
return &AWSCodeCommitClient_GetRepositoryWithContext_Call{Call: _e.mock.On("GetRepositoryWithContext",
|
||||
append([]interface{}{_a0, _a1}, _a2...)...)}
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_GetRepositoryWithContext_Call) Run(run func(_a0 context.Context, _a1 *codecommit.GetRepositoryInput, _a2 ...request.Option)) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
variadicArgs := make([]request.Option, len(args)-2)
|
||||
for i, a := range args[2:] {
|
||||
if a != nil {
|
||||
variadicArgs[i] = a.(request.Option)
|
||||
}
|
||||
}
|
||||
run(args[0].(context.Context), args[1].(*codecommit.GetRepositoryInput), variadicArgs...)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_GetRepositoryWithContext_Call) Return(_a0 *codecommit.GetRepositoryOutput, _a1 error) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_GetRepositoryWithContext_Call) RunAndReturn(run func(context.Context, *codecommit.GetRepositoryInput, ...request.Option) (*codecommit.GetRepositoryOutput, error)) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// 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...)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
// - _a0 context.Context
|
||||
// - _a1 *codecommit.ListBranchesInput
|
||||
// - _a2 ...request.Option
|
||||
func (_e *AWSCodeCommitClient_Expecter) ListBranchesWithContext(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *AWSCodeCommitClient_ListBranchesWithContext_Call {
|
||||
return &AWSCodeCommitClient_ListBranchesWithContext_Call{Call: _e.mock.On("ListBranchesWithContext",
|
||||
append([]interface{}{_a0, _a1}, _a2...)...)}
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_ListBranchesWithContext_Call) Run(run func(_a0 context.Context, _a1 *codecommit.ListBranchesInput, _a2 ...request.Option)) *AWSCodeCommitClient_ListBranchesWithContext_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
variadicArgs := make([]request.Option, len(args)-2)
|
||||
for i, a := range args[2:] {
|
||||
if a != nil {
|
||||
variadicArgs[i] = a.(request.Option)
|
||||
}
|
||||
}
|
||||
run(args[0].(context.Context), args[1].(*codecommit.ListBranchesInput), variadicArgs...)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_ListBranchesWithContext_Call) Return(_a0 *codecommit.ListBranchesOutput, _a1 error) *AWSCodeCommitClient_ListBranchesWithContext_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_ListBranchesWithContext_Call) RunAndReturn(run func(context.Context, *codecommit.ListBranchesInput, ...request.Option) (*codecommit.ListBranchesOutput, error)) *AWSCodeCommitClient_ListBranchesWithContext_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// 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...)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
// - _a0 context.Context
|
||||
// - _a1 *codecommit.ListRepositoriesInput
|
||||
// - _a2 ...request.Option
|
||||
func (_e *AWSCodeCommitClient_Expecter) ListRepositoriesWithContext(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
|
||||
return &AWSCodeCommitClient_ListRepositoriesWithContext_Call{Call: _e.mock.On("ListRepositoriesWithContext",
|
||||
append([]interface{}{_a0, _a1}, _a2...)...)}
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_ListRepositoriesWithContext_Call) Run(run func(_a0 context.Context, _a1 *codecommit.ListRepositoriesInput, _a2 ...request.Option)) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
variadicArgs := make([]request.Option, len(args)-2)
|
||||
for i, a := range args[2:] {
|
||||
if a != nil {
|
||||
variadicArgs[i] = a.(request.Option)
|
||||
}
|
||||
}
|
||||
run(args[0].(context.Context), args[1].(*codecommit.ListRepositoriesInput), variadicArgs...)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_ListRepositoriesWithContext_Call) Return(_a0 *codecommit.ListRepositoriesOutput, _a1 error) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AWSCodeCommitClient_ListRepositoriesWithContext_Call) RunAndReturn(run func(context.Context, *codecommit.ListRepositoriesInput, ...request.Option) (*codecommit.ListRepositoriesOutput, error)) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
type mockConstructorTestingTNewAWSCodeCommitClient interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}
|
||||
|
||||
// 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.
|
||||
func NewAWSCodeCommitClient(t mockConstructorTestingTNewAWSCodeCommitClient) *AWSCodeCommitClient {
|
||||
mock := &AWSCodeCommitClient{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// Code generated by mockery v2.26.1. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
request "github.com/aws/aws-sdk-go/aws/request"
|
||||
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
|
||||
}
|
||||
|
||||
type AWSTaggingClient_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *AWSTaggingClient) EXPECT() *AWSTaggingClient_Expecter {
|
||||
return &AWSTaggingClient_Expecter{mock: &_m.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...)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
// - _a0 context.Context
|
||||
// - _a1 *resourcegroupstaggingapi.GetResourcesInput
|
||||
// - _a2 ...request.Option
|
||||
func (_e *AWSTaggingClient_Expecter) GetResourcesWithContext(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *AWSTaggingClient_GetResourcesWithContext_Call {
|
||||
return &AWSTaggingClient_GetResourcesWithContext_Call{Call: _e.mock.On("GetResourcesWithContext",
|
||||
append([]interface{}{_a0, _a1}, _a2...)...)}
|
||||
}
|
||||
|
||||
func (_c *AWSTaggingClient_GetResourcesWithContext_Call) Run(run func(_a0 context.Context, _a1 *resourcegroupstaggingapi.GetResourcesInput, _a2 ...request.Option)) *AWSTaggingClient_GetResourcesWithContext_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
variadicArgs := make([]request.Option, len(args)-2)
|
||||
for i, a := range args[2:] {
|
||||
if a != nil {
|
||||
variadicArgs[i] = a.(request.Option)
|
||||
}
|
||||
}
|
||||
run(args[0].(context.Context), args[1].(*resourcegroupstaggingapi.GetResourcesInput), variadicArgs...)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AWSTaggingClient_GetResourcesWithContext_Call) Return(_a0 *resourcegroupstaggingapi.GetResourcesOutput, _a1 error) *AWSTaggingClient_GetResourcesWithContext_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AWSTaggingClient_GetResourcesWithContext_Call) RunAndReturn(run func(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error)) *AWSTaggingClient_GetResourcesWithContext_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
type mockConstructorTestingTNewAWSTaggingClient interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}
|
||||
|
||||
// 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.
|
||||
func NewAWSTaggingClient(t mockConstructorTestingTNewAWSTaggingClient) *AWSTaggingClient {
|
||||
mock := &AWSTaggingClient{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
483
applicationset/services/scm_provider/aws_codecommit_test.go
Normal file
483
applicationset/services/scm_provider/aws_codecommit_test.go
Normal file
@@ -0,0 +1,483 @@
|
||||
package scm_provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider/aws_codecommit/mocks"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/codecommit"
|
||||
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type awsCodeCommitTestRepository struct {
|
||||
name string
|
||||
id string
|
||||
arn string
|
||||
accountId string
|
||||
defaultBranch string
|
||||
expectedCloneUrl string
|
||||
getRepositoryError error
|
||||
getRepositoryNilMetadata bool
|
||||
valid bool
|
||||
}
|
||||
|
||||
func TestAWSCodeCommitListRepos(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
repositories []*awsCodeCommitTestRepository
|
||||
cloneProtocol string
|
||||
tagFilters []*v1alpha1.TagFilter
|
||||
expectTagFilters []*resourcegroupstaggingapi.TagFilter
|
||||
listRepositoryError error
|
||||
expectOverallError bool
|
||||
expectListAtCodeCommit bool
|
||||
}{
|
||||
{
|
||||
name: "ListRepos by tag with https",
|
||||
cloneProtocol: "https",
|
||||
repositories: []*awsCodeCommitTestRepository{
|
||||
{
|
||||
name: "repo1",
|
||||
id: "8235624d-d248-4df9-a983-2558b01dbe83",
|
||||
arn: "arn:aws:codecommit:us-east-1:111111111111:repo1",
|
||||
defaultBranch: "main",
|
||||
expectedCloneUrl: "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/repo1",
|
||||
valid: true,
|
||||
},
|
||||
},
|
||||
tagFilters: []*v1alpha1.TagFilter{
|
||||
{Key: "key1", Value: "value1"},
|
||||
{Key: "key1", Value: "value2"},
|
||||
{Key: "key2"},
|
||||
},
|
||||
expectTagFilters: []*resourcegroupstaggingapi.TagFilter{
|
||||
{Key: aws.String("key1"), Values: aws.StringSlice([]string{"value1", "value2"})},
|
||||
{Key: aws.String("key2")},
|
||||
},
|
||||
expectOverallError: false,
|
||||
expectListAtCodeCommit: false,
|
||||
},
|
||||
{
|
||||
name: "ListRepos by tag with https-fips",
|
||||
cloneProtocol: "https-fips",
|
||||
repositories: []*awsCodeCommitTestRepository{
|
||||
{
|
||||
name: "repo1",
|
||||
id: "8235624d-d248-4df9-a983-2558b01dbe83",
|
||||
arn: "arn:aws:codecommit:us-east-1:111111111111:repo1",
|
||||
defaultBranch: "main",
|
||||
expectedCloneUrl: "https://git-codecommit-fips.us-east-1.amazonaws.com/v1/repos/repo1",
|
||||
valid: true,
|
||||
},
|
||||
},
|
||||
tagFilters: []*v1alpha1.TagFilter{
|
||||
{Key: "key1"},
|
||||
},
|
||||
expectTagFilters: []*resourcegroupstaggingapi.TagFilter{
|
||||
{Key: aws.String("key1")},
|
||||
},
|
||||
expectOverallError: false,
|
||||
expectListAtCodeCommit: false,
|
||||
},
|
||||
{
|
||||
name: "ListRepos without tag with invalid repo",
|
||||
cloneProtocol: "ssh",
|
||||
repositories: []*awsCodeCommitTestRepository{
|
||||
{
|
||||
name: "repo1",
|
||||
id: "8235624d-d248-4df9-a983-2558b01dbe83",
|
||||
arn: "arn:aws:codecommit:us-east-1:111111111111:repo1",
|
||||
defaultBranch: "main",
|
||||
expectedCloneUrl: "ssh://git-codecommit.us-east-1.amazonaws.com/v1/repos/repo1",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "repo2",
|
||||
id: "640d5859-d265-4e27-a9fa-e0731eb13ed7",
|
||||
arn: "arn:aws:codecommit:us-east-1:111111111111:repo2",
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
name: "repo3-nil-metadata",
|
||||
id: "24a6ee96-d3a0-4be6-a595-c5e5b1ab1617",
|
||||
arn: "arn:aws:codecommit:us-east-1:111111111111:repo3-nil-metadata",
|
||||
getRepositoryNilMetadata: true,
|
||||
valid: false,
|
||||
},
|
||||
},
|
||||
expectOverallError: false,
|
||||
expectListAtCodeCommit: true,
|
||||
},
|
||||
{
|
||||
name: "ListRepos with invalid protocol",
|
||||
cloneProtocol: "invalid-protocol",
|
||||
repositories: []*awsCodeCommitTestRepository{
|
||||
{
|
||||
name: "repo1",
|
||||
id: "8235624d-d248-4df9-a983-2558b01dbe83",
|
||||
arn: "arn:aws:codecommit:us-east-1:111111111111:repo1",
|
||||
defaultBranch: "main",
|
||||
valid: true,
|
||||
},
|
||||
},
|
||||
expectOverallError: true,
|
||||
expectListAtCodeCommit: true,
|
||||
},
|
||||
{
|
||||
name: "ListRepos error on listRepos",
|
||||
cloneProtocol: "https",
|
||||
listRepositoryError: errors.New("list repo error"),
|
||||
expectOverallError: true,
|
||||
expectListAtCodeCommit: true,
|
||||
},
|
||||
{
|
||||
name: "ListRepos error on getRepo",
|
||||
cloneProtocol: "https",
|
||||
repositories: []*awsCodeCommitTestRepository{
|
||||
{
|
||||
name: "repo1",
|
||||
id: "8235624d-d248-4df9-a983-2558b01dbe83",
|
||||
arn: "arn:aws:codecommit:us-east-1:111111111111:repo1",
|
||||
defaultBranch: "main",
|
||||
getRepositoryError: errors.New("get repo error"),
|
||||
valid: true,
|
||||
},
|
||||
},
|
||||
expectOverallError: true,
|
||||
expectListAtCodeCommit: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
codeCommitClient := mocks.NewAWSCodeCommitClient(t)
|
||||
taggingClient := mocks.NewAWSTaggingClient(t)
|
||||
ctx := context.Background()
|
||||
codecommitRepoNameIdPairs := make([]*codecommit.RepositoryNameIdPair, 0)
|
||||
resourceTaggings := make([]*resourcegroupstaggingapi.ResourceTagMapping, 0)
|
||||
validRepositories := make([]*awsCodeCommitTestRepository, 0)
|
||||
|
||||
for _, repo := range testCase.repositories {
|
||||
repoMetadata := &codecommit.RepositoryMetadata{
|
||||
AccountId: aws.String(repo.accountId),
|
||||
Arn: aws.String(repo.arn),
|
||||
CloneUrlHttp: aws.String("https://git-codecommit.us-east-1.amazonaws.com/v1/repos/" + repo.name),
|
||||
CloneUrlSsh: aws.String("ssh://git-codecommit.us-east-1.amazonaws.com/v1/repos/" + repo.name),
|
||||
DefaultBranch: aws.String(repo.defaultBranch),
|
||||
RepositoryId: aws.String(repo.id),
|
||||
RepositoryName: aws.String(repo.name),
|
||||
}
|
||||
if repo.getRepositoryNilMetadata {
|
||||
repoMetadata = nil
|
||||
}
|
||||
codeCommitClient.EXPECT().
|
||||
GetRepositoryWithContext(ctx, &codecommit.GetRepositoryInput{RepositoryName: aws.String(repo.name)}).
|
||||
Return(&codecommit.GetRepositoryOutput{RepositoryMetadata: repoMetadata}, repo.getRepositoryError)
|
||||
codecommitRepoNameIdPairs = append(codecommitRepoNameIdPairs, &codecommit.RepositoryNameIdPair{
|
||||
RepositoryId: aws.String(repo.id),
|
||||
RepositoryName: aws.String(repo.name),
|
||||
})
|
||||
resourceTaggings = append(resourceTaggings, &resourcegroupstaggingapi.ResourceTagMapping{
|
||||
ResourceARN: aws.String(repo.arn),
|
||||
})
|
||||
if repo.valid {
|
||||
validRepositories = append(validRepositories, repo)
|
||||
}
|
||||
}
|
||||
|
||||
if testCase.expectListAtCodeCommit {
|
||||
codeCommitClient.EXPECT().
|
||||
ListRepositoriesWithContext(ctx, &codecommit.ListRepositoriesInput{}).
|
||||
Return(&codecommit.ListRepositoriesOutput{
|
||||
Repositories: codecommitRepoNameIdPairs,
|
||||
}, testCase.listRepositoryError)
|
||||
} else {
|
||||
taggingClient.EXPECT().
|
||||
GetResourcesWithContext(ctx, mock.MatchedBy(equalIgnoringTagFilterOrder(&resourcegroupstaggingapi.GetResourcesInput{
|
||||
TagFilters: testCase.expectTagFilters,
|
||||
ResourceTypeFilters: aws.StringSlice([]string{resourceTypeCodeCommitRepository}),
|
||||
}))).
|
||||
Return(&resourcegroupstaggingapi.GetResourcesOutput{
|
||||
ResourceTagMappingList: resourceTaggings,
|
||||
}, testCase.listRepositoryError)
|
||||
}
|
||||
|
||||
provider := &AWSCodeCommitProvider{
|
||||
codeCommitClient: codeCommitClient,
|
||||
taggingClient: taggingClient,
|
||||
tagFilters: testCase.tagFilters,
|
||||
}
|
||||
repos, err := provider.ListRepos(ctx, testCase.cloneProtocol)
|
||||
if testCase.expectOverallError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.Len(t, repos, len(validRepositories))
|
||||
for i, repo := range repos {
|
||||
originRepo := validRepositories[i]
|
||||
assert.Equal(t, originRepo.accountId, repo.Organization)
|
||||
assert.Equal(t, originRepo.name, repo.Repository)
|
||||
assert.Equal(t, originRepo.id, repo.RepositoryId)
|
||||
assert.Equal(t, originRepo.defaultBranch, repo.Branch)
|
||||
assert.Equal(t, originRepo.expectedCloneUrl, repo.URL)
|
||||
assert.Empty(t, repo.SHA, "SHA is always empty")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAWSCodeCommitRepoHasPath(t *testing.T) {
|
||||
organization := "111111111111"
|
||||
repoName := "repo1"
|
||||
branch := "main"
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
path string
|
||||
expectedGetFolderPath string
|
||||
getFolderOutput *codecommit.GetFolderOutput
|
||||
getFolderError error
|
||||
expectOverallError bool
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "RepoHasPath on regular file",
|
||||
path: "lib/config.yaml",
|
||||
expectedGetFolderPath: "/lib",
|
||||
getFolderOutput: &codecommit.GetFolderOutput{
|
||||
Files: []*codecommit.File{
|
||||
{RelativePath: aws.String("config.yaml")},
|
||||
},
|
||||
},
|
||||
expectOverallError: false,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "RepoHasPath on folder",
|
||||
path: "lib/config",
|
||||
expectedGetFolderPath: "/lib",
|
||||
getFolderOutput: &codecommit.GetFolderOutput{
|
||||
SubFolders: []*codecommit.Folder{
|
||||
{RelativePath: aws.String("config")},
|
||||
},
|
||||
},
|
||||
expectOverallError: false,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "RepoHasPath on submodules",
|
||||
path: "/lib/submodule/",
|
||||
expectedGetFolderPath: "/lib",
|
||||
getFolderOutput: &codecommit.GetFolderOutput{
|
||||
SubModules: []*codecommit.SubModule{
|
||||
{RelativePath: aws.String("submodule")},
|
||||
},
|
||||
},
|
||||
expectOverallError: false,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "RepoHasPath on symlink",
|
||||
path: "./lib/service.json",
|
||||
expectedGetFolderPath: "/lib",
|
||||
getFolderOutput: &codecommit.GetFolderOutput{
|
||||
SymbolicLinks: []*codecommit.SymbolicLink{
|
||||
{RelativePath: aws.String("service.json")},
|
||||
},
|
||||
},
|
||||
expectOverallError: false,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "RepoHasPath when no match",
|
||||
path: "no-match.json",
|
||||
expectedGetFolderPath: "/",
|
||||
getFolderOutput: &codecommit.GetFolderOutput{
|
||||
Files: []*codecommit.File{
|
||||
{RelativePath: aws.String("config.yaml")},
|
||||
},
|
||||
SubFolders: []*codecommit.Folder{
|
||||
{RelativePath: aws.String("config")},
|
||||
},
|
||||
SubModules: []*codecommit.SubModule{
|
||||
{RelativePath: aws.String("submodule")},
|
||||
},
|
||||
SymbolicLinks: []*codecommit.SymbolicLink{
|
||||
{RelativePath: aws.String("service.json")},
|
||||
},
|
||||
},
|
||||
expectOverallError: false,
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "RepoHasPath when parent folder not found",
|
||||
path: "lib/submodule",
|
||||
expectedGetFolderPath: "/lib",
|
||||
getFolderError: &codecommit.FolderDoesNotExistException{},
|
||||
expectOverallError: false,
|
||||
},
|
||||
{
|
||||
name: "RepoHasPath when unknown error",
|
||||
path: "lib/submodule",
|
||||
expectedGetFolderPath: "/lib",
|
||||
getFolderError: errors.New("unknown error"),
|
||||
expectOverallError: true,
|
||||
},
|
||||
{
|
||||
name: "RepoHasPath on root folder - './'",
|
||||
path: "./",
|
||||
expectOverallError: false,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "RepoHasPath on root folder - '/'",
|
||||
path: "/",
|
||||
expectOverallError: false,
|
||||
expectedResult: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
codeCommitClient := mocks.NewAWSCodeCommitClient(t)
|
||||
taggingClient := mocks.NewAWSTaggingClient(t)
|
||||
ctx := context.Background()
|
||||
if testCase.expectedGetFolderPath != "" {
|
||||
codeCommitClient.EXPECT().
|
||||
GetFolderWithContext(ctx, &codecommit.GetFolderInput{
|
||||
CommitSpecifier: aws.String(branch),
|
||||
FolderPath: aws.String(testCase.expectedGetFolderPath),
|
||||
RepositoryName: aws.String(repoName),
|
||||
}).
|
||||
Return(testCase.getFolderOutput, testCase.getFolderError)
|
||||
}
|
||||
provider := &AWSCodeCommitProvider{
|
||||
codeCommitClient: codeCommitClient,
|
||||
taggingClient: taggingClient,
|
||||
}
|
||||
actual, err := provider.RepoHasPath(ctx, &Repository{
|
||||
Organization: organization,
|
||||
Repository: repoName,
|
||||
Branch: branch,
|
||||
}, testCase.path)
|
||||
if testCase.expectOverallError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.Equal(t, testCase.expectedResult, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAWSCodeCommitGetBranches(t *testing.T) {
|
||||
name := "repo1"
|
||||
id := "1a64adc4-2fb5-4abd-afe7-127984ba83c0"
|
||||
defaultBranch := "main"
|
||||
organization := "111111111111"
|
||||
cloneUrl := "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/repo1"
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
branches []string
|
||||
apiError error
|
||||
expectOverallError bool
|
||||
allBranches bool
|
||||
}{
|
||||
{
|
||||
name: "GetBranches all branches",
|
||||
branches: []string{"main", "feature/codecommit", "chore/go-upgrade"},
|
||||
allBranches: true,
|
||||
},
|
||||
{
|
||||
name: "GetBranches default branch only",
|
||||
allBranches: false,
|
||||
},
|
||||
{
|
||||
name: "GetBranches default branch only",
|
||||
allBranches: false,
|
||||
},
|
||||
{
|
||||
name: "GetBranches all branches on api error",
|
||||
apiError: errors.New("api error"),
|
||||
expectOverallError: true,
|
||||
allBranches: true,
|
||||
},
|
||||
{
|
||||
name: "GetBranches default branch on api error",
|
||||
apiError: errors.New("api error"),
|
||||
expectOverallError: true,
|
||||
allBranches: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
codeCommitClient := mocks.NewAWSCodeCommitClient(t)
|
||||
taggingClient := mocks.NewAWSTaggingClient(t)
|
||||
ctx := context.Background()
|
||||
if testCase.allBranches {
|
||||
codeCommitClient.EXPECT().
|
||||
ListBranchesWithContext(ctx, &codecommit.ListBranchesInput{
|
||||
RepositoryName: aws.String(name),
|
||||
}).
|
||||
Return(&codecommit.ListBranchesOutput{Branches: aws.StringSlice(testCase.branches)}, testCase.apiError)
|
||||
} else {
|
||||
codeCommitClient.EXPECT().
|
||||
GetRepositoryWithContext(ctx, &codecommit.GetRepositoryInput{RepositoryName: aws.String(name)}).
|
||||
Return(&codecommit.GetRepositoryOutput{RepositoryMetadata: &codecommit.RepositoryMetadata{
|
||||
AccountId: aws.String(organization),
|
||||
DefaultBranch: aws.String(defaultBranch),
|
||||
}}, testCase.apiError)
|
||||
}
|
||||
provider := &AWSCodeCommitProvider{
|
||||
codeCommitClient: codeCommitClient,
|
||||
taggingClient: taggingClient,
|
||||
allBranches: testCase.allBranches,
|
||||
}
|
||||
actual, err := provider.GetBranches(ctx, &Repository{
|
||||
Organization: organization,
|
||||
Repository: name,
|
||||
URL: cloneUrl,
|
||||
RepositoryId: id,
|
||||
})
|
||||
if testCase.expectOverallError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assertCopiedProperties := func(repo *Repository) {
|
||||
assert.Equal(t, id, repo.RepositoryId)
|
||||
assert.Equal(t, name, repo.Repository)
|
||||
assert.Equal(t, cloneUrl, repo.URL)
|
||||
assert.Equal(t, organization, repo.Organization)
|
||||
assert.Empty(t, repo.SHA)
|
||||
}
|
||||
actualBranches := make([]string, 0)
|
||||
for _, repo := range actual {
|
||||
assertCopiedProperties(repo)
|
||||
actualBranches = append(actualBranches, repo.Branch)
|
||||
}
|
||||
if testCase.allBranches {
|
||||
assert.ElementsMatch(t, testCase.branches, actualBranches)
|
||||
} else {
|
||||
assert.ElementsMatch(t, []string{defaultBranch}, actualBranches)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// equalIgnoringTagFilterOrder provides an argumentMatcher function that can be used to compare equality of GetResourcesInput ignoring the tagFilter ordering.
|
||||
func equalIgnoringTagFilterOrder(expected *resourcegroupstaggingapi.GetResourcesInput) func(*resourcegroupstaggingapi.GetResourcesInput) bool {
|
||||
return func(actual *resourcegroupstaggingapi.GetResourcesInput) bool {
|
||||
sort.Slice(actual.TagFilters, func(i, j int) bool {
|
||||
return *actual.TagFilters[i].Key < *actual.TagFilters[j].Key
|
||||
})
|
||||
return cmp.Equal(expected, actual)
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package scm_provider
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/utils"
|
||||
bitbucketv1 "github.com/gfleury/go-bitbucket-v1"
|
||||
@@ -183,8 +184,9 @@ func (b *BitbucketServerProvider) listBranches(repo *Repository) ([]bitbucketv1.
|
||||
|
||||
func (b *BitbucketServerProvider) getDefaultBranch(org string, repo string) (*bitbucketv1.Branch, error) {
|
||||
response, err := b.client.DefaultApi.GetDefaultBranch(org, repo)
|
||||
if response != nil && response.StatusCode == 404 {
|
||||
// There's no default branch i.e. empty repo, not an error
|
||||
// The API will return 404 if a default branch is set but doesn't exist. In case the repo is empty and default branch is unset,
|
||||
// we will get an EOF and a nil response.
|
||||
if (response != nil && response.StatusCode == 404) || (response == nil && err == io.EOF) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -365,6 +365,28 @@ func TestGetBranchesMissingDefault(t *testing.T) {
|
||||
assert.Empty(t, repos)
|
||||
}
|
||||
|
||||
func TestGetBranchesEmptyRepo(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Empty(t, r.Header.Get("Authorization"))
|
||||
switch r.RequestURI {
|
||||
case "/rest/api/1.0/projects/PROJECT/repos/REPO/branches/default":
|
||||
return
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false)
|
||||
assert.NoError(t, err)
|
||||
repos, err := provider.GetBranches(context.Background(), &Repository{
|
||||
Organization: "PROJECT",
|
||||
Repository: "REPO",
|
||||
URL: "ssh://git@mycompany.bitbucket.org/PROJECT/REPO.git",
|
||||
Labels: []string{},
|
||||
RepositoryId: 1,
|
||||
})
|
||||
assert.Empty(t, repos)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetBranchesErrorDefaultBranch(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Empty(t, r.Header.Get("Authorization"))
|
||||
|
||||
@@ -27,11 +27,13 @@ func NewGiteaProvider(ctx context.Context, owner, token, url string, allBranches
|
||||
if insecure {
|
||||
cookieJar, _ := cookiejar.New(nil)
|
||||
|
||||
tr := http.DefaultTransport.(*http.Transport).Clone()
|
||||
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
|
||||
httpClient = &http.Client{
|
||||
Jar: cookieJar,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}}
|
||||
Jar: cookieJar,
|
||||
Transport: tr,
|
||||
}
|
||||
}
|
||||
client, err := gitea.NewClient(url, gitea.SetToken(token), gitea.SetHTTPClient(httpClient))
|
||||
if err != nil {
|
||||
|
||||
@@ -3,11 +3,13 @@ package scm_provider
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"net/http"
|
||||
"os"
|
||||
pathpkg "path"
|
||||
|
||||
gitlab "github.com/xanzy/go-gitlab"
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/utils"
|
||||
"github.com/hashicorp/go-retryablehttp"
|
||||
"github.com/xanzy/go-gitlab"
|
||||
)
|
||||
|
||||
type GitlabProvider struct {
|
||||
@@ -19,21 +21,28 @@ type GitlabProvider struct {
|
||||
|
||||
var _ SCMProviderService = &GitlabProvider{}
|
||||
|
||||
func NewGitlabProvider(ctx context.Context, organization string, token string, url string, allBranches, includeSubgroups bool) (*GitlabProvider, error) {
|
||||
func NewGitlabProvider(ctx context.Context, organization string, token string, url string, allBranches, includeSubgroups, insecure bool, scmRootCAPath string) (*GitlabProvider, error) {
|
||||
// Undocumented environment variable to set a default token, to be used in testing to dodge anonymous rate limits.
|
||||
if token == "" {
|
||||
token = os.Getenv("GITLAB_TOKEN")
|
||||
}
|
||||
var client *gitlab.Client
|
||||
|
||||
tr := http.DefaultTransport.(*http.Transport).Clone()
|
||||
tr.TLSClientConfig = utils.GetTlsConfig(scmRootCAPath, insecure)
|
||||
|
||||
retryClient := retryablehttp.NewClient()
|
||||
retryClient.HTTPClient.Transport = tr
|
||||
|
||||
if url == "" {
|
||||
var err error
|
||||
client, err = gitlab.NewClient(token)
|
||||
client, err = gitlab.NewClient(token, gitlab.WithHTTPClient(retryClient.HTTPClient))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
client, err = gitlab.NewClient(token, gitlab.WithBaseURL(url))
|
||||
client, err = gitlab.NewClient(token, gitlab.WithBaseURL(url), gitlab.WithHTTPClient(retryClient.HTTPClient))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -65,7 +74,7 @@ func (g *GitlabProvider) GetBranches(ctx context.Context, repo *Repository) ([]*
|
||||
func (g *GitlabProvider) ListRepos(ctx context.Context, cloneProtocol string) ([]*Repository, error) {
|
||||
opt := &gitlab.ListGroupProjectsOptions{
|
||||
ListOptions: gitlab.ListOptions{PerPage: 100},
|
||||
IncludeSubgroups: &g.includeSubgroups,
|
||||
IncludeSubGroups: &g.includeSubgroups,
|
||||
}
|
||||
repos := []*Repository{}
|
||||
for {
|
||||
|
||||
@@ -286,10 +286,10 @@ func gitlabMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
|
||||
}
|
||||
func TestGitlabListRepos(t *testing.T) {
|
||||
cases := []struct {
|
||||
name, proto, url string
|
||||
hasError, allBranches, includeSubgroups bool
|
||||
branches []string
|
||||
filters []v1alpha1.SCMProviderGeneratorFilter
|
||||
name, proto, url string
|
||||
hasError, allBranches, includeSubgroups, insecure bool
|
||||
branches []string
|
||||
filters []v1alpha1.SCMProviderGeneratorFilter
|
||||
}{
|
||||
{
|
||||
name: "blank protocol",
|
||||
@@ -323,7 +323,7 @@ func TestGitlabListRepos(t *testing.T) {
|
||||
}))
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
provider, _ := NewGitlabProvider(context.Background(), "test-argocd-proton", "", ts.URL, c.allBranches, c.includeSubgroups)
|
||||
provider, _ := NewGitlabProvider(context.Background(), "test-argocd-proton", "", ts.URL, c.allBranches, c.includeSubgroups, c.insecure, "")
|
||||
rawRepos, err := ListRepos(context.Background(), provider, c.filters, c.proto)
|
||||
if c.hasError {
|
||||
assert.NotNil(t, err)
|
||||
@@ -352,7 +352,7 @@ func TestGitlabHasPath(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gitlabMockHandler(t)(w, r)
|
||||
}))
|
||||
host, _ := NewGitlabProvider(context.Background(), "test-argocd-proton", "", ts.URL, false, true)
|
||||
host, _ := NewGitlabProvider(context.Background(), "test-argocd-proton", "", ts.URL, false, true, false, "")
|
||||
repo := &Repository{
|
||||
Organization: "test-argocd-proton",
|
||||
Repository: "argocd",
|
||||
@@ -398,7 +398,7 @@ func TestGitlabGetBranches(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gitlabMockHandler(t)(w, r)
|
||||
}))
|
||||
host, _ := NewGitlabProvider(context.Background(), "test-argocd-proton", "", ts.URL, false, true)
|
||||
host, _ := NewGitlabProvider(context.Background(), "test-argocd-proton", "", ts.URL, false, true, false, "")
|
||||
|
||||
repo := &Repository{
|
||||
RepositoryId: 27084533,
|
||||
|
||||
@@ -50,10 +50,10 @@ const (
|
||||
// ValidateDestination checks:
|
||||
// if we used destination name we infer the server url
|
||||
// if we used both name and server then we return an invalid spec error
|
||||
func ValidateDestination(ctx context.Context, dest *appv1.ApplicationDestination, clientset kubernetes.Interface, namespace string) error {
|
||||
func ValidateDestination(ctx context.Context, dest *appv1.ApplicationDestination, clientset kubernetes.Interface, argoCDNamespace string) error {
|
||||
if dest.Name != "" {
|
||||
if dest.Server == "" {
|
||||
server, err := getDestinationServer(ctx, dest.Name, clientset, namespace)
|
||||
server, err := getDestinationServer(ctx, dest.Name, clientset, argoCDNamespace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to find destination server: %v", err)
|
||||
}
|
||||
@@ -70,11 +70,11 @@ func ValidateDestination(ctx context.Context, dest *appv1.ApplicationDestination
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDestinationServer(ctx context.Context, clusterName string, clientset kubernetes.Interface, namespace string) (string, error) {
|
||||
func getDestinationServer(ctx context.Context, clusterName string, clientset kubernetes.Interface, argoCDNamespace string) (string, error) {
|
||||
// settingsMgr := settings.NewSettingsManager(context.TODO(), clientset, namespace)
|
||||
// argoDB := db.NewDB(namespace, settingsMgr, clientset)
|
||||
// clusterList, err := argoDB.ListClusters(ctx)
|
||||
clusterList, err := ListClusters(ctx, clientset, namespace)
|
||||
clusterList, err := ListClusters(ctx, clientset, argoCDNamespace)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user