mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-02-20 09:38:49 +01:00
Compare commits
562 Commits
v1.7.6
...
refresh-do
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69d23e105e | ||
|
|
2d963ab8d0 | ||
|
|
7a3cd61060 | ||
|
|
5cbb397c93 | ||
|
|
5257a902fb | ||
|
|
4c6a74642f | ||
|
|
ab715efd2b | ||
|
|
50a693e554 | ||
|
|
8409da4df8 | ||
|
|
857ea0edfe | ||
|
|
068a7a6abc | ||
|
|
6c019b037b | ||
|
|
b37bb53f5e | ||
|
|
4251200f78 | ||
|
|
b407d59fa5 | ||
|
|
4f7aaf0d71 | ||
|
|
24b18a83a6 | ||
|
|
ba7054e808 | ||
|
|
5660dbab96 | ||
|
|
0430725544 | ||
|
|
e896c0e4b4 | ||
|
|
3c12f20dc0 | ||
|
|
ce50a9de21 | ||
|
|
7433a996e4 | ||
|
|
7ad79e5e12 | ||
|
|
2a1a03a919 | ||
|
|
7ec8b266f8 | ||
|
|
f665a74e65 | ||
|
|
6e6cd1a1ef | ||
|
|
e1168fd1bf | ||
|
|
13b9b92c99 | ||
|
|
594c827b66 | ||
|
|
fbf0a780a0 | ||
|
|
6de3cf44a4 | ||
|
|
47529ee246 | ||
|
|
a4ee25b59d | ||
|
|
b2b5b50b09 | ||
|
|
656bd51d55 | ||
|
|
73edc0a652 | ||
|
|
047d2e6c8e | ||
|
|
e33b54decb | ||
|
|
2be1b8771a | ||
|
|
ad923ba0c8 | ||
|
|
f54d372440 | ||
|
|
6e2ee623f8 | ||
|
|
ac5cfb9a60 | ||
|
|
d342993c3f | ||
|
|
fd03eccfc8 | ||
|
|
824ff732a2 | ||
|
|
65ecd87027 | ||
|
|
f53cbd5d0e | ||
|
|
6a19451d0b | ||
|
|
11a42c3941 | ||
|
|
3473005364 | ||
|
|
aeeeac79fe | ||
|
|
2d06b5077e | ||
|
|
29850811c7 | ||
|
|
4a06699a6a | ||
|
|
e01703ba3a | ||
|
|
b7e6dcf085 | ||
|
|
223335d96e | ||
|
|
fb8096a1f7 | ||
|
|
e0f7731fee | ||
|
|
c540b719cf | ||
|
|
137ff39293 | ||
|
|
1d1fbf0fb1 | ||
|
|
366b7ff364 | ||
|
|
e22da4a941 | ||
|
|
685f15b695 | ||
|
|
b8adbf0818 | ||
|
|
8eec7b360f | ||
|
|
ae3de246fd | ||
|
|
e4165d01b8 | ||
|
|
2d79d97b44 | ||
|
|
bb15a4996e | ||
|
|
33bf9901ce | ||
|
|
3c3325f3e7 | ||
|
|
c936355508 | ||
|
|
161d1d777d | ||
|
|
f0d8129ea0 | ||
|
|
d9e9a9939d | ||
|
|
8f5379e1cc | ||
|
|
85ffe1c4bf | ||
|
|
8014cf3c03 | ||
|
|
bef46390ea | ||
|
|
c160a7140e | ||
|
|
7dfc439086 | ||
|
|
564a1221b3 | ||
|
|
20ed703e03 | ||
|
|
8da699f177 | ||
|
|
9bf7ae378f | ||
|
|
b29c56aaf1 | ||
|
|
a85f8e89f5 | ||
|
|
bc4fba3f1b | ||
|
|
065e5b9306 | ||
|
|
d6d534a94e | ||
|
|
0cc4fc2b15 | ||
|
|
82b74d274d | ||
|
|
5d5cd742c0 | ||
|
|
f7c2b886db | ||
|
|
baa0f2e39c | ||
|
|
21670820a4 | ||
|
|
5d1bbb13f8 | ||
|
|
5f10461034 | ||
|
|
420c1705ae | ||
|
|
0382763367 | ||
|
|
5e7c68c8fc | ||
|
|
076924dd77 | ||
|
|
abf78a3c4c | ||
|
|
3967baf0c9 | ||
|
|
32642dfb1b | ||
|
|
00f53b510d | ||
|
|
7165b45aea | ||
|
|
d516f4754f | ||
|
|
d8b545d107 | ||
|
|
dbadd74b42 | ||
|
|
e02b8646c9 | ||
|
|
b2a3918005 | ||
|
|
d95e35fcab | ||
|
|
bacddacbcf | ||
|
|
166d0cc68b | ||
|
|
eaf9887d17 | ||
|
|
1ee7997fb8 | ||
|
|
7af5837ffa | ||
|
|
9dd5dd2f83 | ||
|
|
41fb0ac2de | ||
|
|
54716ac0ed | ||
|
|
67d15ce05c | ||
|
|
a12f805bb2 | ||
|
|
ae6b8241d5 | ||
|
|
e26ad305b4 | ||
|
|
534ec17b3c | ||
|
|
ac7dbfe81f | ||
|
|
5037a652f6 | ||
|
|
81497bbcf1 | ||
|
|
c9c8befa45 | ||
|
|
2f080e14f9 | ||
|
|
64e1c3825a | ||
|
|
ffb8ce22ee | ||
|
|
27a609fb1a | ||
|
|
1771d9fd78 | ||
|
|
da42028c8c | ||
|
|
0f233c072b | ||
|
|
98bec61d61 | ||
|
|
f5b9704267 | ||
|
|
eb9216f8e1 | ||
|
|
f4c9c3db59 | ||
|
|
dfcbddf605 | ||
|
|
da6bd1138d | ||
|
|
b0819691d3 | ||
|
|
9c597acaac | ||
|
|
14b8e3da66 | ||
|
|
5d6b3216f5 | ||
|
|
b314824e31 | ||
|
|
49b2e034cf | ||
|
|
cee02b7dac | ||
|
|
eac50fcb22 | ||
|
|
ad1dd825c6 | ||
|
|
3b87781003 | ||
|
|
7d277dc042 | ||
|
|
ddecca412c | ||
|
|
19ec893118 | ||
|
|
36204efcc9 | ||
|
|
94eba091ba | ||
|
|
12ec99ad01 | ||
|
|
30ffe8de71 | ||
|
|
5b464c996b | ||
|
|
cfdf1db4a1 | ||
|
|
8bd59530e8 | ||
|
|
45b3e48dfc | ||
|
|
14cf261f48 | ||
|
|
554c39dc34 | ||
|
|
456f637196 | ||
|
|
4bcdfbe1a9 | ||
|
|
1734b46f71 | ||
|
|
d2295d45d0 | ||
|
|
e3e392c058 | ||
|
|
b2284379dd | ||
|
|
d4ef744454 | ||
|
|
09a1bf4644 | ||
|
|
e08e0d29bb | ||
|
|
fba23d9e45 | ||
|
|
e1990faf67 | ||
|
|
1f7ef07512 | ||
|
|
e160afc225 | ||
|
|
9e61354fa2 | ||
|
|
696bf8be7c | ||
|
|
68f316137b | ||
|
|
e472678815 | ||
|
|
ca1235a21d | ||
|
|
d7a5257094 | ||
|
|
3d019a22c2 | ||
|
|
85ea4c445e | ||
|
|
13eefd8292 | ||
|
|
89a8751216 | ||
|
|
dcf2694285 | ||
|
|
8b486b5e0f | ||
|
|
1ef14dca83 | ||
|
|
b955c6a7a4 | ||
|
|
27912a08f1 | ||
|
|
fdb5ada06d | ||
|
|
9b53f8f93a | ||
|
|
bb7bdbab9b | ||
|
|
3114fdde80 | ||
|
|
73f75df173 | ||
|
|
6b8867a582 | ||
|
|
9ea1163411 | ||
|
|
3b67e91d55 | ||
|
|
b7b74582cb | ||
|
|
ea73f093e8 | ||
|
|
75eed3961c | ||
|
|
9fb51f7af6 | ||
|
|
869af3e2fe | ||
|
|
991894e305 | ||
|
|
0ebde72d6a | ||
|
|
f05ac0cb3a | ||
|
|
6863838917 | ||
|
|
75601a7177 | ||
|
|
5c268b9af9 | ||
|
|
a516ffedba | ||
|
|
c36b7ce4bd | ||
|
|
d804e81e1f | ||
|
|
230bafd3c3 | ||
|
|
da929c2796 | ||
|
|
4417cc8180 | ||
|
|
5f46815469 | ||
|
|
d82f6b425a | ||
|
|
7b0fe7bffb | ||
|
|
d766a82185 | ||
|
|
cfbd535904 | ||
|
|
2b50698987 | ||
|
|
3bf3ac7e21 | ||
|
|
708adaff60 | ||
|
|
dc13884f4b | ||
|
|
854c8a6f98 | ||
|
|
ee28288d50 | ||
|
|
f6a32a9ebc | ||
|
|
c108e2f061 | ||
|
|
6095fc564f | ||
|
|
6e1649b1e2 | ||
|
|
b89639e921 | ||
|
|
3ca1b02725 | ||
|
|
48291d8701 | ||
|
|
345c77a8c7 | ||
|
|
371de922d8 | ||
|
|
a8a451f811 | ||
|
|
c214fe0d95 | ||
|
|
eb0d8b48bb | ||
|
|
cb59fc72ef | ||
|
|
ae12145f2d | ||
|
|
79509003fa | ||
|
|
5aa1e47f8e | ||
|
|
9ed140deaa | ||
|
|
c147320687 | ||
|
|
3ddf50b787 | ||
|
|
97c8b561ea | ||
|
|
0c47b008ff | ||
|
|
9bfa1e706b | ||
|
|
59443ad185 | ||
|
|
6e6f4f50a0 | ||
|
|
ed547aa545 | ||
|
|
ca7fa55a2b | ||
|
|
7ee951b5b8 | ||
|
|
be8308199c | ||
|
|
d8c08bfe7d | ||
|
|
231509bb3c | ||
|
|
a2d6582e54 | ||
|
|
4b23918802 | ||
|
|
51f2949883 | ||
|
|
ffa824bbba | ||
|
|
e4e503aad7 | ||
|
|
e8e810934d | ||
|
|
762b33c819 | ||
|
|
69d8831b38 | ||
|
|
ad8715cbad | ||
|
|
6cadaa2a5d | ||
|
|
8b1a118cdb | ||
|
|
ccb7371047 | ||
|
|
2f72f3adad | ||
|
|
ae17c70b00 | ||
|
|
5921feda5f | ||
|
|
71bd3fdd24 | ||
|
|
30816bc549 | ||
|
|
0681c2754a | ||
|
|
5040d6f080 | ||
|
|
e3b733627f | ||
|
|
be513e431a | ||
|
|
5e24d21ae8 | ||
|
|
eec8f79923 | ||
|
|
d1ba640bff | ||
|
|
7947b59eeb | ||
|
|
4c3f97f78a | ||
|
|
c4dcae3442 | ||
|
|
d1a36e5b6d | ||
|
|
9c51838ccc | ||
|
|
700a4104c6 | ||
|
|
dcb5f07c23 | ||
|
|
9625e50ccd | ||
|
|
3e19b2fdf1 | ||
|
|
96e0f0d3be | ||
|
|
620e31e52d | ||
|
|
ab7e1773f0 | ||
|
|
e67d934827 | ||
|
|
9ee0d2c6c0 | ||
|
|
9c684ddc08 | ||
|
|
c9f3c64a58 | ||
|
|
8c3a8e3655 | ||
|
|
449b50cf6c | ||
|
|
da3ab59be0 | ||
|
|
e8f63d4583 | ||
|
|
cdb3df1077 | ||
|
|
858676c4f8 | ||
|
|
44c31e278c | ||
|
|
a759601264 | ||
|
|
9bbbda55a4 | ||
|
|
b97f4f7f8e | ||
|
|
422a26e8d8 | ||
|
|
245e1ee636 | ||
|
|
2166fea351 | ||
|
|
dea75eb481 | ||
|
|
6ef89e3c09 | ||
|
|
b9954e55ac | ||
|
|
86031504af | ||
|
|
97003caebc | ||
|
|
42ebb227e1 | ||
|
|
c7f7631f2e | ||
|
|
9cdfe40faf | ||
|
|
21304ee2c5 | ||
|
|
7f0ffb4cd2 | ||
|
|
22e0b4ff55 | ||
|
|
0767dff025 | ||
|
|
5ba8710ff1 | ||
|
|
dde5f143fc | ||
|
|
f02115af15 | ||
|
|
cd302fd055 | ||
|
|
cfd59aded2 | ||
|
|
dd856e1c2b | ||
|
|
387f775f4a | ||
|
|
837ed45361 | ||
|
|
46ee2f21a2 | ||
|
|
3bf9deb15e | ||
|
|
a96b476f16 | ||
|
|
764ea07fc0 | ||
|
|
6ddd98c4f8 | ||
|
|
be60425a47 | ||
|
|
0850bcc184 | ||
|
|
aaae4003a0 | ||
|
|
6535d1ac34 | ||
|
|
81b84e66c1 | ||
|
|
5fdbe2057a | ||
|
|
ac8d18d39d | ||
|
|
3d39accdb2 | ||
|
|
4d643a151d | ||
|
|
41ab92fbdd | ||
|
|
2e06118792 | ||
|
|
bbfbf6834f | ||
|
|
c7dbe4883b | ||
|
|
bdee71d4c7 | ||
|
|
9af729c738 | ||
|
|
c0f9c9ae93 | ||
|
|
dfc75df7a0 | ||
|
|
cf03c1dcc5 | ||
|
|
7824a1fc2b | ||
|
|
5db8d97bf0 | ||
|
|
05c493b3a6 | ||
|
|
894f95dce5 | ||
|
|
90227f226d | ||
|
|
fd482316d0 | ||
|
|
c620fa7aaa | ||
|
|
a0f6e033c0 | ||
|
|
15b0a6e793 | ||
|
|
3408e2d72d | ||
|
|
e09cacba65 | ||
|
|
66d86fe56f | ||
|
|
67c91564c4 | ||
|
|
698712f396 | ||
|
|
001d990d0c | ||
|
|
1cbada9d86 | ||
|
|
dff7da7271 | ||
|
|
12957a494c | ||
|
|
4f1e371830 | ||
|
|
8e11facb94 | ||
|
|
61c8f73e21 | ||
|
|
23ac24bdea | ||
|
|
e6f116319b | ||
|
|
5fa808a788 | ||
|
|
6d64280fba | ||
|
|
3ae1d13dfd | ||
|
|
f512d213cf | ||
|
|
35914ff7ab | ||
|
|
48891e2536 | ||
|
|
a88c729148 | ||
|
|
c8ca3e7c45 | ||
|
|
2a0012d5f1 | ||
|
|
fd483babb7 | ||
|
|
52f4ed203f | ||
|
|
497cd603ca | ||
|
|
97f094756d | ||
|
|
5cdcca4544 | ||
|
|
d479d22de7 | ||
|
|
303925f4a0 | ||
|
|
8eb3306064 | ||
|
|
9f2eab665b | ||
|
|
3ac0bc36d4 | ||
|
|
2b84672641 | ||
|
|
701dda9a28 | ||
|
|
8995d0405a | ||
|
|
286f98ba82 | ||
|
|
10f68dde2d | ||
|
|
5592150f18 | ||
|
|
7af7f30715 | ||
|
|
4534bd2725 | ||
|
|
95d19cdcca | ||
|
|
05b70f1e97 | ||
|
|
da57c9f1c8 | ||
|
|
287e8cffdb | ||
|
|
f93da5346c | ||
|
|
aefa739169 | ||
|
|
3123c00a85 | ||
|
|
efb7028d84 | ||
|
|
3848f64807 | ||
|
|
5af0c5ad3a | ||
|
|
24927c4d4f | ||
|
|
91e62bfc3b | ||
|
|
b08f895d9a | ||
|
|
3409e0728f | ||
|
|
dd63715017 | ||
|
|
0d2fc86330 | ||
|
|
8299e99049 | ||
|
|
7b60548e8a | ||
|
|
0226190ef4 | ||
|
|
25823a4625 | ||
|
|
712df19fac | ||
|
|
bccaefdac9 | ||
|
|
53d50df001 | ||
|
|
8b2e05c20d | ||
|
|
9bb9c19e67 | ||
|
|
6b106768a5 | ||
|
|
4810874348 | ||
|
|
d96083c293 | ||
|
|
f815c96605 | ||
|
|
4347a3c0ad | ||
|
|
3f7d60018f | ||
|
|
b7c2002a11 | ||
|
|
c1ee89b502 | ||
|
|
74f5043e87 | ||
|
|
fbfa89d358 | ||
|
|
1c95c90a2d | ||
|
|
9f47a11621 | ||
|
|
e28a3e5ed0 | ||
|
|
52cae98705 | ||
|
|
3850e80040 | ||
|
|
989f5c80c6 | ||
|
|
82340a0740 | ||
|
|
28e60406a8 | ||
|
|
eb0d018c31 | ||
|
|
850de2021a | ||
|
|
b8d1b9bbc0 | ||
|
|
7caa2106ef | ||
|
|
24b5c1e34d | ||
|
|
3aa0748c70 | ||
|
|
1462ab3c06 | ||
|
|
5de3a302fb | ||
|
|
14fa7f954c | ||
|
|
1b3d7a02e1 | ||
|
|
eb13305984 | ||
|
|
3a30a4fc74 | ||
|
|
d4ddd51602 | ||
|
|
95eda65759 | ||
|
|
c26573369d | ||
|
|
c91acc0673 | ||
|
|
51a5795b44 | ||
|
|
9aae99cf7f | ||
|
|
5f680d6cec | ||
|
|
28a76352c5 | ||
|
|
cfb925c0d4 | ||
|
|
4e6d8cc1d2 | ||
|
|
f215233af4 | ||
|
|
b1e3036bc2 | ||
|
|
df4002a987 | ||
|
|
96035d3b51 | ||
|
|
89ee234634 | ||
|
|
ff2aa41539 | ||
|
|
85b27e6deb | ||
|
|
d60486fb47 | ||
|
|
41ca6b2ada | ||
|
|
d9f4e224a0 | ||
|
|
29c1095a2d | ||
|
|
0473ed8104 | ||
|
|
706205958c | ||
|
|
e5dde6eefc | ||
|
|
aa2762a9b7 | ||
|
|
6959f032f0 | ||
|
|
1dfe670d1c | ||
|
|
76782a0270 | ||
|
|
b3be910465 | ||
|
|
34c4aa42d6 | ||
|
|
89ebf5a906 | ||
|
|
bae5f93590 | ||
|
|
8f7eabefd4 | ||
|
|
8fa2c7f43f | ||
|
|
d3eb8e9590 | ||
|
|
ad38421b76 | ||
|
|
92bbcf15e8 | ||
|
|
10dc3ac12a | ||
|
|
2f5e45490c | ||
|
|
e10c20f683 | ||
|
|
bc537c1e87 | ||
|
|
a482546112 | ||
|
|
7ead93458e | ||
|
|
dc6d88950c | ||
|
|
c44074d4d6 | ||
|
|
016c8b333a | ||
|
|
cc1592eb0c | ||
|
|
ea9b0b35d0 | ||
|
|
44623d6be2 | ||
|
|
f625ddc6b9 | ||
|
|
324a336a52 | ||
|
|
d09bd23cf8 | ||
|
|
90eb262f64 | ||
|
|
c14f87d565 | ||
|
|
fca0f69b5e | ||
|
|
edf2904004 | ||
|
|
d7a70bfc6f | ||
|
|
a4ea2624a8 | ||
|
|
dec73c77e6 | ||
|
|
761ad0bdcf | ||
|
|
d1e272e192 | ||
|
|
9fb7aa4f20 | ||
|
|
8fa0f04e43 | ||
|
|
c00e84700c | ||
|
|
2d2335f95a | ||
|
|
10d05cdb60 | ||
|
|
9f79340505 | ||
|
|
290712d4b3 | ||
|
|
c49dd8d383 | ||
|
|
3a50f8df81 | ||
|
|
569a2a6bc6 | ||
|
|
2e8a8f09b1 | ||
|
|
d04b6e2d35 | ||
|
|
f208700f78 | ||
|
|
7a3d05cb7c | ||
|
|
8df8bfff18 | ||
|
|
9ac6bb3248 | ||
|
|
ee57ded16f | ||
|
|
f508dec107 | ||
|
|
0b387a454b | ||
|
|
bc565d384d | ||
|
|
372eae0f21 | ||
|
|
474301c5ab | ||
|
|
beb2817d6f | ||
|
|
0ee983fc31 | ||
|
|
c32d5fd5ee | ||
|
|
0d193cfd57 | ||
|
|
263e7a8497 | ||
|
|
7f86e6b38c | ||
|
|
194d471db4 | ||
|
|
84e8af7976 | ||
|
|
f2dca0315c | ||
|
|
01d9b94f62 | ||
|
|
ebb216ff11 | ||
|
|
e56997f504 |
@@ -1,16 +0,0 @@
|
||||
version: 2.1
|
||||
jobs:
|
||||
dummy:
|
||||
docker:
|
||||
- image: cimg/base:2020.01
|
||||
steps:
|
||||
- run:
|
||||
name: Dummy step
|
||||
command: |
|
||||
echo "This is a dummy step to satisfy CircleCI"
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
workflow:
|
||||
jobs:
|
||||
- dummy
|
||||
@@ -1,324 +0,0 @@
|
||||
# CircleCI currently disabled in favor of GH actions
|
||||
version: 2.1
|
||||
commands:
|
||||
prepare_environment:
|
||||
steps:
|
||||
- run:
|
||||
name: Configure environment
|
||||
command: |
|
||||
set -x
|
||||
echo "export GOCACHE=/tmp/go-build-cache" | tee -a $BASH_ENV
|
||||
echo "export ARGOCD_TEST_VERBOSE=true" | tee -a $BASH_ENV
|
||||
echo "export ARGOCD_TEST_PARALLELISM=4" | tee -a $BASH_ENV
|
||||
echo "export ARGOCD_SONAR_VERSION=4.2.0.1873" | tee -a $BASH_ENV
|
||||
configure_git:
|
||||
steps:
|
||||
- run:
|
||||
name: Configure Git
|
||||
command: |
|
||||
set -x
|
||||
# must be configured for tests to run
|
||||
git config --global user.email you@example.com
|
||||
git config --global user.name "Your Name"
|
||||
echo "export PATH=/home/circleci/.go_workspace/src/github.com/argoproj/argo-cd/hack:\$PATH" | tee -a $BASH_ENV
|
||||
echo "export GIT_ASKPASS=git-ask-pass.sh" | tee -a $BASH_ENV
|
||||
setup_go_modules:
|
||||
steps:
|
||||
- run:
|
||||
name: Run go mod download and populate vendor
|
||||
command: |
|
||||
go mod download
|
||||
go mod vendor
|
||||
save_coverage_info:
|
||||
steps:
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- coverage.out
|
||||
save_node_modules:
|
||||
steps:
|
||||
- persist_to_workspace:
|
||||
root: ~/argo-cd
|
||||
paths:
|
||||
- ui/node_modules
|
||||
save_go_cache:
|
||||
steps:
|
||||
- persist_to_workspace:
|
||||
root: /tmp
|
||||
paths:
|
||||
- go-build-cache
|
||||
attach_go_cache:
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp
|
||||
install_golang:
|
||||
steps:
|
||||
- run:
|
||||
name: Install Golang v1.14.1
|
||||
command: |
|
||||
go get golang.org/dl/go1.14.1
|
||||
[ -e /home/circleci/sdk/go1.14.1 ] || go1.14.1 download
|
||||
go env
|
||||
echo "export GOPATH=/home/circleci/.go_workspace" | tee -a $BASH_ENV
|
||||
echo "export PATH=/home/circleci/sdk/go1.14.1/bin:\$PATH" | tee -a $BASH_ENV
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: argoproj/argocd-test-tools:v0.5.0
|
||||
working_directory: /go/src/github.com/argoproj/argo-cd
|
||||
steps:
|
||||
- prepare_environment
|
||||
- checkout
|
||||
- run: make build-local
|
||||
- run: chmod -R 777 vendor
|
||||
- run: chmod -R 777 ${GOCACHE}
|
||||
- save_go_cache
|
||||
|
||||
codegen:
|
||||
docker:
|
||||
- image: argoproj/argocd-test-tools:v0.5.0
|
||||
working_directory: /go/src/github.com/argoproj/argo-cd
|
||||
steps:
|
||||
- prepare_environment
|
||||
- checkout
|
||||
- attach_go_cache
|
||||
- run: helm2 init --client-only
|
||||
- run: make codegen-local
|
||||
- run:
|
||||
name: Check nothing has changed
|
||||
command: |
|
||||
set -xo pipefail
|
||||
# This makes sure you ran `make pre-commit` before you pushed.
|
||||
# We exclude the Swagger resources; CircleCI doesn't generate them correctly.
|
||||
# When this fails, it will, create a patch file you can apply locally to fix it.
|
||||
# To troubleshoot builds: https://argoproj.github.io/argo-cd/developer-guide/ci/
|
||||
git diff --exit-code -- . ':!Gopkg.lock' ':!assets/swagger.json' | tee codegen.patch
|
||||
- store_artifacts:
|
||||
path: codegen.patch
|
||||
destination: .
|
||||
test:
|
||||
working_directory: /go/src/github.com/argoproj/argo-cd
|
||||
docker:
|
||||
- image: argoproj/argocd-test-tools:v0.5.0
|
||||
steps:
|
||||
- prepare_environment
|
||||
- checkout
|
||||
- configure_git
|
||||
- attach_go_cache
|
||||
- run: make test-local
|
||||
- run:
|
||||
name: Uploading code coverage
|
||||
command: bash <(curl -s https://codecov.io/bash) -f coverage.out
|
||||
- run:
|
||||
name: Output of test-results
|
||||
command: |
|
||||
ls -l test-results || true
|
||||
cat test-results/junit.xml || true
|
||||
- save_coverage_info
|
||||
- store_test_results:
|
||||
path: test-results
|
||||
- store_artifacts:
|
||||
path: test-results
|
||||
destination: .
|
||||
|
||||
lint:
|
||||
working_directory: /go/src/github.com/argoproj/argo-cd
|
||||
docker:
|
||||
- image: argoproj/argocd-test-tools:v0.5.0
|
||||
steps:
|
||||
- prepare_environment
|
||||
- checkout
|
||||
- configure_git
|
||||
- attach_vendor
|
||||
- store_go_cache_docker
|
||||
- run:
|
||||
name: Run golangci-lint
|
||||
command: ARGOCD_LINT_GOGC=10 make lint-local
|
||||
- run:
|
||||
name: Check that nothing has changed
|
||||
command: |
|
||||
gDiff=$(git diff)
|
||||
if test "$gDiff" != ""; then
|
||||
echo
|
||||
echo "###############################################################################"
|
||||
echo "golangci-lint has made automatic corrections to your code. Please check below"
|
||||
echo "diff output and commit this to your local branch, or run make lint locally."
|
||||
echo "###############################################################################"
|
||||
echo
|
||||
git diff
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sonarcloud:
|
||||
working_directory: /go/src/github.com/argoproj/argo-cd
|
||||
docker:
|
||||
- image: argoproj/argocd-test-tools:v0.5.0
|
||||
environment:
|
||||
NODE_MODULES: /go/src/github.com/argoproj/argo-cd/ui/node_modules
|
||||
steps:
|
||||
- prepare_environment
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
command: mkdir -p /tmp/cache/scanner
|
||||
name: Create cache directory if it doesn't exist
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-sonarcloud-scanner-4.2.0.1873
|
||||
- run:
|
||||
command: |
|
||||
set -e
|
||||
VERSION=4.2.0.1873
|
||||
SONAR_TOKEN=$SONAR_TOKEN
|
||||
SCANNER_DIRECTORY=/tmp/cache/scanner
|
||||
export SONAR_USER_HOME=$SCANNER_DIRECTORY/.sonar
|
||||
OS="linux"
|
||||
echo $SONAR_USER_HOME
|
||||
|
||||
if [[ ! -x "$SCANNER_DIRECTORY/sonar-scanner-$VERSION-$OS/bin/sonar-scanner" ]]; then
|
||||
curl -Ol https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$VERSION-$OS.zip
|
||||
unzip -qq -o sonar-scanner-cli-$VERSION-$OS.zip -d $SCANNER_DIRECTORY
|
||||
fi
|
||||
|
||||
chmod +x $SCANNER_DIRECTORY/sonar-scanner-$VERSION-$OS/bin/sonar-scanner
|
||||
chmod +x $SCANNER_DIRECTORY/sonar-scanner-$VERSION-$OS/jre/bin/java
|
||||
|
||||
# Workaround for a possible bug in CircleCI
|
||||
if ! echo $CIRCLE_PULL_REQUEST | grep https://github.com/argoproj; then
|
||||
unset CIRCLE_PULL_REQUEST
|
||||
unset CIRCLE_PULL_REQUESTS
|
||||
fi
|
||||
|
||||
# Explicitly set NODE_MODULES
|
||||
export NODE_MODULES=/go/src/github.com/argoproj/argo-cd/ui/node_modules
|
||||
export NODE_PATH=/go/src/github.com/argoproj/argo-cd/ui/node_modules
|
||||
|
||||
$SCANNER_DIRECTORY/sonar-scanner-$VERSION-$OS/bin/sonar-scanner
|
||||
name: SonarCloud
|
||||
- save_cache:
|
||||
key: v1-sonarcloud-scanner-4.2.0.1873
|
||||
paths:
|
||||
- /tmp/cache/scanner
|
||||
|
||||
e2e:
|
||||
working_directory: /home/circleci/.go_workspace/src/github.com/argoproj/argo-cd
|
||||
machine:
|
||||
image: ubuntu-1604:201903-01
|
||||
environment:
|
||||
ARGOCD_FAKE_IN_CLUSTER: "true"
|
||||
ARGOCD_SSH_DATA_PATH: "/tmp/argo-e2e/app/config/ssh"
|
||||
ARGOCD_TLS_DATA_PATH: "/tmp/argo-e2e/app/config/tls"
|
||||
ARGOCD_E2E_K3S: "true"
|
||||
steps:
|
||||
- run:
|
||||
name: Install and start K3S v0.5.0
|
||||
command: |
|
||||
curl -sfL https://get.k3s.io | sh -
|
||||
sudo chmod -R a+rw /etc/rancher/k3s
|
||||
kubectl version
|
||||
environment:
|
||||
INSTALL_K3S_EXEC: --docker
|
||||
INSTALL_K3S_VERSION: v0.5.0
|
||||
- prepare_environment
|
||||
- checkout
|
||||
- run:
|
||||
name: Fix permissions on filesystem
|
||||
command: |
|
||||
mkdir -p /home/circleci/.go_workspace/pkg/mod
|
||||
chmod -R 777 /home/circleci/.go_workspace/pkg/mod
|
||||
mkdir -p /tmp/go-build-cache
|
||||
chmod -R 777 /tmp/go-build-cache
|
||||
- attach_go_cache
|
||||
- run:
|
||||
name: Update kubectl configuration for container
|
||||
command: |
|
||||
ipaddr=$(ifconfig $IFACE |grep "inet " | awk '{print $2}')
|
||||
if echo $ipaddr | grep -q 'addr:'; then
|
||||
ipaddr=$(echo $ipaddr | awk -F ':' '{print $2}')
|
||||
fi
|
||||
test -d $HOME/.kube || mkdir -p $HOME/.kube
|
||||
kubectl config view --raw | sed -e "s/127.0.0.1:6443/${ipaddr}:6443/g" -e "s/localhost:6443/${ipaddr}:6443/g" > $HOME/.kube/config
|
||||
environment:
|
||||
IFACE: ens4
|
||||
- run:
|
||||
name: Start E2E test server
|
||||
command: make start-e2e
|
||||
background: true
|
||||
environment:
|
||||
DOCKER_SRCDIR: /home/circleci/.go_workspace/src
|
||||
ARGOCD_E2E_TEST: "true"
|
||||
ARGOCD_IN_CI: "true"
|
||||
GOPATH: /home/circleci/.go_workspace
|
||||
- run:
|
||||
name: Wait for API server to become available
|
||||
command: |
|
||||
count=1
|
||||
until curl -v http://localhost:8080/healthz; do
|
||||
sleep 10;
|
||||
if test $count -ge 60; then
|
||||
echo "Timeout"
|
||||
exit 1
|
||||
fi
|
||||
count=$((count+1))
|
||||
done
|
||||
- run:
|
||||
name: Run E2E tests
|
||||
command: |
|
||||
make test-e2e
|
||||
environment:
|
||||
ARGOCD_OPTS: "--plaintext"
|
||||
ARGOCD_E2E_K3S: "true"
|
||||
IFACE: ens4
|
||||
DOCKER_SRCDIR: /home/circleci/.go_workspace/src
|
||||
GOPATH: /home/circleci/.go_workspace
|
||||
- store_test_results:
|
||||
path: test-results
|
||||
- store_artifacts:
|
||||
path: test-results
|
||||
destination: .
|
||||
ui:
|
||||
docker:
|
||||
- image: node:11.15.0
|
||||
working_directory: ~/argo-cd/ui
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/argo-cd/
|
||||
- restore_cache:
|
||||
keys:
|
||||
- yarn-packages-v4-{{ checksum "yarn.lock" }}
|
||||
- run: yarn install --frozen-lockfile --ignore-optional --non-interactive
|
||||
- save_cache:
|
||||
key: yarn-packages-v4-{{ checksum "yarn.lock" }}
|
||||
paths: [~/.cache/yarn, node_modules]
|
||||
- run: yarn test
|
||||
- run: ./node_modules/.bin/codecov -p ..
|
||||
- run: NODE_ENV='production' yarn build
|
||||
- run: yarn lint
|
||||
- save_node_modules
|
||||
|
||||
orbs:
|
||||
sonarcloud: sonarsource/sonarcloud@1.0.1
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
workflow:
|
||||
jobs:
|
||||
- build
|
||||
- test:
|
||||
requires:
|
||||
- build
|
||||
- codegen:
|
||||
requires:
|
||||
- build
|
||||
- ui:
|
||||
requires:
|
||||
- build
|
||||
- sonarcloud:
|
||||
context: SonarCloud
|
||||
requires:
|
||||
- test
|
||||
- ui
|
||||
- e2e:
|
||||
requires:
|
||||
- build
|
||||
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -6,8 +6,7 @@ labels: 'bug'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
If you are trying to resolve an environment-specific issue or have a one-off question about the edge case that does not require a feature then please consider asking a
|
||||
question in argocd slack [channel](https://argoproj.github.io/community/join-slack).
|
||||
If you are trying to resolve an environment-specific issue or have a one-off question about the edge case that does not require a feature then please consider asking a question in argocd slack [channel](https://argoproj.github.io/community/join-slack).
|
||||
|
||||
Checklist:
|
||||
|
||||
|
||||
12
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
12
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
blank_issues_enabled: false
|
||||
|
||||
contact_links:
|
||||
- name: Have you read the docs?
|
||||
url: https://argo-cd.readthedocs.io/
|
||||
about: Much help can be found in the docs
|
||||
- name: Ask a question
|
||||
url: https://github.com/argoproj/argo-cd/discussions/new
|
||||
about: Ask a question or start a discussion about Argo CD
|
||||
- name: Chat on Slack
|
||||
url: https://argoproj.github.io/community/join-slack
|
||||
about: Maybe chatting with the community can help
|
||||
12
.github/pull_request_template.md
vendored
12
.github/pull_request_template.md
vendored
@@ -1,7 +1,17 @@
|
||||
Note on DCO:
|
||||
|
||||
If the DCO action in the integration test fails, one or more of your commits are not signed off. Please click on the *Details* link next to the DCO action for instructions on how to resolve this.
|
||||
|
||||
Checklist:
|
||||
|
||||
* [ ] Either (a) I've created an [enhancement proposal](https://github.com/argoproj/argo-cd/issues/new/choose) and discussed it with the community, (b) this is a bug fix, or (c) this does not need to be in the release notes.
|
||||
* [ ] The title of the PR states what changed and the related issues number (used for the release note).
|
||||
* [ ] I've included "Closes [ISSUE #]" or "Fixes [ISSUE #]" in the description to automatically close the associated issue.
|
||||
* [ ] I've updated both the CLI and UI to expose my feature, or I plan to submit a second PR with them.
|
||||
* [ ] Does this PR require documentation updates?
|
||||
* [ ] I've updated documentation as required by this PR.
|
||||
* [ ] Optional. My organization is added to USERS.md.
|
||||
* [ ] I've signed the CLA and my build is green ([troubleshooting builds](https://argoproj.github.io/argo-cd/developer-guide/ci/)).
|
||||
* [ ] I have signed off all my commits as required by [DCO](https://github.com/argoproj/argoproj/tree/master/community#contributing-to-argo)
|
||||
* [ ] I have written unit and/or e2e tests for my change. PRs without these are unlikely to be merged.
|
||||
* [ ] My build is green ([troubleshooting builds](https://argo-cd.readthedocs.io/en/latest/developer-guide/ci/)).
|
||||
|
||||
|
||||
112
.github/workflows/ci-build.yaml
vendored
112
.github/workflows/ci-build.yaml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: '1.14.2'
|
||||
go-version: '1.14.12'
|
||||
- name: Download all Go modules
|
||||
run: |
|
||||
go mod download
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: '1.14.2'
|
||||
go-version: '1.14.12'
|
||||
- name: Restore go build cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
@@ -67,10 +67,10 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v1
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
with:
|
||||
version: v1.26
|
||||
args: --timeout 5m
|
||||
version: v1.29
|
||||
args: --timeout 5m --exclude SA5011
|
||||
|
||||
test-go:
|
||||
name: Run unit tests for Go packages
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: '1.14.2'
|
||||
go-version: '1.14.12'
|
||||
- name: Install required packages
|
||||
run: |
|
||||
sudo apt-get install git -y
|
||||
@@ -99,9 +99,11 @@ jobs:
|
||||
run: |
|
||||
git fetch --prune --no-tags --depth=1 origin +refs/heads/*:refs/remotes/origin/*
|
||||
- name: Add ~/go/bin to PATH
|
||||
run: echo "::add-path::/home/runner/go/bin"
|
||||
run: |
|
||||
echo "/home/runner/go/bin" >> $GITHUB_PATH
|
||||
- name: Add /usr/local/bin to PATH
|
||||
run: echo "::add-path::/usr/local/bin"
|
||||
run: |
|
||||
echo "/usr/local/bin" >> $GITHUB_PATH
|
||||
- name: Restore go build cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
@@ -130,6 +132,61 @@ jobs:
|
||||
name: test-results
|
||||
path: test-results/
|
||||
|
||||
test-go-race:
|
||||
name: Run unit tests with -race, for Go packages
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-go
|
||||
steps:
|
||||
- name: Create checkout directory
|
||||
run: mkdir -p ~/go/src/github.com/argoproj
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Create symlink in GOPATH
|
||||
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: '1.14.12'
|
||||
- name: Install required packages
|
||||
run: |
|
||||
sudo apt-get install git -y
|
||||
- name: Switch to temporal branch so we re-attach head
|
||||
run: |
|
||||
git switch -c temporal-pr-branch
|
||||
git status
|
||||
- name: Fetch complete history for blame information
|
||||
run: |
|
||||
git fetch --prune --no-tags --depth=1 origin +refs/heads/*:refs/remotes/origin/*
|
||||
- name: Add ~/go/bin to PATH
|
||||
run: |
|
||||
echo "/home/runner/go/bin" >> $GITHUB_PATH
|
||||
- name: Add /usr/local/bin to PATH
|
||||
run: |
|
||||
echo "/usr/local/bin" >> $GITHUB_PATH
|
||||
- name: Restore go build cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
|
||||
- name: Install all tools required for building & testing
|
||||
run: |
|
||||
make install-test-tools-local
|
||||
- name: Setup git username and email
|
||||
run: |
|
||||
git config --global user.name "John Doe"
|
||||
git config --global user.email "john.doe@example.com"
|
||||
- name: Download and vendor all required packages
|
||||
run: |
|
||||
go mod download
|
||||
- name: Run all unit tests
|
||||
run: make test-race-local
|
||||
- name: Generate test results artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: race-results
|
||||
path: test-results/
|
||||
|
||||
codegen:
|
||||
name: Check changes to generated code
|
||||
runs-on: ubuntu-latest
|
||||
@@ -139,15 +196,17 @@ jobs:
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: '1.14.2'
|
||||
go-version: '1.14.12'
|
||||
- name: Create symlink in GOPATH
|
||||
run: |
|
||||
mkdir -p ~/go/src/github.com/argoproj
|
||||
cp -a ../argo-cd ~/go/src/github.com/argoproj
|
||||
- name: Add /usr/local/bin to PATH
|
||||
run: echo "::add-path::/usr/local/bin"
|
||||
- name: Add ~/go/bin to PATH
|
||||
run: echo "::add-path::/home/runner/go/bin"
|
||||
run: |
|
||||
echo "/home/runner/go/bin" >> $GITHUB_PATH
|
||||
- name: Add /usr/local/bin to PATH
|
||||
run: |
|
||||
echo "/usr/local/bin" >> $GITHUB_PATH
|
||||
- name: Download & vendor dependencies
|
||||
run: |
|
||||
# We need to vendor go modules for codegen yet
|
||||
@@ -184,7 +243,7 @@ jobs:
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '11.15.0'
|
||||
node-version: '12.18.4'
|
||||
- name: Restore node dependency cache
|
||||
id: cache-dependencies
|
||||
uses: actions/cache@v1
|
||||
@@ -274,6 +333,9 @@ jobs:
|
||||
test-e2e:
|
||||
name: Run end-to-end tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
k3s-version: [v1.20.2, v1.19.2, v1.18.9, v1.17.11, v1.16.15]
|
||||
needs:
|
||||
- build-go
|
||||
env:
|
||||
@@ -292,10 +354,10 @@ jobs:
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: '1.14.2'
|
||||
go-version: '1.14.12'
|
||||
- name: Install K3S
|
||||
env:
|
||||
INSTALL_K3S_VERSION: v0.5.0
|
||||
INSTALL_K3S_VERSION: ${{ matrix.k3s-version }}+k3s1
|
||||
run: |
|
||||
set -x
|
||||
curl -sfL https://get.k3s.io | sh -
|
||||
@@ -309,10 +371,12 @@ jobs:
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
|
||||
- name: Add /usr/local/bin to PATH
|
||||
run: echo "::add-path::/usr/local/bin"
|
||||
- name: Add ~/go/bin to PATH
|
||||
run: echo "::add-path::/home/runner/go/bin"
|
||||
run: |
|
||||
echo "/home/runner/go/bin" >> $GITHUB_PATH
|
||||
- name: Add /usr/local/bin to PATH
|
||||
run: |
|
||||
echo "/usr/local/bin" >> $GITHUB_PATH
|
||||
- name: Download Go dependencies
|
||||
run: |
|
||||
go mod download
|
||||
@@ -326,9 +390,9 @@ jobs:
|
||||
git config --global user.email "john.doe@example.com"
|
||||
- name: Pull Docker image required for tests
|
||||
run: |
|
||||
docker pull quay.io/dexidp/dex:v2.22.0
|
||||
docker pull quay.io/dexidp/dex:v2.25.0
|
||||
docker pull argoproj/argo-cd-ci-builder:v1.0.0
|
||||
docker pull redis:5.0.8-alpine
|
||||
docker pull redis:5.0.10-alpine
|
||||
- name: Create target directory for binaries in the build-process
|
||||
run: |
|
||||
mkdir -p dist
|
||||
@@ -341,7 +405,7 @@ jobs:
|
||||
# port 8080 which is not visible in netstat -tulpen, but still there
|
||||
# with a HTTP listener. We have API server listening on port 8088
|
||||
# instead.
|
||||
make start-e2e-local &
|
||||
make start-e2e-local 2>&1 | sed -r "s/[[:cntrl:]]\[[0-9]{1,3}m//g" > /tmp/e2e-server.log &
|
||||
count=1
|
||||
until curl -f http://127.0.0.1:8088/healthz; do
|
||||
sleep 10;
|
||||
@@ -355,3 +419,9 @@ jobs:
|
||||
run: |
|
||||
set -x
|
||||
make test-e2e-local
|
||||
- name: Upload e2e-server logs
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: e2e-server-k8s${{ matrix.k3s-version }}.log
|
||||
path: /tmp/e2e-server.log
|
||||
if: ${{ failure() }}
|
||||
|
||||
3
.github/workflows/gh-pages.yaml
vendored
3
.github/workflows/gh-pages.yaml
vendored
@@ -19,9 +19,8 @@ jobs:
|
||||
python-version: 3.x
|
||||
- name: build
|
||||
run: |
|
||||
pip install mkdocs==1.0.4 mkdocs_material==4.1.1
|
||||
pip install -r docs/requirements.txt
|
||||
mkdocs build
|
||||
mkdir ./site/.circleci && echo '{version: 2, jobs: {build: {branches: {ignore: gh-pages}}}}' > ./site/.circleci/config.yml
|
||||
- name: deploy
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
uses: peaceiris/actions-gh-pages@v2.5.0
|
||||
|
||||
6
.github/workflows/image.yaml
vendored
6
.github/workflows/image.yaml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: '1.14.1'
|
||||
go-version: '1.14.12'
|
||||
- uses: actions/checkout@master
|
||||
with:
|
||||
path: src/github.com/argoproj/argo-cd
|
||||
@@ -42,9 +42,9 @@ jobs:
|
||||
env:
|
||||
TOKEN: ${{ secrets.TOKEN }}
|
||||
- run: |
|
||||
docker run -v $(pwd):/src -w /src --rm -t lyft/kustomizer:v3.3.0 kustomize edit set image argoproj/argocd=docker.pkg.github.com/argoproj/argo-cd/argocd:${{ steps.image.outputs.tag }}
|
||||
docker run -v $(pwd):/src -w /src --rm -t lyft/kustomizer:v3.3.0 kustomize edit set image quay.io/argoproj/argocd=docker.pkg.github.com/argoproj/argo-cd/argocd:${{ steps.image.outputs.tag }}
|
||||
git config --global user.email 'ci@argoproj.com'
|
||||
git config --global user.name 'CI'
|
||||
git diff --exit-code && echo 'Already deployed' || (git commit -am 'Upgrade argocd to ${{ steps.image.outputs.tag }}' && git push)
|
||||
working-directory: argoproj-deployments/argocd
|
||||
# TODO: clean up old images once github supports it: https://github.community/t5/How-to-use-Git-and-GitHub/Deleting-images-from-Github-Package-Registry/m-p/41202/thread-id/9811
|
||||
# TODO: clean up old images once github supports it: https://github.community/t5/How-to-use-Git-and-GitHub/Deleting-images-from-Github-Package-Registry/m-p/41202/thread-id/9811
|
||||
|
||||
84
.github/workflows/release.yaml
vendored
84
.github/workflows/release.yaml
vendored
@@ -23,11 +23,9 @@ jobs:
|
||||
DRY_RUN: false
|
||||
# Whether a draft release should be created, instead of public one
|
||||
DRAFT_RELEASE: false
|
||||
# The name of the repository containing tap formulae
|
||||
TAP_REPOSITORY: argoproj/homebrew-tap
|
||||
# Whether to update homebrew with this release as well
|
||||
# Set RELEASE_HOMEBREW_TOKEN secret in repository for this to work - needs
|
||||
# access to public repositories (or homebrew-tap repo specifically)
|
||||
# access to public repositories
|
||||
UPDATE_HOMEBREW: false
|
||||
# Name of the GitHub user for Git config
|
||||
GIT_USERNAME: argo-bot
|
||||
@@ -46,7 +44,7 @@ jobs:
|
||||
# Target version must match major.minor.patch and optional -rcX suffix
|
||||
# where X must be a number.
|
||||
TARGET_VERSION=${SOURCE_TAG#*release-v}
|
||||
if ! echo ${TARGET_VERSION} | egrep '^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)*$'; then
|
||||
if ! echo "${TARGET_VERSION}" | egrep '^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)*$'; then
|
||||
echo "::error::Target version '${TARGET_VERSION}' is malformed, refusing to continue." >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -78,10 +76,10 @@ jobs:
|
||||
fi
|
||||
|
||||
# Make the variables available in follow-up steps
|
||||
echo "::set-env name=TARGET_VERSION::${TARGET_VERSION}"
|
||||
echo "::set-env name=TARGET_BRANCH::${TARGET_BRANCH}"
|
||||
echo "::set-env name=RELEASE_TAG::${RELEASE_TAG}"
|
||||
echo "::set-env name=PRE_RELEASE::${PRE_RELEASE}"
|
||||
echo "TARGET_VERSION=${TARGET_VERSION}" >> $GITHUB_ENV
|
||||
echo "TARGET_BRANCH=${TARGET_BRANCH}" >> $GITHUB_ENV
|
||||
echo "RELEASE_TAG=${RELEASE_TAG}" >> $GITHUB_ENV
|
||||
echo "PRE_RELEASE=${PRE_RELEASE}" >> $GITHUB_ENV
|
||||
|
||||
- name: Check if our release tag has a correct annotation
|
||||
run: |
|
||||
@@ -103,16 +101,16 @@ jobs:
|
||||
# Whatever is in commit history for the tag, we only want that
|
||||
# annotation from our tag. We discard everything else.
|
||||
if test "$begin" = "false"; then
|
||||
if echo $line | grep -q "tag ${SOURCE_TAG#refs/tags/}"; then begin="true"; fi
|
||||
if echo "$line" | grep -q "tag ${SOURCE_TAG#refs/tags/}"; then begin="true"; fi
|
||||
continue
|
||||
fi
|
||||
if test "$prefix" = "true"; then
|
||||
if test -z "$line"; then prefix=false; fi
|
||||
else
|
||||
if echo $line | egrep -q '^commit [0-9a-f]+'; then
|
||||
if echo "$line" | egrep -q '^commit [0-9a-f]+'; then
|
||||
break
|
||||
fi
|
||||
echo $line >> ${RELEASE_NOTES}
|
||||
echo "$line" >> ${RELEASE_NOTES}
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -136,12 +134,12 @@ jobs:
|
||||
|
||||
# We store path to temporary release notes file for later reading, we
|
||||
# need it when creating release.
|
||||
echo "::set-env name=RELEASE_NOTES::$RELEASE_NOTES"
|
||||
echo "RELEASE_NOTES=${RELEASE_NOTES}" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: '1.14.2'
|
||||
go-version: '1.14.12'
|
||||
|
||||
- name: Setup Git author information
|
||||
run: |
|
||||
@@ -195,10 +193,15 @@ jobs:
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.RELEASE_DOCKERHUB_USERNAME }}
|
||||
DOCKER_TOKEN: ${{ secrets.RELEASE_DOCKERHUB_TOKEN }}
|
||||
QUAY_USERNAME: ${{ secrets.RELEASE_QUAY_USERNAME }}
|
||||
QUAY_TOKEN: ${{ secrets.RELEASE_QUAY_TOKEN }}
|
||||
run: |
|
||||
set -ue
|
||||
docker login --username "${DOCKER_USERNAME}" --password "${DOCKER_TOKEN}"
|
||||
docker push ${IMAGE_NAMESPACE}/argocd:v${TARGET_VERSION}
|
||||
docker login quay.io --username "${QUAY_USERNAME}" --password "${QUAY_TOKEN}"
|
||||
docker tag ${IMAGE_NAMESPACE}/argocd:v${TARGET_VERSION} quay.io/${IMAGE_NAMESPACE}/argocd:v${TARGET_VERSION}
|
||||
docker push quay.io/${IMAGE_NAMESPACE}/argocd:v${TARGET_VERSION}
|
||||
if: ${{ env.DRY_RUN != 'true' }}
|
||||
|
||||
- name: Read release notes file
|
||||
@@ -258,32 +261,51 @@ jobs:
|
||||
asset_content_type: application/octet-stream
|
||||
if: ${{ env.DRY_RUN != 'true' }}
|
||||
|
||||
- name: Check out homebrew tap repository
|
||||
uses: actions/checkout@v2
|
||||
# include argocd-util as part of release artifacts (argoproj/argo-cd#5174)
|
||||
- name: Upload argocd-util-linux-amd64 binary to release assets
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
HOMEBREW_TOKEN: ${{ secrets.RELEASE_HOMEBREW_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
repository: ${{ env.TAP_REPOSITORY }}
|
||||
path: homebrew-tap
|
||||
fetch-depth: 0
|
||||
token: ${{ env.HOMEBREW_TOKEN }}
|
||||
if: ${{ env.HOMEBREW_TOKEN != '' && env.UPDATE_HOMEBREW == 'true' && env.PRE_RELEASE != 'true' }}
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./dist/argocd-linux-amd64
|
||||
asset_name: argocd-util-linux-amd64
|
||||
asset_content_type: application/octet-stream
|
||||
if: ${{ env.DRY_RUN != 'true' }}
|
||||
|
||||
- name: Update homebrew tap formula
|
||||
- name: Upload argocd-util-darwin-amd64 binary to release assets
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./dist/argocd-darwin-amd64
|
||||
asset_name: argocd-util-darwin-amd64
|
||||
asset_content_type: application/octet-stream
|
||||
if: ${{ env.DRY_RUN != 'true' }}
|
||||
|
||||
- name: Upload argocd-util-windows-amd64 binary to release assets
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./dist/argocd-windows-amd64.exe
|
||||
asset_name: argocd-util-windows-amd64.exe
|
||||
asset_content_type: application/octet-stream
|
||||
if: ${{ env.DRY_RUN != 'true' }}
|
||||
|
||||
- name: Update homebrew formula
|
||||
env:
|
||||
HOMEBREW_TOKEN: ${{ secrets.RELEASE_HOMEBREW_TOKEN }}
|
||||
run: |
|
||||
set -ue
|
||||
cd homebrew-tap
|
||||
./update.sh argocd ${TARGET_VERSION}
|
||||
git commit -am "Update argocd to ${TARGET_VERSION}"
|
||||
git push
|
||||
cd ..
|
||||
rm -rf homebrew-tap
|
||||
uses: dawidd6/action-homebrew-bump-formula@v3
|
||||
with:
|
||||
token: ${{env.HOMEBREW_TOKEN}}
|
||||
formula: argocd
|
||||
if: ${{ env.HOMEBREW_TOKEN != '' && env.UPDATE_HOMEBREW == 'true' && env.PRE_RELEASE != 'true' }}
|
||||
|
||||
- name: Delete original request tag from repository
|
||||
run: |
|
||||
set -ue
|
||||
git push --delete origin ${SOURCE_TAG}
|
||||
if: ${{ always() }}
|
||||
if: ${{ always() }}
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -12,3 +12,11 @@ coverage.out
|
||||
test-results
|
||||
.scannerwork
|
||||
.scratch
|
||||
node_modules/
|
||||
|
||||
# ignore built binaries
|
||||
cmd/argocd/argocd
|
||||
cmd/argocd-application-controller/argocd-application-controller
|
||||
cmd/argocd-repo-server/argocd-repo-server
|
||||
cmd/argocd-server/argocd-server
|
||||
cmd/argocd-util/argocd-util
|
||||
7
.readthedocs.yml
Normal file
7
.readthedocs.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
version: 2
|
||||
formats: all
|
||||
mkdocs:
|
||||
fail_on_warning: false
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
132
CHANGELOG.md
132
CHANGELOG.md
@@ -1,6 +1,136 @@
|
||||
# Changelog
|
||||
|
||||
## v1.7.0 (Unreleased)
|
||||
## v1.8.0 (Unreleased)
|
||||
|
||||
### Mono-Repository Improvements
|
||||
|
||||
Enhanced performance during manifest generation from mono-repository - the repository that represents the
|
||||
desired state of the whole cluster and contains hundreds of applications. The improved argocd-repo-server
|
||||
now able to concurrently generate manifests from the same repository and for the same commit SHA. This
|
||||
might provide 10x performance improvement of manifests generation.
|
||||
|
||||
### Annotation Based Path Detection
|
||||
|
||||
The feature that allows specifying which source repository directories influence the application manifest generation
|
||||
using the `argocd.argoproj.io/manifest-generate-paths` annotation. The annotation improves the Git webhook handler
|
||||
behavior. The webhook avoids related applications reconciliation if no related files have been changed by the Git commit
|
||||
and even allows to skip manifests generation for new commit by re-using generation manifests for the previous commit.
|
||||
|
||||
### Horizontal Controller Scaling
|
||||
|
||||
This release allows scaling the `argocd-application-controller` horizontally. This allows you to manage as many Kubernetes clusters
|
||||
as needed using a single Argo CD instance.
|
||||
|
||||
## New Core Functionality Features
|
||||
|
||||
Besides performance improvements, Argo CD got a lot of usability enhancements and new features:
|
||||
|
||||
* Namespace and CRD creation [#4354](https://github.com/argoproj/argo-cd/issues/4354)
|
||||
* Unknown fields of built-in K8S types [#1787](https://github.com/argoproj/argo-cd/issues/1787)
|
||||
* Endpoints Diffing [#1816](https://github.com/argoproj/argo-cd/issues/1816)
|
||||
* Better compatibility with Helm Hooks [#1816](https://github.com/argoproj/argo-cd/issues/1816)
|
||||
* App-of-Apps Health Assessment [#3781](https://github.com/argoproj/argo-cd/issues/3781)
|
||||
|
||||
## Global Projects
|
||||
|
||||
This release makes it easy to manage an Argo CD that has hundreds of Projects. Instead of duplicating the same organization-wide rules in all projects
|
||||
you can put such rules into one project and make this project “global” for all other projects. Rules defined in the global project are inherited by all
|
||||
other projects and therefore don’t have to be duplicated. The sample below demonstrates how you can create a global project and specify which project should
|
||||
inherit global project rules using Kubernetes labels.
|
||||
|
||||
## User Interface Improvements
|
||||
|
||||
The Argo CD user interface is an important part of a project and we keep working hard on improving the user experience. Here is an incomplete list of implemented improvements:
|
||||
|
||||
* Improved Applications Filters [#4622](https://github.com/argoproj/argo-cd/issues/4622)
|
||||
* Git tags and branches autocompletion [#4713](https://github.com/argoproj/argo-cd/issues/4713)
|
||||
* Project Details Page [#4400](https://github.com/argoproj/argo-cd/issues/4400)
|
||||
* New version information panel [#4376](https://github.com/argoproj/argo-cd/issues/4376)
|
||||
* Progress Indicators [#4411](https://github.com/argoproj/argo-cd/issues/4411)
|
||||
* External links annotations [#4380](https://github.com/argoproj/argo-cd/issues/4380) and more!
|
||||
|
||||
## Config Management Tools Enhancements
|
||||
|
||||
* OCI Based Repositories [#4018](https://github.com/argoproj/argo-cd/issues/4018)
|
||||
* Configurable Helm Versions [#4111](https://github.com/argoproj/argo-cd/issues/4111)
|
||||
|
||||
## Bug fixes and under the hood changes
|
||||
|
||||
In addition to new features and enhancements, we’ve fixed more than 50 bugs and upgraded third-party components and libraries that Argo CD relies on.
|
||||
|
||||
## v1.7.9 (2020-11-17)
|
||||
|
||||
- fix: improve commit verification tolerance (#4825)
|
||||
- fix: argocd diff --local should not print data of local secrets (#4850)
|
||||
- fix(ui): stack overflow crash of resource tree view for large applications (#4685)
|
||||
- chore: Update golang to v1.14.12 [backport to release-1.7] (#4834)
|
||||
- chore: Update redis to 5.0.10 (#4767)
|
||||
- chore: Replace deprecated GH actions directives for integration tests (#4589)
|
||||
|
||||
## v1.7.8 (2020-10-15)
|
||||
|
||||
- fix(logging.go): changing marshaler for JSON logging to use gogo (#4319)
|
||||
- fix: login with apiKey capability (#4557)
|
||||
- fix: api-server should not try creating default project it is exists already (#4517)
|
||||
- fix: JS error on application list page if app has no namespace (#4499)
|
||||
|
||||
|
||||
## v1.7.7 (2020-09-28)
|
||||
|
||||
- fix: Support transition from a git managed namespace to auto create (#4401)
|
||||
- fix: reduce memory spikes during cluster cache refresh (#4298)
|
||||
- fix: No error/warning condition if application destination namespace not monitored by Argo CD (#4329)
|
||||
- fix: Fix local diff/sync of apps using cluster name (#4201)
|
||||
|
||||
## v1.7.6 (2020-09-18)
|
||||
|
||||
- fix: Added cluster authentication to AKS clusters (#4265)
|
||||
- fix: swagger UI stuck loading (#4377)
|
||||
- fix: prevent 'argocd app sync' hangs if sync is completed too quickly (#4373)
|
||||
- fix: argocd app wait/sync might stuck (#4350)
|
||||
- fix: failed syncs are not retried soon enough (#4353)
|
||||
|
||||
## v1.7.5 (2020-09-15)
|
||||
|
||||
- fix: app create with -f should not ignore other options (#4322)
|
||||
- fix: limit concurrent list requests accross all clusters (#4328)
|
||||
- fix: fix possible deadlock in /v1/api/stream/applications and /v1/api/application APIs (#4315)
|
||||
- fix: WatchResourceTree does not enforce RBAC (#4311)
|
||||
- fix: app refresh API should use app resource version (#4303)
|
||||
- fix: use informer instead of k8s watch to ensure app is refreshed (#4290)
|
||||
|
||||
|
||||
## v1.7.4 (2020-09-04)
|
||||
|
||||
- fix: automatically stop watch API requests when page is hidden (#4269)
|
||||
- fix: upgrade gitops-engine dependency (issues #4242, #1881) (#4268)
|
||||
- fix: application stream API should not return 'ADDED' events if resource version is provided (#4260)
|
||||
- fix: return parsing error (#3942)
|
||||
- fix: JS error when using cluster filter in the /application view (#4247)
|
||||
- fix: improve applications list page client side performance (#4244)
|
||||
|
||||
## v1.7.3 (2020-09-01)
|
||||
|
||||
- fix: application details page crash when app is deleted (#4229)
|
||||
- fix: api-server unnecessary normalize projects on every start (#4219)
|
||||
- fix: load only project names in UI (#4217)
|
||||
- fix: Re-create already initialized ARGOCD_GNUPGHOME on startup (#4214) (#4223)
|
||||
- fix: Add openshift as a dex connector type which requires a redirectURI (#4222)
|
||||
- fix: Replace status.observedAt with redis pub/sub channels for resource tree updates (#1340) (#4208)
|
||||
- fix: cache inconsistency of child resources (#4053) (#4202)
|
||||
- fix: do not include kube-api check in application liveness flow (#4163)
|
||||
|
||||
## v1.7.2 (2020-08-27)
|
||||
|
||||
- fix: Sync hangs with cert-manager on latest RC (#4105)
|
||||
- fix: support for PKCE for cli login (#2932)
|
||||
|
||||
## v1.7.2 (2020-08-25)
|
||||
|
||||
- fix: Unable to create project JWT token on K8S v1.15 (#4165)
|
||||
- fix: Argo CD does not exclude creationTimestamp from diffing (#4157)
|
||||
|
||||
## v1.7.0 (2020-08-24)
|
||||
|
||||
### GnuPG Signature Verification
|
||||
|
||||
|
||||
28
Dockerfile
28
Dockerfile
@@ -1,10 +1,10 @@
|
||||
ARG BASE_IMAGE=debian:10-slim
|
||||
ARG BASE_IMAGE=ubuntu:20.10
|
||||
####################################################################################################
|
||||
# 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 golang:1.14.1 as builder
|
||||
FROM golang:1.14.12 as builder
|
||||
|
||||
RUN echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list
|
||||
|
||||
@@ -28,7 +28,6 @@ ADD hack/installers installers
|
||||
ADD hack/tool-versions.sh .
|
||||
|
||||
RUN ./install.sh packr-linux
|
||||
RUN ./install.sh kubectl-linux
|
||||
RUN ./install.sh ksonnet-linux
|
||||
RUN ./install.sh helm2-linux
|
||||
RUN ./install.sh helm-linux
|
||||
@@ -41,7 +40,7 @@ FROM $BASE_IMAGE as argocd-base
|
||||
|
||||
USER root
|
||||
|
||||
RUN echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN groupadd -g 999 argocd && \
|
||||
useradd -r -u 999 -g argocd argocd && \
|
||||
@@ -50,6 +49,7 @@ RUN groupadd -g 999 argocd && \
|
||||
chmod g=u /home/argocd && \
|
||||
chmod g=u /etc/passwd && \
|
||||
apt-get update && \
|
||||
apt-get dist-upgrade -y && \
|
||||
apt-get install -y git git-lfs python3-pip tini gpg && \
|
||||
apt-get clean && \
|
||||
pip3 install awscli==1.18.80 && \
|
||||
@@ -61,7 +61,6 @@ COPY hack/git-verify-wrapper.sh /usr/local/bin/git-verify-wrapper.sh
|
||||
COPY --from=builder /usr/local/bin/ks /usr/local/bin/ks
|
||||
COPY --from=builder /usr/local/bin/helm2 /usr/local/bin/helm2
|
||||
COPY --from=builder /usr/local/bin/helm /usr/local/bin/helm
|
||||
COPY --from=builder /usr/local/bin/kubectl /usr/local/bin/kubectl
|
||||
COPY --from=builder /usr/local/bin/kustomize /usr/local/bin/kustomize
|
||||
# script to add current (possibly arbitrary) user to /etc/passwd at runtime
|
||||
# (if it's not already there, to be openshift friendly)
|
||||
@@ -87,7 +86,7 @@ WORKDIR /home/argocd
|
||||
####################################################################################################
|
||||
# Argo CD UI stage
|
||||
####################################################################################################
|
||||
FROM node:11.15.0 as argocd-ui
|
||||
FROM node:12.18.4 as argocd-ui
|
||||
|
||||
WORKDIR /src
|
||||
ADD ["ui/package.json", "ui/yarn.lock", "./"]
|
||||
@@ -103,7 +102,7 @@ RUN NODE_ENV='production' yarn build
|
||||
####################################################################################################
|
||||
# Argo CD Build stage which performs the actual build of Argo CD binaries
|
||||
####################################################################################################
|
||||
FROM golang:1.14.1 as argocd-build
|
||||
FROM golang:1.14.12 as argocd-build
|
||||
|
||||
COPY --from=builder /usr/local/bin/packr /usr/local/bin/packr
|
||||
|
||||
@@ -116,12 +115,12 @@ RUN go mod download
|
||||
|
||||
# Perform the build
|
||||
COPY . .
|
||||
RUN make cli-local server controller repo-server argocd-util
|
||||
RUN make argocd-all
|
||||
|
||||
ARG BUILD_ALL_CLIS=true
|
||||
RUN if [ "$BUILD_ALL_CLIS" = "true" ] ; then \
|
||||
make CLI_NAME=argocd-darwin-amd64 GOOS=darwin cli-local && \
|
||||
make CLI_NAME=argocd-windows-amd64.exe GOOS=windows cli-local \
|
||||
make BIN_NAME=argocd-darwin-amd64 GOOS=darwin argocd-all && \
|
||||
make BIN_NAME=argocd-windows-amd64.exe GOOS=windows argocd-all \
|
||||
; fi
|
||||
|
||||
####################################################################################################
|
||||
@@ -130,3 +129,12 @@ RUN if [ "$BUILD_ALL_CLIS" = "true" ] ; then \
|
||||
FROM argocd-base
|
||||
COPY --from=argocd-build /go/src/github.com/argoproj/argo-cd/dist/argocd* /usr/local/bin/
|
||||
COPY --from=argocd-ui ./src/dist/app /shared/app
|
||||
|
||||
USER root
|
||||
RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-util
|
||||
RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-server
|
||||
RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-repo-server
|
||||
RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-application-controller
|
||||
RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-dex
|
||||
|
||||
USER 999
|
||||
@@ -2,5 +2,18 @@
|
||||
# argocd-dev
|
||||
####################################################################################################
|
||||
FROM argocd-base
|
||||
COPY argocd* /usr/local/bin/
|
||||
COPY argocd /usr/local/bin/
|
||||
COPY argocd-darwin-amd64 /usr/local/bin/
|
||||
COPY argocd-windows-amd64.exe /usr/local/bin/
|
||||
|
||||
USER root
|
||||
RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-server
|
||||
RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-repo-server
|
||||
RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-application-controller
|
||||
RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-dex
|
||||
RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-util
|
||||
RUN ln -s /usr/local/bin/argocd-darwin-amd64 /usr/local/bin/argocd-util-darwin-amd64
|
||||
RUN ln -s /usr/local/bin/argocd-windows-amd64.exe /usr/local/bin/argocd-util-windows-amd64.exe
|
||||
USER 999
|
||||
|
||||
COPY --from=argocd-ui ./src/dist/app /shared/app
|
||||
|
||||
135
Makefile
135
Makefile
@@ -2,6 +2,8 @@ PACKAGE=github.com/argoproj/argo-cd/common
|
||||
CURRENT_DIR=$(shell pwd)
|
||||
DIST_DIR=${CURRENT_DIR}/dist
|
||||
CLI_NAME=argocd
|
||||
UTIL_CLI_NAME=argocd-util
|
||||
BIN_NAME=argocd
|
||||
|
||||
HOST_OS:=$(shell go env GOOS)
|
||||
HOST_ARCH:=$(shell go env GOARCH)
|
||||
@@ -13,6 +15,7 @@ GIT_TAG=$(shell if [ -z "`git status --porcelain`" ]; then git describe --exact-
|
||||
GIT_TREE_STATE=$(shell if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)
|
||||
PACKR_CMD=$(shell if [ "`which packr`" ]; then echo "packr"; else echo "go run github.com/gobuffalo/packr/packr"; 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 all | grep k8s.io/client-go | cut -d ' ' -f5)
|
||||
|
||||
GOPATH?=$(shell if test -x `which go`; then go env GOPATH; else echo "$(HOME)/go"; fi)
|
||||
GOCACHE?=$(HOME)/.cache/go-build
|
||||
@@ -22,6 +25,11 @@ DOCKER_WORKDIR?=/go/src/github.com/argoproj/argo-cd
|
||||
|
||||
ARGOCD_PROCFILE?=Procfile
|
||||
|
||||
# Strict mode has been disabled in latest versions of mkdocs-material.
|
||||
# Thus pointing to the older image of mkdocs-material matching the version used by argo-cd.
|
||||
MKDOCS_DOCKER_IMAGE?=squidfunk/mkdocs-material:4.1.1
|
||||
MKDOCS_RUN_ARGS?=
|
||||
|
||||
# Configuration for building argocd-test-tools image
|
||||
TEST_TOOLS_NAMESPACE?=
|
||||
TEST_TOOLS_IMAGE=argocd-test-tools
|
||||
@@ -43,11 +51,21 @@ ARGOCD_TEST_E2E?=true
|
||||
|
||||
ARGOCD_LINT_GOGC?=20
|
||||
|
||||
# Depending on where we are (legacy or non-legacy pwd), we need to use
|
||||
# different Docker volume mounts for our source tree
|
||||
LEGACY_PATH=$(GOPATH)/src/github.com/argoproj/argo-cd
|
||||
ifeq ("$(PWD)","$(LEGACY_PATH)")
|
||||
DOCKER_SRC_MOUNT="$(DOCKER_SRCDIR):/go/src$(VOLUME_MOUNT)"
|
||||
else
|
||||
DOCKER_SRC_MOUNT="$(PWD):/go/src/github.com/argoproj/argo-cd$(VOLUME_MOUNT)"
|
||||
endif
|
||||
|
||||
# Runs any command in the argocd-test-utils container in server mode
|
||||
# Server mode container will start with uid 0 and drop privileges during runtime
|
||||
define run-in-test-server
|
||||
docker run --rm -it \
|
||||
--name argocd-test-server \
|
||||
-u $(shell id -u):$(shell id -g) \
|
||||
-e USER_ID=$(shell id -u) \
|
||||
-e HOME=/home/user \
|
||||
-e GOPATH=/go \
|
||||
@@ -55,7 +73,7 @@ define run-in-test-server
|
||||
-e ARGOCD_IN_CI=$(ARGOCD_IN_CI) \
|
||||
-e ARGOCD_E2E_TEST=$(ARGOCD_E2E_TEST) \
|
||||
-e ARGOCD_E2E_YARN_HOST=$(ARGOCD_E2E_YARN_HOST) \
|
||||
-v ${DOCKER_SRCDIR}:/go/src${VOLUME_MOUNT} \
|
||||
-v ${DOCKER_SRC_MOUNT} \
|
||||
-v ${GOPATH}/pkg/mod:/go/pkg/mod${VOLUME_MOUNT} \
|
||||
-v ${GOCACHE}:/tmp/go-build-cache${VOLUME_MOUNT} \
|
||||
-v ${HOME}/.kube:/home/user/.kube${VOLUME_MOUNT} \
|
||||
@@ -71,13 +89,13 @@ endef
|
||||
define run-in-test-client
|
||||
docker run --rm -it \
|
||||
--name argocd-test-client \
|
||||
-u $(shell id -u) \
|
||||
-u $(shell id -u):$(shell id -g) \
|
||||
-e HOME=/home/user \
|
||||
-e GOPATH=/go \
|
||||
-e ARGOCD_E2E_K3S=$(ARGOCD_E2E_K3S) \
|
||||
-e GOCACHE=/tmp/go-build-cache \
|
||||
-e ARGOCD_LINT_GOGC=$(ARGOCD_LINT_GOGC) \
|
||||
-v ${DOCKER_SRCDIR}:/go/src${VOLUME_MOUNT} \
|
||||
-v ${DOCKER_SRC_MOUNT} \
|
||||
-v ${GOPATH}/pkg/mod:/go/pkg/mod${VOLUME_MOUNT} \
|
||||
-v ${GOCACHE}:/tmp/go-build-cache${VOLUME_MOUNT} \
|
||||
-v ${HOME}/.kube:/home/user/.kube${VOLUME_MOUNT} \
|
||||
@@ -89,7 +107,7 @@ endef
|
||||
|
||||
#
|
||||
define exec-in-test-server
|
||||
docker exec -it -u $(shell id -u) -e ARGOCD_E2E_K3S=$(ARGOCD_E2E_K3S) argocd-test-server $(1)
|
||||
docker exec -it -u $(shell id -u):$(shell id -g) -e ARGOCD_E2E_K3S=$(ARGOCD_E2E_K3S) argocd-test-server $(1)
|
||||
endef
|
||||
|
||||
PATH:=$(PATH):$(PWD)/hack
|
||||
@@ -108,7 +126,9 @@ override LDFLAGS += \
|
||||
-X ${PACKAGE}.version=${VERSION} \
|
||||
-X ${PACKAGE}.buildDate=${BUILD_DATE} \
|
||||
-X ${PACKAGE}.gitCommit=${GIT_COMMIT} \
|
||||
-X ${PACKAGE}.gitTreeState=${GIT_TREE_STATE}
|
||||
-X ${PACKAGE}.gitTreeState=${GIT_TREE_STATE}\
|
||||
-X ${PACKAGE}.gitTreeState=${GIT_TREE_STATE}\
|
||||
-X ${PACKAGE}.kubectlVersion=${KUBECTL_VERSION}
|
||||
|
||||
ifeq (${STATIC_BUILD}, true)
|
||||
override LDFLAGS += -extldflags "-static"
|
||||
@@ -134,28 +154,43 @@ endif
|
||||
.PHONY: all
|
||||
all: cli image argocd-util
|
||||
|
||||
# We have some legacy requirements for being checked out within $GOPATH.
|
||||
# The ensure-gopath target can be used as dependency to ensure we are running
|
||||
# within these boundaries.
|
||||
.PHONY: ensure-gopath
|
||||
ensure-gopath:
|
||||
ifneq ("$(PWD)","$(LEGACY_PATH)")
|
||||
@echo "Due to legacy requirements for codegen, repository needs to be checked out within \$$GOPATH"
|
||||
@echo "Location of this repo should be '$(LEGACY_PATH)' but is '$(PWD)'"
|
||||
@exit 1
|
||||
endif
|
||||
|
||||
.PHONY: gogen
|
||||
gogen:
|
||||
gogen: ensure-gopath
|
||||
export GO111MODULE=off
|
||||
go generate ./util/argo/...
|
||||
|
||||
.PHONY: protogen
|
||||
protogen:
|
||||
protogen: ensure-gopath
|
||||
export GO111MODULE=off
|
||||
./hack/generate-proto.sh
|
||||
|
||||
.PHONY: openapigen
|
||||
openapigen:
|
||||
openapigen: ensure-gopath
|
||||
export GO111MODULE=off
|
||||
./hack/update-openapi.sh
|
||||
|
||||
.PHONY: clientgen
|
||||
clientgen:
|
||||
clientgen: ensure-gopath
|
||||
export GO111MODULE=off
|
||||
./hack/update-codegen.sh
|
||||
|
||||
.PHONY: clidocsgen
|
||||
clidocsgen: ensure-gopath
|
||||
go run tools/cmd-docs/main.go
|
||||
|
||||
.PHONY: codegen-local
|
||||
codegen-local: mod-vendor-local gogen protogen clientgen openapigen manifests-local
|
||||
codegen-local: ensure-gopath mod-vendor-local gogen protogen clientgen openapigen clidocsgen manifests-local
|
||||
rm -rf vendor/
|
||||
|
||||
.PHONY: codegen
|
||||
@@ -168,10 +203,11 @@ cli: test-tools-image
|
||||
|
||||
.PHONY: cli-local
|
||||
cli-local: clean-debug
|
||||
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${CLI_NAME} ./cmd/argocd
|
||||
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${CLI_NAME} ./cmd
|
||||
|
||||
.PHONY: cli-docker
|
||||
go build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${CLI_NAME} ./cmd/argocd
|
||||
.PHONY: cli-argocd
|
||||
cli-argocd:
|
||||
go build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${CLI_NAME} ./cmd
|
||||
|
||||
.PHONY: release-cli
|
||||
release-cli: clean-debug image
|
||||
@@ -184,7 +220,7 @@ release-cli: clean-debug image
|
||||
.PHONY: argocd-util
|
||||
argocd-util: clean-debug
|
||||
# Build argocd-util as a statically linked binary, so it could run within the alpine-based dex container (argoproj/argo-cd#844)
|
||||
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-util ./cmd/argocd-util
|
||||
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${UTIL_CLI_NAME} ./cmd
|
||||
|
||||
# .PHONY: dev-tools-image
|
||||
# dev-tools-image:
|
||||
@@ -193,7 +229,7 @@ argocd-util: clean-debug
|
||||
|
||||
.PHONY: test-tools-image
|
||||
test-tools-image:
|
||||
docker build -t $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE) -f test/container/Dockerfile .
|
||||
docker build --build-arg UID=$(shell id -u) -t $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE) -f test/container/Dockerfile .
|
||||
docker tag $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE) $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG)
|
||||
|
||||
.PHONY: manifests-local
|
||||
@@ -204,20 +240,24 @@ manifests-local:
|
||||
manifests: test-tools-image
|
||||
$(call run-in-test-client,make manifests-local IMAGE_NAMESPACE='${IMAGE_NAMESPACE}' IMAGE_TAG='${IMAGE_TAG}')
|
||||
|
||||
# consolidated binary for cli, util, server, repo-server, controller
|
||||
.PHONY: argocd-all
|
||||
argocd-all: clean-debug
|
||||
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${BIN_NAME} ./cmd
|
||||
|
||||
# NOTE: we use packr to do the build instead of go, since we embed swagger files and policy.csv
|
||||
# files into the go binary
|
||||
.PHONY: server
|
||||
server: clean-debug
|
||||
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-server ./cmd/argocd-server
|
||||
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-server ./cmd
|
||||
|
||||
.PHONY: repo-server
|
||||
repo-server:
|
||||
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-repo-server ./cmd/argocd-repo-server
|
||||
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-repo-server ./cmd
|
||||
|
||||
.PHONY: controller
|
||||
controller:
|
||||
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-application-controller ./cmd/argocd-application-controller
|
||||
CGO_ENABLED=0 ${PACKR_CMD} build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-application-controller ./cmd
|
||||
|
||||
.PHONY: packr
|
||||
packr:
|
||||
@@ -232,13 +272,16 @@ IMAGE_TAG="dev-$(shell git describe --always --dirty)"
|
||||
image: packr
|
||||
docker build -t argocd-base --target argocd-base .
|
||||
docker build -t argocd-ui --target argocd-ui .
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 dist/packr build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-server ./cmd/argocd-server
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 dist/packr build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-application-controller ./cmd/argocd-application-controller
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 dist/packr build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-repo-server ./cmd/argocd-repo-server
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 dist/packr build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-util ./cmd/argocd-util
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 dist/packr build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd ./cmd/argocd
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 dist/packr build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-darwin-amd64 ./cmd/argocd
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 dist/packr build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-windows-amd64.exe ./cmd/argocd
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 dist/packr build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd ./cmd
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 dist/packr build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-darwin-amd64 ./cmd
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 dist/packr build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-windows-amd64.exe ./cmd
|
||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-server
|
||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-application-controller
|
||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-repo-server
|
||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-dex
|
||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-util
|
||||
ln -sfn ${DIST_DIR}/argocd-darwin-amd64 ${DIST_DIR}/argocd-util-darwin-amd64
|
||||
ln -sfn ${DIST_DIR}/argocd-windows-amd64.exe ${DIST_DIR}/argocd-util-windows-amd64.exe
|
||||
cp Dockerfile.dev dist
|
||||
docker build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) -f dist/Dockerfile.dev dist
|
||||
else
|
||||
@@ -308,7 +351,7 @@ build: test-tools-image
|
||||
|
||||
# Build all Go code (local version)
|
||||
.PHONY: build-local
|
||||
build-local:
|
||||
build-local:
|
||||
go build -v `go list ./... | grep -v 'resource_customizations\|test/e2e'`
|
||||
|
||||
# Run all unit tests
|
||||
@@ -329,10 +372,24 @@ test-local:
|
||||
./hack/test.sh -coverprofile=coverage.out "$(TEST_MODULE)"; \
|
||||
fi
|
||||
|
||||
.PHONY: test-race
|
||||
test-race: test-tools-image
|
||||
mkdir -p $(GOCACHE)
|
||||
$(call run-in-test-client,make TEST_MODULE=$(TEST_MODULE) test-race-local)
|
||||
|
||||
# Run all unit tests, with data race detection, skipping known failures (local version)
|
||||
.PHONY: test-race-local
|
||||
test-race-local:
|
||||
if test "$(TEST_MODULE)" = ""; then \
|
||||
./hack/test.sh -race -coverprofile=coverage.out `go list ./... | grep -v 'test/e2e'`; \
|
||||
else \
|
||||
./hack/test.sh -race -coverprofile=coverage.out "$(TEST_MODULE)"; \
|
||||
fi
|
||||
|
||||
# Run the E2E test suite. E2E test servers (see start-e2e target) must be
|
||||
# started before.
|
||||
.PHONY: test-e2e
|
||||
test-e2e:
|
||||
test-e2e:
|
||||
$(call exec-in-test-server,make test-e2e-local)
|
||||
|
||||
# Run the E2E test suite (local version)
|
||||
@@ -359,7 +416,7 @@ start-e2e: test-tools-image
|
||||
|
||||
# Starts e2e server locally (or within a container)
|
||||
.PHONY: start-e2e-local
|
||||
start-e2e-local:
|
||||
start-e2e-local:
|
||||
kubectl create ns argocd-e2e || true
|
||||
kubectl config set-context --current --namespace=argocd-e2e
|
||||
kustomize build test/manifests/base | kubectl apply -f -
|
||||
@@ -367,7 +424,6 @@ start-e2e-local:
|
||||
if test -d /tmp/argo-e2e/app/config/gpg; then rm -rf /tmp/argo-e2e/app/config/gpg/*; fi
|
||||
mkdir -p /tmp/argo-e2e/app/config/gpg/keys && chmod 0700 /tmp/argo-e2e/app/config/gpg/keys
|
||||
mkdir -p /tmp/argo-e2e/app/config/gpg/source && chmod 0700 /tmp/argo-e2e/app/config/gpg/source
|
||||
if test "$(USER_ID)" != ""; then chown -R "$(USER_ID)" /tmp/argo-e2e; fi
|
||||
# set paths for locally managed ssh known hosts and tls certs data
|
||||
ARGOCD_SSH_DATA_PATH=/tmp/argo-e2e/app/config/ssh \
|
||||
ARGOCD_TLS_DATA_PATH=/tmp/argo-e2e/app/config/tls \
|
||||
@@ -378,7 +434,7 @@ start-e2e-local:
|
||||
ARGOCD_ZJWT_FEATURE_FLAG=always \
|
||||
ARGOCD_IN_CI=$(ARGOCD_IN_CI) \
|
||||
ARGOCD_E2E_TEST=true \
|
||||
goreman -f $(ARGOCD_PROCFILE) start
|
||||
goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START}
|
||||
|
||||
# Cleans VSCode debug.test files from sub-dirs to prevent them from being included in packr boxes
|
||||
.PHONY: clean-debug
|
||||
@@ -396,7 +452,7 @@ start: test-tools-image
|
||||
|
||||
# Starts a local instance of ArgoCD
|
||||
.PHONY: start-local
|
||||
start-local: mod-vendor-local
|
||||
start-local: mod-vendor-local dep-ui-local
|
||||
# check we can connect to Docker to start Redis
|
||||
killall goreman || true
|
||||
kubectl create ns argocd || true
|
||||
@@ -427,23 +483,27 @@ release-precheck: manifests
|
||||
.PHONY: release
|
||||
release: pre-commit release-precheck image release-cli
|
||||
|
||||
.PHONY: build-docs-local
|
||||
build-docs-local:
|
||||
mkdocs build
|
||||
|
||||
.PHONY: build-docs
|
||||
build-docs:
|
||||
mkdocs build
|
||||
docker run ${MKDOCS_RUN_ARGS} --rm -it -p 8000:8000 -v ${CURRENT_DIR}:/docs ${MKDOCS_DOCKER_IMAGE} build
|
||||
|
||||
.PHONY: serve-docs-local
|
||||
serve-docs-local:
|
||||
mkdocs serve
|
||||
|
||||
.PHONY: serve-docs
|
||||
serve-docs:
|
||||
mkdocs serve
|
||||
docker run ${MKDOCS_RUN_ARGS} --rm -it -p 8000:8000 -v ${CURRENT_DIR}:/docs ${MKDOCS_DOCKER_IMAGE} serve -a 0.0.0.0:8000
|
||||
|
||||
.PHONY: lint-docs
|
||||
lint-docs:
|
||||
# https://github.com/dkhamsing/awesome_bot
|
||||
find docs -name '*.md' -exec grep -l http {} + | xargs docker run --rm -v $(PWD):/mnt:ro dkhamsing/awesome_bot -t 3 --allow-dupe --allow-redirect --white-list `cat white-list | grep -v "#" | tr "\n" ','` --skip-save-results --
|
||||
|
||||
.PHONY: publish-docs
|
||||
publish-docs: lint-docs
|
||||
mkdocs gh-deploy
|
||||
|
||||
# Verify that kubectl can connect to your K8s cluster from Docker
|
||||
.PHONY: verify-kube-connect
|
||||
verify-kube-connect: test-tools-image
|
||||
@@ -465,7 +525,6 @@ install-tools-local: install-test-tools-local install-codegen-tools-local instal
|
||||
.PHONY: install-test-tools-local
|
||||
install-test-tools-local:
|
||||
sudo ./hack/install.sh packr-linux
|
||||
sudo ./hack/install.sh kubectl-linux
|
||||
sudo ./hack/install.sh kustomize-linux
|
||||
sudo ./hack/install.sh ksonnet-linux
|
||||
sudo ./hack/install.sh helm2-linux
|
||||
|
||||
5
OWNERS
5
OWNERS
@@ -10,3 +10,8 @@ approvers:
|
||||
- jessesuen
|
||||
- mayzhang2000
|
||||
- rachelwang20
|
||||
|
||||
reviewers:
|
||||
- jgwest
|
||||
- wtam2018
|
||||
- tetchel
|
||||
|
||||
10
Procfile
10
Procfile
@@ -1,8 +1,8 @@
|
||||
controller: 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} go run ./cmd/argocd-application-controller/main.go --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
|
||||
api-server: 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} go run ./cmd/argocd-server/main.go --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} --staticassets ui/dist/app"
|
||||
dex: sh -c "go run github.com/argoproj/argo-cd/cmd/argocd-util 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 quay.io/dexidp/dex:v2.22.0 serve /dex.yaml"
|
||||
redis: docker run --rm --name argocd-redis -i -p ${ARGOCD_E2E_REDIS_PORT:-6379}:${ARGOCD_E2E_REDIS_PORT:-6379} redis:5.0.8-alpine --save "" --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}
|
||||
repo-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} 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} go run ./cmd/argocd-repo-server/main.go --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}"
|
||||
controller: 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 go run ./cmd/main.go --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
|
||||
api-server: 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 go run ./cmd/main.go --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} --staticassets ui/dist/app"
|
||||
dex: sh -c "ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/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:v2.27.0 serve /dex.yaml"
|
||||
redis: docker run --rm --name argocd-redis -i -p ${ARGOCD_E2E_REDIS_PORT:-6379}:${ARGOCD_E2E_REDIS_PORT:-6379} redis:5.0.10-alpine --save "" --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}
|
||||
repo-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} 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 go run ./cmd/main.go --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}"
|
||||
ui: sh -c 'cd ui && ${ARGOCD_E2E_YARN_CMD:-yarn} start'
|
||||
git-server: test/fixture/testrepos/start-git.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}
|
||||
|
||||
15
README.md
15
README.md
@@ -2,6 +2,7 @@
|
||||
[](https://argoproj.github.io/community/join-slack)
|
||||
[](https://codecov.io/gh/argoproj/argo-cd)
|
||||
[](https://github.com/argoproj/argo-cd/releases/latest)
|
||||
[](https://bestpractices.coreinfrastructure.org/projects/4486)
|
||||
|
||||
# Argo CD - Declarative Continuous Delivery for Kubernetes
|
||||
|
||||
@@ -11,6 +12,8 @@ Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes.
|
||||
|
||||

|
||||
|
||||
[](https://youtu.be/0WAm0y2vLIo)
|
||||
|
||||
## Why Argo CD?
|
||||
|
||||
1. Application definitions, configurations, and environments should be declarative and version controlled.
|
||||
@@ -22,11 +25,15 @@ Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes.
|
||||
|
||||
## Documentation
|
||||
|
||||
To learn more about Argo CD [go to the complete documentation](https://argoproj.github.io/argo-cd/).
|
||||
To learn more about Argo CD [go to the complete documentation](https://argo-cd.readthedocs.io/).
|
||||
Check live demo at https://cd.apps.argoproj.io/.
|
||||
|
||||
## Community Blogs and Presentations
|
||||
|
||||
1. [Automation of Everything - How To Combine Argo Events, Workflows & Pipelines, CD, and Rollouts](https://youtu.be/XNXJtxkUKeY)
|
||||
1. [Environments Based On Pull Requests (PRs): Using Argo CD To Apply GitOps Principles On Previews](https://youtu.be/cpAaI8p4R60)
|
||||
1. [Argo CD: Applying GitOps Principles To Manage Production Environment In Kubernetes](https://youtu.be/vpWQeoaiRM4)
|
||||
1. [Creating Temporary Preview Environments Based On Pull Requests With Argo CD And Codefresh](https://codefresh.io/continuous-deployment/creating-temporary-preview-environments-based-pull-requests-argo-cd-codefresh/)
|
||||
1. [Tutorial: Everything You Need To Become A GitOps Ninja](https://www.youtube.com/watch?v=r50tRQjisxw) 90m tutorial on GitOps and Argo CD.
|
||||
1. [Comparison of Argo CD, Spinnaker, Jenkins X, and Tekton](https://www.inovex.de/blog/spinnaker-vs-argo-cd-vs-tekton-vs-jenkins-x/)
|
||||
1. [Simplify and Automate Deployments Using GitOps with IBM Multicloud Manager 3.1.2](https://medium.com/ibm-cloud/simplify-and-automate-deployments-using-gitops-with-ibm-multicloud-manager-3-1-2-4395af317359)
|
||||
@@ -38,3 +45,9 @@ Check live demo at https://cd.apps.argoproj.io/.
|
||||
1. [Argo CD - GitOps Continuous Delivery for Kubernetes](https://www.youtube.com/watch?v=aWDIQMbp1cc&feature=youtu.be&t=1m4s)
|
||||
1. [Introduction to Argo CD : Kubernetes DevOps CI/CD](https://www.youtube.com/watch?v=2WSJF7d8dUg&feature=youtu.be)
|
||||
1. [GitOps Deployment and Kubernetes - using ArgoCD](https://medium.com/riskified-technology/gitops-deployment-and-kubernetes-f1ab289efa4b)
|
||||
1. [Deploy Argo CD with Ingress and TLS in Three Steps: No YAML Yak Shaving Required](https://itnext.io/deploy-argo-cd-with-ingress-and-tls-in-three-steps-no-yaml-yak-shaving-required-bc536d401491)
|
||||
1. [GitOps Continuous Delivery with Argo and Codefresh](https://codefresh.io/events/cncf-member-webinar-gitops-continuous-delivery-argo-codefresh/)
|
||||
1. [Stay up to date with ArgoCD and Renovate](https://mjpitz.com/blog/2020/12/03/renovate-your-gitops/)
|
||||
1. [Setting up Argo CD with Helm](https://www.arthurkoziel.com/setting-up-argocd-with-helm/)
|
||||
1. [Applied GitOps with ArgoCD](https://thenewstack.io/applied-gitops-with-argocd/)
|
||||
1. [Solving configuration drift using GitOps with Argo CD](https://www.cncf.io/blog/2020/12/17/solving-configuration-drift-using-gitops-with-argo-cd/)
|
||||
|
||||
47
SECURITY.md
Normal file
47
SECURITY.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Security Policy for Argo CD
|
||||
|
||||
Version: **v1.0 (2020-02-26)**
|
||||
|
||||
## Preface
|
||||
|
||||
As a deployment tool, Argo CD needs to have production access which makes
|
||||
security a very important topic. The Argoproj team takes security very
|
||||
seriously and is continuously working on improving it.
|
||||
|
||||
## 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 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
|
||||
important bugs. Prior releases might receive critical security fixes on a best
|
||||
effort basis, however, it cannot be guaranteed that security fixes get
|
||||
back-ported to these unsupported versions.
|
||||
|
||||
In rare cases, where a security fix needs complex re-design of a feature or is
|
||||
otherwise very intrusive, and there's a workaround available, we may decide to
|
||||
provide a forward-fix only, e.g. to be released the next minor release, instead
|
||||
of releasing it within a patch branch for the currently supported releases.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you find a security related bug in ArgoCD, we kindly ask you for responsible
|
||||
disclosure and for giving us appropriate time to react, analyze and develop a
|
||||
fix to mitigate the found security vulnerability.
|
||||
|
||||
We will do our best to react quickly on your inquiry, and to coordinate a fix
|
||||
and disclosure with you. Sometimes, it might take a little longer for us to
|
||||
react (e.g. out of office conditions), so please bear with us in these cases.
|
||||
|
||||
We will publish security advisiories using the Git Hub SA 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 all of the following people:
|
||||
|
||||
* jfischer@redhat.com
|
||||
* Jesse_Suen@intuit.com
|
||||
* Alexander_Matyushentsev@intuit.com
|
||||
* Edward_Lee@intuit.com
|
||||
@@ -1,7 +1,7 @@
|
||||
# Defined below are the security contacts for this repo.
|
||||
#
|
||||
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
|
||||
# INSTRUCTIONS AT https://argoproj.github.io/argo-cd/security_considerations/#reporting-vulnerabilities
|
||||
# INSTRUCTIONS AT https://argo-cd.readthedocs.io/en/latest/security_considerations/#reporting-vulnerabilities
|
||||
|
||||
alexmt
|
||||
edlee2121
|
||||
|
||||
55
USERS.md
55
USERS.md
@@ -5,23 +5,35 @@ As the Argo Community grows, we'd like to keep track of our users. Please send a
|
||||
Currently, the following organizations are **officially** using Argo CD:
|
||||
|
||||
1. [127Labs](https://127labs.com/)
|
||||
1. [3Rein](https://www.3rein.com/)
|
||||
1. [7shifts](https://www.7shifts.com/)
|
||||
1. [Adevinta](https://www.adevinta.com/)
|
||||
1. [AppDirect](https://www.appdirect.com)
|
||||
1. [ANSTO - Australian Synchrotron](https://www.synchrotron.org.au/)
|
||||
1. [AppDirect](https://www.appdirect.com)
|
||||
1. [Arctiq Inc.](https://www.arctiq.ca)
|
||||
1. [ARZ Allgemeines Rechenzentrum GmbH ](https://www.arz.at/)
|
||||
1. [Baloise](https://www.baloise.com)
|
||||
1. [BCDevExchange DevOps Platform](https://bcdevexchange.org/DevOpsPlatform)
|
||||
1. [Beat](https://thebeat.co/en/)
|
||||
1. [Beez Innovation Labs](https://www.beezlabs.com/)
|
||||
1. [BioBox Analytics](https://biobox.io)
|
||||
1. [Camptocamp](https://camptocamp.com)
|
||||
1. [CARFAX](https://www.carfax.com)
|
||||
1. [Celonis](https://www.celonis.com/)
|
||||
1. [Codefresh](https://www.codefresh.io/)
|
||||
1. [Codility](https://www.codility.com/)
|
||||
1. [Commonbond](https://commonbond.co/)
|
||||
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. [Devtron Labs](https://github.com/devtron-labs/devtron)
|
||||
1. [EDF Renewables](https://www.edf-re.com/)
|
||||
1. [edX](https://edx.org)
|
||||
1. [Electronic Arts Inc. ](https://www.ea.com)
|
||||
1. [Elium](https://www.elium.com)
|
||||
1. [END.](https://www.endclothing.com/)
|
||||
1. [Energisme](https://energisme.com/)
|
||||
1. [Fave](https://myfave.com)
|
||||
1. [Future PLC](https://www.futureplc.com/)
|
||||
1. [Garner](https://www.garnercorp.com)
|
||||
@@ -30,48 +42,79 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Healy](https://www.healyworld.net)
|
||||
1. [hipages](https://hipages.com.au/)
|
||||
1. [Honestbank](https://honestbank.com)
|
||||
1. [IBM](https://www.ibm.com/)
|
||||
1. [InsideBoard](https://www.insideboard.com)
|
||||
1. [Intuit](https://www.intuit.com/)
|
||||
1. [JovianX](https://www.jovianx.com/)
|
||||
1. [Kasa](https://kasa.co.kr/)
|
||||
1. [Keptn](https://keptn.sh)
|
||||
1. [Kinguin](https://www.kinguin.net/)
|
||||
1. [KintoHub](https://www.kintohub.com/)
|
||||
1. [KompiTech GmbH](https://www.kompitech.com/)
|
||||
1. [LexisNexis](https://www.lexisnexis.com/)
|
||||
1. [LINE](https://linecorp.com/en/)
|
||||
1. [Lytt](https://www.lytt.co/)
|
||||
1. [Major League Baseball](https://mlb.com)
|
||||
1. [Mambu](https://www.mambu.com/)
|
||||
1. [Max Kelsen](https://www.maxkelsen.com/)
|
||||
1. [MindSpore](https://mindspore.cn)
|
||||
1. [Mirantis](https://mirantis.com/)
|
||||
1. [Moengage](https://www.moengage.com/)
|
||||
1. [Money Forward](https://corp.moneyforward.com/en/)
|
||||
1. [MOO Print](https://www.moo.com/)
|
||||
1. [MTN Group](https://www.mtn.com/)
|
||||
1. [New Relic](https://newrelic.com/)
|
||||
1. [Nextdoor](https://nextdoor.com/)
|
||||
1. [Nikkei](https://www.nikkei.co.jp/nikkeiinfo/en/)
|
||||
1. [Octadesk](https://octadesk.com)
|
||||
1. [openEuler](https://openeuler.org)
|
||||
1. [openGauss](https://opengauss.org/)
|
||||
1. [openLooKeng](https://openlookeng.io)
|
||||
1. [OpenSaaS Studio](https://opensaas.studio)
|
||||
1. [Opensurvey](https://www.opensurvey.co.kr/)
|
||||
1. [Optoro](https://www.optoro.com/)
|
||||
1. [Orbital Insight](https://orbitalinsight.com/)
|
||||
1. [PayPay](https://paypay.ne.jp/)
|
||||
1. [Peloton Interactive](https://www.onepeloton.com/)
|
||||
1. [Pipefy](https://www.pipefy.com/)
|
||||
1. [Preferred Networks](https://preferred.jp/en/)
|
||||
1. [Prudential](https://prudential.com.sg)
|
||||
1. [PUBG](https://www.pubg.com)
|
||||
1. [Qonto](https://qonto.com)
|
||||
1. [QuintoAndar](https://quintoandar.com.br)
|
||||
1. [Quipper](https://www.quipper.com/)
|
||||
1. [Recreation.gov](https://www.recreation.gov/)
|
||||
1. [Red Hat](https://www.redhat.com/)
|
||||
1. [Robotinfra](https://www.robotinfra.com)
|
||||
1. [Riskified](https://www.riskified.com/)
|
||||
1. [Robotinfra](https://www.robotinfra.com)
|
||||
1. [Saildrone](https://www.saildrone.com/)
|
||||
1. [Saloodo! GmbH](https://www.saloodo.com)
|
||||
1. [Speee](https://speee.jp/)
|
||||
1. [Spendesk](https://spendesk.com/)
|
||||
1. [Sumo Logic](https://sumologic.com/)
|
||||
1. [Swisscom](https://www.swisscom.ch)
|
||||
1. [Swissquote](https://github.com/swissquote)
|
||||
1. [Syncier](https://syncier.com/)
|
||||
1. [TableCheck](https://tablecheck.com/)
|
||||
1. [Tailor Brands](https://www.tailorbrands.com)
|
||||
1. [Tesla](https://tesla.com/)
|
||||
1. [ThousandEyes](https://www.thousandeyes.com/)
|
||||
1. [Ticketmaster](https://ticketmaster.com)
|
||||
1. [Tiger Analytics](https://www.tigeranalytics.com/)
|
||||
1. [Toss](https://toss.im/en)
|
||||
1. [tru.ID](https://tru.id)
|
||||
1. [Twilio SendGrid](https://sendgrid.com)
|
||||
1. [tZERO](https://www.tzero.com/)
|
||||
1. [UBIO](https://ub.io/)
|
||||
1. [UFirstGroup](https://www.ufirstgroup.com/en/)
|
||||
1. [Universidad Mesoamericana](https://www.umes.edu.gt/)
|
||||
1. [Viaduct](https://www.viaduct.ai/)
|
||||
1. [Virtuo](https://www.govirtuo.com/)
|
||||
1. [VISITS Technologies](https://visits.world/en)
|
||||
1. [Volvo Cars](https://www.volvocars.com/)
|
||||
1. [VSHN - The DevOps Company](https://vshn.ch/)
|
||||
1. [Walkbase](https://www.walkbase.com/)
|
||||
1. [WeMo Scooter](https://www.wemoscooter.com/)
|
||||
1. [Whitehat Berlin](https://whitehat.berlin) by Guido Maria Serra +Fenaroli
|
||||
1. [Yieldlab](https://www.yieldlab.de/)
|
||||
1. [MTN Group](https://www.mtn.com/)
|
||||
1. [Moengage](https://www.moengage.com/)
|
||||
1. [LexisNexis](https://www.lexisnexis.com/)
|
||||
1. [PayPay](https://paypay.ne.jp/)
|
||||
1. [Sap Labs] (http://sap.com)
|
||||
|
||||
1495
assets/swagger.json
1495
assets/swagger.json
File diff suppressed because it is too large
Load Diff
@@ -1,33 +1,30 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
"github.com/argoproj/pkg/stats"
|
||||
"github.com/go-redis/redis"
|
||||
"github.com/go-redis/redis/v8"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
// load the gcp plugin (required to authenticate against GKE clusters).
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
// load the oidc plugin (required to authenticate with OpenID Connect).
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||
|
||||
cmdutil "github.com/argoproj/argo-cd/cmd/util"
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
"github.com/argoproj/argo-cd/controller"
|
||||
"github.com/argoproj/argo-cd/controller/sharding"
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
|
||||
"github.com/argoproj/argo-cd/reposerver/apiclient"
|
||||
cacheutil "github.com/argoproj/argo-cd/util/cache"
|
||||
appstatecache "github.com/argoproj/argo-cd/util/cache/appstate"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/env"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
kubeutil "github.com/argoproj/argo-cd/util/kube"
|
||||
"github.com/argoproj/argo-cd/util/settings"
|
||||
)
|
||||
|
||||
@@ -38,7 +35,7 @@ const (
|
||||
defaultAppResyncPeriod = 180
|
||||
)
|
||||
|
||||
func newCommand() *cobra.Command {
|
||||
func NewCommand() *cobra.Command {
|
||||
var (
|
||||
clientConfig clientcmd.ClientConfig
|
||||
appResyncPeriod int64
|
||||
@@ -47,20 +44,21 @@ func newCommand() *cobra.Command {
|
||||
selfHealTimeoutSeconds int
|
||||
statusProcessors int
|
||||
operationProcessors int
|
||||
logFormat string
|
||||
logLevel string
|
||||
glogLevel int
|
||||
metricsPort int
|
||||
metricsCacheExpiration time.Duration
|
||||
kubectlParallelismLimit int64
|
||||
cacheSrc func() (*appstatecache.Cache, error)
|
||||
redisClient *redis.Client
|
||||
)
|
||||
var command = cobra.Command{
|
||||
Use: cliName,
|
||||
Short: "application-controller is a controller to operate on applications CRD",
|
||||
Use: cliName,
|
||||
Short: "Run ArgoCD Application Controller",
|
||||
Long: "ArgoCD application controller is a Kubernetes controller that continuously monitors running applications and compares the current, live state against the desired target state (as specified in the repo). This command runs Application Controller in the foreground. It can be configured by following options.",
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(c *cobra.Command, args []string) error {
|
||||
cli.SetLogFormat(logFormat)
|
||||
cli.SetLogLevel(logLevel)
|
||||
cli.SetLogFormat(cmdutil.LogFormat)
|
||||
cli.SetLogLevel(cmdutil.LogLevel)
|
||||
cli.SetGLogLevel(glogLevel)
|
||||
|
||||
config, err := clientConfig.ClientConfig()
|
||||
@@ -80,9 +78,11 @@ func newCommand() *cobra.Command {
|
||||
|
||||
cache, err := cacheSrc()
|
||||
errors.CheckError(err)
|
||||
cache.Cache.SetClient(cacheutil.NewTwoLevelClient(cache.Cache.GetClient(), 10*time.Minute))
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(ctx, kubeClient, namespace)
|
||||
kubectl := &kube.KubectlCmd{}
|
||||
kubectl := kubeutil.NewKubectl()
|
||||
clusterFilter := getClusterFilter()
|
||||
appController, err := controller.NewApplicationController(
|
||||
namespace,
|
||||
settingsMgr,
|
||||
@@ -94,7 +94,9 @@ func newCommand() *cobra.Command {
|
||||
resyncDuration,
|
||||
time.Duration(selfHealTimeoutSeconds)*time.Second,
|
||||
metricsPort,
|
||||
kubectlParallelismLimit)
|
||||
metricsCacheExpiration,
|
||||
kubectlParallelismLimit,
|
||||
clusterFilter)
|
||||
errors.CheckError(err)
|
||||
cacheutil.CollectMetrics(redisClient, appController.GetMetricsServer())
|
||||
|
||||
@@ -117,10 +119,11 @@ func newCommand() *cobra.Command {
|
||||
command.Flags().IntVar(&repoServerTimeoutSeconds, "repo-server-timeout-seconds", 60, "Repo server RPC call timeout seconds.")
|
||||
command.Flags().IntVar(&statusProcessors, "status-processors", 1, "Number of application status processors")
|
||||
command.Flags().IntVar(&operationProcessors, "operation-processors", 1, "Number of application operation processors")
|
||||
command.Flags().StringVar(&logFormat, "logformat", "text", "Set the logging format. One of: text|json")
|
||||
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
|
||||
command.Flags().StringVar(&cmdutil.LogFormat, "logformat", "text", "Set the logging format. One of: text|json")
|
||||
command.Flags().StringVar(&cmdutil.LogLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
|
||||
command.Flags().IntVar(&glogLevel, "gloglevel", 0, "Set the glog logging level")
|
||||
command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortArgoCDMetrics, "Start metrics server on given port")
|
||||
command.Flags().DurationVar(&metricsCacheExpiration, "metrics-cache-expiration", 0*time.Second, "Prometheus metrics cache expiration (disabled by default. e.g. 24h0m0s)")
|
||||
command.Flags().IntVar(&selfHealTimeoutSeconds, "self-heal-timeout-seconds", 5, "Specifies timeout between application self heal attempts")
|
||||
command.Flags().Int64Var(&kubectlParallelismLimit, "kubectl-parallelism-limit", 20, "Number of allowed concurrent kubectl fork/execs. Any value less the 1 means no limit.")
|
||||
cacheSrc = appstatecache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {
|
||||
@@ -129,9 +132,20 @@ func newCommand() *cobra.Command {
|
||||
return &command
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := newCommand().Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
func getClusterFilter() func(cluster *v1alpha1.Cluster) bool {
|
||||
replicas := env.ParseNumFromEnv(common.EnvControllerReplicas, 0, 0, math.MaxInt32)
|
||||
shard := env.ParseNumFromEnv(common.EnvControllerShard, -1, -math.MaxInt32, math.MaxInt32)
|
||||
var clusterFilter func(cluster *v1alpha1.Cluster) bool
|
||||
if replicas > 1 {
|
||||
if shard < 0 {
|
||||
var err error
|
||||
shard, err = sharding.InferShard()
|
||||
errors.CheckError(err)
|
||||
}
|
||||
log.Infof("Processing clusters from shard %d", shard)
|
||||
clusterFilter = sharding.GetClusterFilter(replicas, shard)
|
||||
} else {
|
||||
log.Info("Processing all cluster shards")
|
||||
}
|
||||
return clusterFilter
|
||||
}
|
||||
202
cmd/argocd-dex/commands/argocd_dex.go
Normal file
202
cmd/argocd-dex/commands/argocd_dex.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
cmdutil "github.com/argoproj/argo-cd/cmd/util"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/dex"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/settings"
|
||||
)
|
||||
|
||||
const (
|
||||
cliName = "argocd-dex"
|
||||
)
|
||||
|
||||
func NewCommand() *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: cliName,
|
||||
Short: "argocd-dex tools used by Argo CD",
|
||||
Long: "argocd-dex has internal utility tools used by Argo CD",
|
||||
DisableAutoGenTag: true,
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
c.HelpFunc()(c, args)
|
||||
},
|
||||
}
|
||||
|
||||
command.AddCommand(NewRunDexCommand())
|
||||
command.AddCommand(NewGenDexConfigCommand())
|
||||
|
||||
command.Flags().StringVar(&cmdutil.LogFormat, "logformat", "text", "Set the logging format. One of: text|json")
|
||||
command.Flags().StringVar(&cmdutil.LogLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
|
||||
return command
|
||||
}
|
||||
|
||||
func NewRunDexCommand() *cobra.Command {
|
||||
var (
|
||||
clientConfig clientcmd.ClientConfig
|
||||
)
|
||||
var command = cobra.Command{
|
||||
Use: "rundex",
|
||||
Short: "Runs dex generating a config using settings from the Argo CD configmap and secret",
|
||||
RunE: func(c *cobra.Command, args []string) error {
|
||||
_, err := exec.LookPath("dex")
|
||||
errors.CheckError(err)
|
||||
config, err := clientConfig.ClientConfig()
|
||||
errors.CheckError(err)
|
||||
namespace, _, err := clientConfig.Namespace()
|
||||
errors.CheckError(err)
|
||||
kubeClientset := kubernetes.NewForConfigOrDie(config)
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), kubeClientset, namespace)
|
||||
prevSettings, err := settingsMgr.GetSettings()
|
||||
errors.CheckError(err)
|
||||
updateCh := make(chan *settings.ArgoCDSettings, 1)
|
||||
settingsMgr.Subscribe(updateCh)
|
||||
|
||||
for {
|
||||
var cmd *exec.Cmd
|
||||
dexCfgBytes, err := dex.GenerateDexConfigYAML(prevSettings)
|
||||
errors.CheckError(err)
|
||||
if len(dexCfgBytes) == 0 {
|
||||
log.Infof("dex is not configured")
|
||||
} else {
|
||||
err = ioutil.WriteFile("/tmp/dex.yaml", dexCfgBytes, 0644)
|
||||
errors.CheckError(err)
|
||||
log.Debug(redactor(string(dexCfgBytes)))
|
||||
cmd = exec.Command("dex", "serve", "/tmp/dex.yaml")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Start()
|
||||
errors.CheckError(err)
|
||||
}
|
||||
|
||||
// loop until the dex config changes
|
||||
for {
|
||||
newSettings := <-updateCh
|
||||
newDexCfgBytes, err := dex.GenerateDexConfigYAML(newSettings)
|
||||
errors.CheckError(err)
|
||||
if string(newDexCfgBytes) != string(dexCfgBytes) {
|
||||
prevSettings = newSettings
|
||||
log.Infof("dex config modified. restarting dex")
|
||||
if cmd != nil && cmd.Process != nil {
|
||||
err = cmd.Process.Signal(syscall.SIGTERM)
|
||||
errors.CheckError(err)
|
||||
_, err = cmd.Process.Wait()
|
||||
errors.CheckError(err)
|
||||
}
|
||||
break
|
||||
} else {
|
||||
log.Infof("dex config unmodified")
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
clientConfig = cli.AddKubectlFlagsToCmd(&command)
|
||||
return &command
|
||||
}
|
||||
|
||||
func NewGenDexConfigCommand() *cobra.Command {
|
||||
var (
|
||||
clientConfig clientcmd.ClientConfig
|
||||
out string
|
||||
)
|
||||
var command = cobra.Command{
|
||||
Use: "gendexcfg",
|
||||
Short: "Generates a dex config from Argo CD settings",
|
||||
RunE: func(c *cobra.Command, args []string) error {
|
||||
config, err := clientConfig.ClientConfig()
|
||||
errors.CheckError(err)
|
||||
namespace, _, err := clientConfig.Namespace()
|
||||
errors.CheckError(err)
|
||||
kubeClientset := kubernetes.NewForConfigOrDie(config)
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), kubeClientset, namespace)
|
||||
settings, err := settingsMgr.GetSettings()
|
||||
errors.CheckError(err)
|
||||
dexCfgBytes, err := dex.GenerateDexConfigYAML(settings)
|
||||
errors.CheckError(err)
|
||||
if len(dexCfgBytes) == 0 {
|
||||
log.Infof("dex is not configured")
|
||||
return nil
|
||||
}
|
||||
if out == "" {
|
||||
dexCfg := make(map[string]interface{})
|
||||
err := yaml.Unmarshal(dexCfgBytes, &dexCfg)
|
||||
errors.CheckError(err)
|
||||
if staticClientsInterface, ok := dexCfg["staticClients"]; ok {
|
||||
if staticClients, ok := staticClientsInterface.([]interface{}); ok {
|
||||
for i := range staticClients {
|
||||
staticClient := staticClients[i]
|
||||
if mappings, ok := staticClient.(map[string]interface{}); ok {
|
||||
for key := range mappings {
|
||||
if key == "secret" {
|
||||
mappings[key] = "******"
|
||||
}
|
||||
}
|
||||
staticClients[i] = mappings
|
||||
}
|
||||
}
|
||||
dexCfg["staticClients"] = staticClients
|
||||
}
|
||||
}
|
||||
errors.CheckError(err)
|
||||
maskedDexCfgBytes, err := yaml.Marshal(dexCfg)
|
||||
errors.CheckError(err)
|
||||
fmt.Print(string(maskedDexCfgBytes))
|
||||
} else {
|
||||
err = ioutil.WriteFile(out, dexCfgBytes, 0644)
|
||||
errors.CheckError(err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
clientConfig = cli.AddKubectlFlagsToCmd(&command)
|
||||
command.Flags().StringVarP(&out, "out", "o", "", "Output to the specified file instead of stdout")
|
||||
return &command
|
||||
}
|
||||
|
||||
func iterateStringFields(obj interface{}, callback func(name string, val string) string) {
|
||||
if mapField, ok := obj.(map[string]interface{}); ok {
|
||||
for field, val := range mapField {
|
||||
if strVal, ok := val.(string); ok {
|
||||
mapField[field] = callback(field, strVal)
|
||||
} else {
|
||||
iterateStringFields(val, callback)
|
||||
}
|
||||
}
|
||||
} else if arrayField, ok := obj.([]interface{}); ok {
|
||||
for i := range arrayField {
|
||||
iterateStringFields(arrayField[i], callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func redactor(dirtyString string) string {
|
||||
config := make(map[string]interface{})
|
||||
err := yaml.Unmarshal([]byte(dirtyString), &config)
|
||||
errors.CheckError(err)
|
||||
iterateStringFields(config, func(name string, val string) string {
|
||||
if name == "clientSecret" || name == "secret" || name == "bindPW" {
|
||||
return "********"
|
||||
} else {
|
||||
return val
|
||||
}
|
||||
})
|
||||
data, err := yaml.Marshal(config)
|
||||
errors.CheckError(err)
|
||||
return string(data)
|
||||
}
|
||||
161
cmd/argocd-repo-server/commands/argocd_repo_server.go
Normal file
161
cmd/argocd-repo-server/commands/argocd_repo_server.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/pkg/stats"
|
||||
"github.com/go-redis/redis/v8"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc/health/grpc_health_v1"
|
||||
|
||||
cmdutil "github.com/argoproj/argo-cd/cmd/util"
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
"github.com/argoproj/argo-cd/reposerver"
|
||||
"github.com/argoproj/argo-cd/reposerver/apiclient"
|
||||
reposervercache "github.com/argoproj/argo-cd/reposerver/cache"
|
||||
"github.com/argoproj/argo-cd/reposerver/metrics"
|
||||
"github.com/argoproj/argo-cd/reposerver/repository"
|
||||
cacheutil "github.com/argoproj/argo-cd/util/cache"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/env"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/gpg"
|
||||
"github.com/argoproj/argo-cd/util/healthz"
|
||||
ioutil "github.com/argoproj/argo-cd/util/io"
|
||||
"github.com/argoproj/argo-cd/util/tls"
|
||||
)
|
||||
|
||||
const (
|
||||
// CLIName is the name of the CLI
|
||||
cliName = "argocd-repo-server"
|
||||
gnuPGSourcePath = "/app/config/gpg/source"
|
||||
|
||||
defaultPauseGenerationAfterFailedGenerationAttempts = 3
|
||||
defaultPauseGenerationOnFailureForMinutes = 60
|
||||
defaultPauseGenerationOnFailureForRequests = 0
|
||||
)
|
||||
|
||||
func getGnuPGSourcePath() string {
|
||||
if path := os.Getenv("ARGOCD_GPG_DATA_PATH"); path != "" {
|
||||
return path
|
||||
} else {
|
||||
return gnuPGSourcePath
|
||||
}
|
||||
}
|
||||
|
||||
func getPauseGenerationAfterFailedGenerationAttempts() int {
|
||||
return env.ParseNumFromEnv(common.EnvPauseGenerationAfterFailedAttempts, defaultPauseGenerationAfterFailedGenerationAttempts, 0, math.MaxInt32)
|
||||
}
|
||||
|
||||
func getPauseGenerationOnFailureForMinutes() int {
|
||||
return env.ParseNumFromEnv(common.EnvPauseGenerationMinutes, defaultPauseGenerationOnFailureForMinutes, 0, math.MaxInt32)
|
||||
}
|
||||
|
||||
func getPauseGenerationOnFailureForRequests() int {
|
||||
return env.ParseNumFromEnv(common.EnvPauseGenerationRequests, defaultPauseGenerationOnFailureForRequests, 0, math.MaxInt32)
|
||||
}
|
||||
|
||||
func NewCommand() *cobra.Command {
|
||||
var (
|
||||
parallelismLimit int64
|
||||
listenPort int
|
||||
metricsPort int
|
||||
cacheSrc func() (*reposervercache.Cache, error)
|
||||
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
|
||||
redisClient *redis.Client
|
||||
)
|
||||
var command = cobra.Command{
|
||||
Use: cliName,
|
||||
Short: "Run ArgoCD Repository Server",
|
||||
Long: "ArgoCD Repository Server is an internal service which maintains a local cache of the Git repository holding the application manifests, and is responsible for generating and returning the Kubernetes manifests. This command runs Repository Server in the foreground. It can be configured by following options.",
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(c *cobra.Command, args []string) error {
|
||||
cli.SetLogFormat(cmdutil.LogFormat)
|
||||
cli.SetLogLevel(cmdutil.LogLevel)
|
||||
|
||||
tlsConfigCustomizer, err := tlsConfigCustomizerSrc()
|
||||
errors.CheckError(err)
|
||||
|
||||
cache, err := cacheSrc()
|
||||
errors.CheckError(err)
|
||||
|
||||
metricsServer := metrics.NewMetricsServer()
|
||||
cacheutil.CollectMetrics(redisClient, metricsServer)
|
||||
server, err := reposerver.NewServer(metricsServer, cache, tlsConfigCustomizer, repository.RepoServerInitConstants{
|
||||
ParallelismLimit: parallelismLimit,
|
||||
PauseGenerationAfterFailedGenerationAttempts: getPauseGenerationAfterFailedGenerationAttempts(),
|
||||
PauseGenerationOnFailureForMinutes: getPauseGenerationOnFailureForMinutes(),
|
||||
PauseGenerationOnFailureForRequests: getPauseGenerationOnFailureForRequests(),
|
||||
})
|
||||
errors.CheckError(err)
|
||||
|
||||
grpc := server.CreateGRPC()
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", listenPort))
|
||||
errors.CheckError(err)
|
||||
|
||||
healthz.ServeHealthCheck(http.DefaultServeMux, func(r *http.Request) error {
|
||||
if val, ok := r.URL.Query()["full"]; ok && len(val) > 0 && val[0] == "true" {
|
||||
// connect to itself to make sure repo server is able to serve connection
|
||||
// used by liveness probe to auto restart repo server
|
||||
// see https://github.com/argoproj/argo-cd/issues/5110 for more information
|
||||
conn, err := apiclient.NewConnection(fmt.Sprintf("localhost:%d", listenPort), 60)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ioutil.Close(conn)
|
||||
client := grpc_health_v1.NewHealthClient(conn)
|
||||
res, err := client.Check(r.Context(), &grpc_health_v1.HealthCheckRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.Status != grpc_health_v1.HealthCheckResponse_SERVING {
|
||||
return fmt.Errorf("grpc health check status is '%v'", res.Status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
})
|
||||
http.Handle("/metrics", metricsServer.GetHandler())
|
||||
go func() { errors.CheckError(http.ListenAndServe(fmt.Sprintf(":%d", metricsPort), nil)) }()
|
||||
|
||||
if gpg.IsGPGEnabled() {
|
||||
log.Infof("Initializing GnuPG keyring at %s", common.GetGnuPGHomePath())
|
||||
err = gpg.InitializeGnuPG()
|
||||
errors.CheckError(err)
|
||||
|
||||
log.Infof("Populating GnuPG keyring with keys from %s", getGnuPGSourcePath())
|
||||
added, removed, err := gpg.SyncKeyRingFromDirectory(getGnuPGSourcePath())
|
||||
errors.CheckError(err)
|
||||
log.Infof("Loaded %d (and removed %d) keys from keyring", len(added), len(removed))
|
||||
|
||||
go func() { errors.CheckError(reposerver.StartGPGWatcher(getGnuPGSourcePath())) }()
|
||||
}
|
||||
|
||||
log.Infof("argocd-repo-server %s serving on %s", common.GetVersion(), listener.Addr())
|
||||
stats.RegisterStackDumper()
|
||||
stats.StartStatsTicker(10 * time.Minute)
|
||||
stats.RegisterHeapDumper("memprofile")
|
||||
err = grpc.Serve(listener)
|
||||
errors.CheckError(err)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
command.Flags().StringVar(&cmdutil.LogFormat, "logformat", "text", "Set the logging format. One of: text|json")
|
||||
command.Flags().StringVar(&cmdutil.LogLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
|
||||
command.Flags().Int64Var(¶llelismLimit, "parallelismlimit", 0, "Limit on number of concurrent manifests generate requests. Any value less the 1 means no limit.")
|
||||
command.Flags().IntVar(&listenPort, "port", common.DefaultPortRepoServer, "Listen on given port for incoming connections")
|
||||
command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortRepoServerMetrics, "Start metrics server on given port")
|
||||
|
||||
tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(&command)
|
||||
cacheSrc = reposervercache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {
|
||||
redisClient = client
|
||||
})
|
||||
return &command
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
"github.com/argoproj/pkg/stats"
|
||||
"github.com/go-redis/redis"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
"github.com/argoproj/argo-cd/reposerver"
|
||||
reposervercache "github.com/argoproj/argo-cd/reposerver/cache"
|
||||
"github.com/argoproj/argo-cd/reposerver/metrics"
|
||||
cacheutil "github.com/argoproj/argo-cd/util/cache"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/gpg"
|
||||
"github.com/argoproj/argo-cd/util/tls"
|
||||
)
|
||||
|
||||
const (
|
||||
// CLIName is the name of the CLI
|
||||
cliName = "argocd-repo-server"
|
||||
gnuPGSourcePath = "/app/config/gpg/source"
|
||||
)
|
||||
|
||||
func getGnuPGSourcePath() string {
|
||||
if path := os.Getenv("ARGOCD_GPG_DATA_PATH"); path != "" {
|
||||
return path
|
||||
} else {
|
||||
return gnuPGSourcePath
|
||||
}
|
||||
}
|
||||
|
||||
func newCommand() *cobra.Command {
|
||||
var (
|
||||
logFormat string
|
||||
logLevel string
|
||||
parallelismLimit int64
|
||||
listenPort int
|
||||
metricsPort int
|
||||
cacheSrc func() (*reposervercache.Cache, error)
|
||||
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
|
||||
redisClient *redis.Client
|
||||
)
|
||||
var command = cobra.Command{
|
||||
Use: cliName,
|
||||
Short: "Run argocd-repo-server",
|
||||
RunE: func(c *cobra.Command, args []string) error {
|
||||
cli.SetLogFormat(logFormat)
|
||||
cli.SetLogLevel(logLevel)
|
||||
|
||||
tlsConfigCustomizer, err := tlsConfigCustomizerSrc()
|
||||
errors.CheckError(err)
|
||||
|
||||
cache, err := cacheSrc()
|
||||
errors.CheckError(err)
|
||||
|
||||
metricsServer := metrics.NewMetricsServer()
|
||||
cacheutil.CollectMetrics(redisClient, metricsServer)
|
||||
server, err := reposerver.NewServer(metricsServer, cache, tlsConfigCustomizer, parallelismLimit)
|
||||
errors.CheckError(err)
|
||||
|
||||
grpc := server.CreateGRPC()
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", listenPort))
|
||||
errors.CheckError(err)
|
||||
|
||||
http.Handle("/metrics", metricsServer.GetHandler())
|
||||
go func() { errors.CheckError(http.ListenAndServe(fmt.Sprintf(":%d", metricsPort), nil)) }()
|
||||
|
||||
if gpg.IsGPGEnabled() {
|
||||
log.Infof("Initializing GnuPG keyring at %s", common.GetGnuPGHomePath())
|
||||
err = gpg.InitializeGnuPG()
|
||||
errors.CheckError(err)
|
||||
|
||||
log.Infof("Populating GnuPG keyring with keys from %s", getGnuPGSourcePath())
|
||||
added, removed, err := gpg.SyncKeyRingFromDirectory(getGnuPGSourcePath())
|
||||
errors.CheckError(err)
|
||||
log.Infof("Loaded %d (and removed %d) keys from keyring", len(added), len(removed))
|
||||
|
||||
go func() { errors.CheckError(reposerver.StartGPGWatcher(getGnuPGSourcePath())) }()
|
||||
}
|
||||
|
||||
log.Infof("argocd-repo-server %s serving on %s", common.GetVersion(), listener.Addr())
|
||||
stats.RegisterStackDumper()
|
||||
stats.StartStatsTicker(10 * time.Minute)
|
||||
stats.RegisterHeapDumper("memprofile")
|
||||
err = grpc.Serve(listener)
|
||||
errors.CheckError(err)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
command.Flags().StringVar(&logFormat, "logformat", "text", "Set the logging format. One of: text|json")
|
||||
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
|
||||
command.Flags().Int64Var(¶llelismLimit, "parallelismlimit", 0, "Limit on number of concurrent manifests generate requests. Any value less the 1 means no limit.")
|
||||
command.Flags().IntVar(&listenPort, "port", common.DefaultPortRepoServer, "Listen on given port for incoming connections")
|
||||
command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortRepoServerMetrics, "Start metrics server on given port")
|
||||
tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(&command)
|
||||
cacheSrc = reposervercache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {
|
||||
redisClient = client
|
||||
})
|
||||
return &command
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := newCommand().Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -4,15 +4,14 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
"github.com/argoproj/pkg/stats"
|
||||
"github.com/go-redis/redis"
|
||||
"github.com/go-redis/redis/v8"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
cmdutil "github.com/argoproj/argo-cd/cmd/util"
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
|
||||
@@ -21,6 +20,7 @@ import (
|
||||
servercache "github.com/argoproj/argo-cd/server/cache"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/env"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/kube"
|
||||
"github.com/argoproj/argo-cd/util/tls"
|
||||
)
|
||||
@@ -47,8 +47,6 @@ func NewCommand() *cobra.Command {
|
||||
insecure bool
|
||||
listenPort int
|
||||
metricsPort int
|
||||
logFormat string
|
||||
logLevel string
|
||||
glogLevel int
|
||||
clientConfig clientcmd.ClientConfig
|
||||
repoServerTimeoutSeconds int
|
||||
@@ -58,17 +56,19 @@ func NewCommand() *cobra.Command {
|
||||
repoServerAddress string
|
||||
dexServerAddress string
|
||||
disableAuth bool
|
||||
enableGZip bool
|
||||
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
|
||||
cacheSrc func() (*servercache.Cache, error)
|
||||
frameOptions string
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: cliName,
|
||||
Short: "Run the argocd API server",
|
||||
Long: "Run the argocd API server",
|
||||
Use: cliName,
|
||||
Short: "Run the ArgoCD API server",
|
||||
Long: "The API server is a gRPC/REST server which exposes the API consumed by the Web UI, CLI, and CI/CD systems. This command runs API server in the foreground. It can be configured by following options.",
|
||||
DisableAutoGenTag: true,
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
cli.SetLogFormat(logFormat)
|
||||
cli.SetLogLevel(logLevel)
|
||||
cli.SetLogFormat(cmdutil.LogFormat)
|
||||
cli.SetLogLevel(cmdutil.LogLevel)
|
||||
cli.SetGLogLevel(glogLevel)
|
||||
|
||||
config, err := clientConfig.ClientConfig()
|
||||
@@ -115,6 +115,7 @@ func NewCommand() *cobra.Command {
|
||||
RepoClientset: repoclientset,
|
||||
DexServerAddr: dexServerAddress,
|
||||
DisableAuth: disableAuth,
|
||||
EnableGZip: enableGZip,
|
||||
TLSConfigCustomizer: tlsConfigCustomizer,
|
||||
Cache: cache,
|
||||
XFrameOptions: frameOptions,
|
||||
@@ -140,12 +141,13 @@ func NewCommand() *cobra.Command {
|
||||
command.Flags().StringVar(&staticAssetsDir, "staticassets", "", "Static assets directory path")
|
||||
command.Flags().StringVar(&baseHRef, "basehref", "/", "Value for base href in index.html. Used if Argo CD is running behind reverse proxy under subpath different from /")
|
||||
command.Flags().StringVar(&rootPath, "rootpath", "", "Used if Argo CD is running behind reverse proxy under subpath different from /")
|
||||
command.Flags().StringVar(&logFormat, "logformat", "text", "Set the logging format. One of: text|json")
|
||||
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
|
||||
command.Flags().StringVar(&cmdutil.LogFormat, "logformat", "text", "Set the logging format. One of: text|json")
|
||||
command.Flags().StringVar(&cmdutil.LogLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
|
||||
command.Flags().IntVar(&glogLevel, "gloglevel", 0, "Set the glog logging level")
|
||||
command.Flags().StringVar(&repoServerAddress, "repo-server", common.DefaultRepoServerAddr, "Repo server address")
|
||||
command.Flags().StringVar(&dexServerAddress, "dex-server", common.DefaultDexServerAddr, "Dex server address")
|
||||
command.Flags().BoolVar(&disableAuth, "disable-auth", false, "Disable client authentication")
|
||||
command.Flags().BoolVar(&enableGZip, "enable-gzip", false, "Enable GZIP compression")
|
||||
command.AddCommand(cli.NewVersionCmd(cliName))
|
||||
command.Flags().IntVar(&listenPort, "port", common.DefaultPortAPIServer, "Listen on given port")
|
||||
command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortArgoCDAPIServerMetrics, "Start metrics on given port")
|
||||
@@ -1,17 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
|
||||
commands "github.com/argoproj/argo-cd/cmd/argocd-server/commands"
|
||||
|
||||
// load the gcp plugin (required to authenticate against GKE clusters).
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
// load the oidc plugin (required to authenticate with OpenID Connect).
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := commands.NewCommand().Execute()
|
||||
errors.CheckError(err)
|
||||
}
|
||||
@@ -5,13 +5,13 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/diff"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
appstatecache "github.com/argoproj/argo-cd/util/cache/appstate"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/spf13/cobra"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
@@ -30,16 +30,19 @@ import (
|
||||
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
|
||||
appinformers "github.com/argoproj/argo-cd/pkg/client/informers/externalversions"
|
||||
"github.com/argoproj/argo-cd/reposerver/apiclient"
|
||||
cacheutil "github.com/argoproj/argo-cd/util/cache"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/config"
|
||||
"github.com/argoproj/argo-cd/util/db"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
kubeutil "github.com/argoproj/argo-cd/util/kube"
|
||||
"github.com/argoproj/argo-cd/util/settings"
|
||||
)
|
||||
|
||||
func NewAppsCommand() *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "apps",
|
||||
Use: "apps",
|
||||
Short: "Utility commands operate on ArgoCD applications",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
c.HelpFunc()(c, args)
|
||||
},
|
||||
@@ -147,7 +150,7 @@ func diffReconcileResults(res1 reconcileResults, res2 reconcileResults) error {
|
||||
})
|
||||
for _, item := range pairs {
|
||||
printLine(item.name)
|
||||
_ = diff.PrintDiff(item.name, item.first, item.second)
|
||||
_ = cli.PrintDiff(item.name, item.first, item.second)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -277,16 +280,27 @@ func reconcileApplications(
|
||||
|
||||
appLister := appInformerFactory.Argoproj().V1alpha1().Applications().Lister()
|
||||
projLister := appInformerFactory.Argoproj().V1alpha1().AppProjects().Lister()
|
||||
server := metrics.NewMetricsServer("", appLister, func() error {
|
||||
server, err := metrics.NewMetricsServer("", appLister, func(obj interface{}) bool {
|
||||
return true
|
||||
}, func(r *http.Request) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stateCache := createLiveStateCache(argoDB, appInformer, settingsMgr, server)
|
||||
if err := stateCache.Init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cache := appstatecache.NewCache(
|
||||
cacheutil.NewCache(cacheutil.NewInMemoryCache(1*time.Minute)),
|
||||
1*time.Minute,
|
||||
)
|
||||
|
||||
appStateManager := controller.NewAppStateManager(
|
||||
argoDB, appClientset, repoServerClient, namespace, &kube.KubectlCmd{}, settingsMgr, stateCache, projInformer, server)
|
||||
argoDB, appClientset, repoServerClient, namespace, kubeutil.NewKubectl(), settingsMgr, stateCache, projInformer, server, cache, time.Second)
|
||||
|
||||
appsList, err := appClientset.ArgoprojV1alpha1().Applications(namespace).List(context.Background(), v1.ListOptions{LabelSelector: selector})
|
||||
if err != nil {
|
||||
@@ -328,5 +342,5 @@ func reconcileApplications(
|
||||
}
|
||||
|
||||
func newLiveStateCache(argoDB db.ArgoDB, appInformer kubecache.SharedIndexInformer, settingsMgr *settings.SettingsManager, server *metrics.MetricsServer) cache.LiveStateCache {
|
||||
return cache.NewLiveStateCache(argoDB, appInformer, settingsMgr, &kube.KubectlCmd{}, server, func(managedByApp map[string]bool, ref apiv1.ObjectReference) {})
|
||||
return cache.NewLiveStateCache(argoDB, appInformer, settingsMgr, kubeutil.NewKubectl(), server, func(managedByApp map[string]bool, ref apiv1.ObjectReference) {}, nil)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -7,11 +7,8 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
"syscall"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
"github.com/ghodss/yaml"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -26,17 +23,12 @@ import (
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
"github.com/argoproj/argo-cd/cmd/argocd-util/commands"
|
||||
cmdutil "github.com/argoproj/argo-cd/cmd/util"
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/db"
|
||||
"github.com/argoproj/argo-cd/util/dex"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/settings"
|
||||
|
||||
// load the gcp plugin (required to authenticate against GKE clusters).
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
// load the oidc plugin (required to authenticate with OpenID Connect).
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -56,158 +48,34 @@ var (
|
||||
// NewCommand returns a new instance of an argocd command
|
||||
func NewCommand() *cobra.Command {
|
||||
var (
|
||||
logFormat string
|
||||
logLevel string
|
||||
pathOpts = clientcmd.NewDefaultPathOptions()
|
||||
)
|
||||
|
||||
var command = &cobra.Command{
|
||||
Use: cliName,
|
||||
Short: "argocd-util has internal tools used by Argo CD",
|
||||
Use: cliName,
|
||||
Short: "argocd-util tools used by Argo CD",
|
||||
Long: "argocd-util has internal utility tools used by Argo CD",
|
||||
DisableAutoGenTag: true,
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
c.HelpFunc()(c, args)
|
||||
},
|
||||
}
|
||||
|
||||
command.AddCommand(cli.NewVersionCmd(cliName))
|
||||
command.AddCommand(NewRunDexCommand())
|
||||
command.AddCommand(NewGenDexConfigCommand())
|
||||
command.AddCommand(NewImportCommand())
|
||||
command.AddCommand(NewExportCommand())
|
||||
command.AddCommand(NewClusterConfig())
|
||||
command.AddCommand(commands.NewProjectsCommand())
|
||||
command.AddCommand(commands.NewSettingsCommand())
|
||||
command.AddCommand(commands.NewAppsCommand())
|
||||
command.AddCommand(NewProjectsCommand())
|
||||
command.AddCommand(NewSettingsCommand())
|
||||
command.AddCommand(NewAppsCommand())
|
||||
command.AddCommand(NewRBACCommand())
|
||||
command.AddCommand(NewGenerateConfigCommand(pathOpts))
|
||||
|
||||
command.Flags().StringVar(&logFormat, "logformat", "text", "Set the logging format. One of: text|json")
|
||||
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
|
||||
command.Flags().StringVar(&cmdutil.LogFormat, "logformat", "text", "Set the logging format. One of: text|json")
|
||||
command.Flags().StringVar(&cmdutil.LogLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
|
||||
return command
|
||||
}
|
||||
|
||||
func NewRunDexCommand() *cobra.Command {
|
||||
var (
|
||||
clientConfig clientcmd.ClientConfig
|
||||
)
|
||||
var command = cobra.Command{
|
||||
Use: "rundex",
|
||||
Short: "Runs dex generating a config using settings from the Argo CD configmap and secret",
|
||||
RunE: func(c *cobra.Command, args []string) error {
|
||||
_, err := exec.LookPath("dex")
|
||||
errors.CheckError(err)
|
||||
config, err := clientConfig.ClientConfig()
|
||||
errors.CheckError(err)
|
||||
namespace, _, err := clientConfig.Namespace()
|
||||
errors.CheckError(err)
|
||||
kubeClientset := kubernetes.NewForConfigOrDie(config)
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), kubeClientset, namespace)
|
||||
prevSettings, err := settingsMgr.GetSettings()
|
||||
errors.CheckError(err)
|
||||
updateCh := make(chan *settings.ArgoCDSettings, 1)
|
||||
settingsMgr.Subscribe(updateCh)
|
||||
|
||||
for {
|
||||
var cmd *exec.Cmd
|
||||
dexCfgBytes, err := dex.GenerateDexConfigYAML(prevSettings)
|
||||
errors.CheckError(err)
|
||||
if len(dexCfgBytes) == 0 {
|
||||
log.Infof("dex is not configured")
|
||||
} else {
|
||||
err = ioutil.WriteFile("/tmp/dex.yaml", dexCfgBytes, 0644)
|
||||
errors.CheckError(err)
|
||||
log.Debug(redactor(string(dexCfgBytes)))
|
||||
cmd = exec.Command("dex", "serve", "/tmp/dex.yaml")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Start()
|
||||
errors.CheckError(err)
|
||||
}
|
||||
|
||||
// loop until the dex config changes
|
||||
for {
|
||||
newSettings := <-updateCh
|
||||
newDexCfgBytes, err := dex.GenerateDexConfigYAML(newSettings)
|
||||
errors.CheckError(err)
|
||||
if string(newDexCfgBytes) != string(dexCfgBytes) {
|
||||
prevSettings = newSettings
|
||||
log.Infof("dex config modified. restarting dex")
|
||||
if cmd != nil && cmd.Process != nil {
|
||||
err = cmd.Process.Signal(syscall.SIGTERM)
|
||||
errors.CheckError(err)
|
||||
_, err = cmd.Process.Wait()
|
||||
errors.CheckError(err)
|
||||
}
|
||||
break
|
||||
} else {
|
||||
log.Infof("dex config unmodified")
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
clientConfig = cli.AddKubectlFlagsToCmd(&command)
|
||||
return &command
|
||||
}
|
||||
|
||||
func NewGenDexConfigCommand() *cobra.Command {
|
||||
var (
|
||||
clientConfig clientcmd.ClientConfig
|
||||
out string
|
||||
)
|
||||
var command = cobra.Command{
|
||||
Use: "gendexcfg",
|
||||
Short: "Generates a dex config from Argo CD settings",
|
||||
RunE: func(c *cobra.Command, args []string) error {
|
||||
config, err := clientConfig.ClientConfig()
|
||||
errors.CheckError(err)
|
||||
namespace, _, err := clientConfig.Namespace()
|
||||
errors.CheckError(err)
|
||||
kubeClientset := kubernetes.NewForConfigOrDie(config)
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), kubeClientset, namespace)
|
||||
settings, err := settingsMgr.GetSettings()
|
||||
errors.CheckError(err)
|
||||
dexCfgBytes, err := dex.GenerateDexConfigYAML(settings)
|
||||
errors.CheckError(err)
|
||||
if len(dexCfgBytes) == 0 {
|
||||
log.Infof("dex is not configured")
|
||||
return nil
|
||||
}
|
||||
if out == "" {
|
||||
dexCfg := make(map[string]interface{})
|
||||
err := yaml.Unmarshal(dexCfgBytes, &dexCfg)
|
||||
errors.CheckError(err)
|
||||
if staticClientsInterface, ok := dexCfg["staticClients"]; ok {
|
||||
if staticClients, ok := staticClientsInterface.([]interface{}); ok {
|
||||
for i := range staticClients {
|
||||
staticClient := staticClients[i]
|
||||
if mappings, ok := staticClient.(map[string]interface{}); ok {
|
||||
for key := range mappings {
|
||||
if key == "secret" {
|
||||
mappings[key] = "******"
|
||||
}
|
||||
}
|
||||
staticClients[i] = mappings
|
||||
}
|
||||
}
|
||||
dexCfg["staticClients"] = staticClients
|
||||
}
|
||||
}
|
||||
errors.CheckError(err)
|
||||
maskedDexCfgBytes, err := yaml.Marshal(dexCfg)
|
||||
errors.CheckError(err)
|
||||
fmt.Print(string(maskedDexCfgBytes))
|
||||
} else {
|
||||
err = ioutil.WriteFile(out, dexCfgBytes, 0644)
|
||||
errors.CheckError(err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
clientConfig = cli.AddKubectlFlagsToCmd(&command)
|
||||
command.Flags().StringVarP(&out, "out", "o", "", "Output to the specified file instead of stdout")
|
||||
return &command
|
||||
}
|
||||
|
||||
// NewImportCommand defines a new command for exporting Kubernetes and Argo CD resources.
|
||||
func NewImportCommand() *cobra.Command {
|
||||
var (
|
||||
@@ -619,8 +487,9 @@ func NewClusterConfig() *cobra.Command {
|
||||
clientConfig clientcmd.ClientConfig
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "kubeconfig CLUSTER_URL OUTPUT_PATH",
|
||||
Short: "Generates kubeconfig for the specified cluster",
|
||||
Use: "kubeconfig CLUSTER_URL OUTPUT_PATH",
|
||||
Short: "Generates kubeconfig for the specified cluster",
|
||||
DisableAutoGenTag: true,
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
c.HelpFunc()(c, args)
|
||||
@@ -676,10 +545,3 @@ func redactor(dirtyString string) string {
|
||||
errors.CheckError(err)
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := NewCommand().Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
359
cmd/argocd-util/commands/config.go
Normal file
359
cmd/argocd-util/commands/config.go
Normal file
@@ -0,0 +1,359 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
apierr "k8s.io/apimachinery/pkg/api/errors"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
cmdutil "github.com/argoproj/argo-cd/cmd/util"
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/db"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/git"
|
||||
"github.com/argoproj/argo-cd/util/settings"
|
||||
)
|
||||
|
||||
const (
|
||||
ArgoCDNamespace = "argocd"
|
||||
repoSecretPrefix = "repo"
|
||||
)
|
||||
|
||||
func NewGenerateConfigCommand(pathOpts *clientcmd.PathOptions) *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Generate declarative configuration files",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
c.HelpFunc()(c, args)
|
||||
},
|
||||
}
|
||||
command.AddCommand(NewGenAppConfigCommand())
|
||||
command.AddCommand(NewGenProjectConfigCommand())
|
||||
command.AddCommand(NewGenClusterConfigCommand(pathOpts))
|
||||
command.AddCommand(NewGenRepoConfigCommand())
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
// NewGenAppConfigCommand generates declarative configuration file for given application
|
||||
func NewGenAppConfigCommand() *cobra.Command {
|
||||
var (
|
||||
appOpts cmdutil.AppOptions
|
||||
fileURL string
|
||||
appName string
|
||||
labels []string
|
||||
outputFormat string
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "app APPNAME",
|
||||
Short: "Generate declarative config for an application",
|
||||
Example: `
|
||||
# Generate declarative config for a directory app
|
||||
argocd-util config app guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --dest-namespace default --dest-server https://kubernetes.default.svc --directory-recurse
|
||||
|
||||
# Generate declarative config for a Jsonnet app
|
||||
argocd-util config app jsonnet-guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path jsonnet-guestbook --dest-namespace default --dest-server https://kubernetes.default.svc --jsonnet-ext-str replicas=2
|
||||
|
||||
# Generate declarative config for a Helm app
|
||||
argocd-util config app helm-guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path helm-guestbook --dest-namespace default --dest-server https://kubernetes.default.svc --helm-set replicaCount=2
|
||||
|
||||
# Generate declarative config for a Helm app from a Helm repo
|
||||
argocd-util config app nginx-ingress --repo https://kubernetes-charts.storage.googleapis.com --helm-chart nginx-ingress --revision 1.24.3 --dest-namespace default --dest-server https://kubernetes.default.svc
|
||||
|
||||
# Generate declarative config for a Kustomize app
|
||||
argocd-util config app kustomize-guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path kustomize-guestbook --dest-namespace default --dest-server https://kubernetes.default.svc --kustomize-image gcr.io/heptio-images/ks-guestbook-demo:0.1
|
||||
|
||||
# Generate declarative config for a app using a custom tool:
|
||||
argocd-util config app ksane --repo https://github.com/argoproj/argocd-example-apps.git --path plugins/kasane --dest-namespace default --dest-server https://kubernetes.default.svc --config-management-plugin kasane
|
||||
`,
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
app, err := cmdutil.ConstructApp(fileURL, appName, labels, args, appOpts, c.Flags())
|
||||
errors.CheckError(err)
|
||||
|
||||
if app.Name == "" {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var printResources []interface{}
|
||||
printResources = append(printResources, app)
|
||||
errors.CheckError(cmdutil.PrintResources(printResources, outputFormat))
|
||||
},
|
||||
}
|
||||
command.Flags().StringVar(&appName, "name", "", "A name for the app, ignored if a file is set (DEPRECATED)")
|
||||
command.Flags().StringVarP(&fileURL, "file", "f", "", "Filename or URL to Kubernetes manifests for the app")
|
||||
command.Flags().StringArrayVarP(&labels, "label", "l", []string{}, "Labels to apply to the app")
|
||||
command.Flags().StringVarP(&outputFormat, "output", "o", "yaml", "Output format. One of: json|yaml")
|
||||
|
||||
// Only complete files with appropriate extension.
|
||||
err := command.Flags().SetAnnotation("file", cobra.BashCompFilenameExt, []string{"json", "yaml", "yml"})
|
||||
errors.CheckError(err)
|
||||
|
||||
cmdutil.AddAppFlags(command, &appOpts)
|
||||
return command
|
||||
}
|
||||
|
||||
// NewGenProjectConfigCommand generates declarative configuration file for given project
|
||||
func NewGenProjectConfigCommand() *cobra.Command {
|
||||
var (
|
||||
opts cmdutil.ProjectOpts
|
||||
fileURL string
|
||||
outputFormat string
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "proj PROJECT",
|
||||
Short: "Generate declarative config for a project",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
proj, err := cmdutil.ConstructAppProj(fileURL, args, opts, c)
|
||||
errors.CheckError(err)
|
||||
|
||||
var printResources []interface{}
|
||||
printResources = append(printResources, proj)
|
||||
errors.CheckError(cmdutil.PrintResources(printResources, outputFormat))
|
||||
},
|
||||
}
|
||||
command.Flags().StringVarP(&fileURL, "file", "f", "", "Filename or URL to Kubernetes manifests for the project")
|
||||
command.Flags().StringVarP(&outputFormat, "output", "o", "yaml", "Output format. One of: json|yaml")
|
||||
err := command.Flags().SetAnnotation("file", cobra.BashCompFilenameExt, []string{"json", "yaml", "yml"})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
cmdutil.AddProjFlags(command, &opts)
|
||||
return command
|
||||
}
|
||||
|
||||
func NewGenClusterConfigCommand(pathOpts *clientcmd.PathOptions) *cobra.Command {
|
||||
var (
|
||||
clusterOpts cmdutil.ClusterOptions
|
||||
bearerToken string
|
||||
outputFormat string
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "cluster CONTEXT",
|
||||
Short: "Generate declarative config for a cluster",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
var configAccess clientcmd.ConfigAccess = pathOpts
|
||||
if len(args) == 0 {
|
||||
log.Error("Choose a context name from:")
|
||||
cmdutil.PrintKubeContexts(configAccess)
|
||||
os.Exit(1)
|
||||
}
|
||||
cfgAccess, err := configAccess.GetStartingConfig()
|
||||
errors.CheckError(err)
|
||||
contextName := args[0]
|
||||
clstContext := cfgAccess.Contexts[contextName]
|
||||
if clstContext == nil {
|
||||
log.Fatalf("Context %s does not exist in kubeconfig", contextName)
|
||||
}
|
||||
|
||||
overrides := clientcmd.ConfigOverrides{
|
||||
Context: *clstContext,
|
||||
}
|
||||
clientConfig := clientcmd.NewDefaultClientConfig(*cfgAccess, &overrides)
|
||||
conf, err := clientConfig.ClientConfig()
|
||||
errors.CheckError(err)
|
||||
kubeClientset := fake.NewSimpleClientset()
|
||||
|
||||
var awsAuthConf *argoappv1.AWSAuthConfig
|
||||
var execProviderConf *argoappv1.ExecProviderConfig
|
||||
if clusterOpts.AwsClusterName != "" {
|
||||
awsAuthConf = &argoappv1.AWSAuthConfig{
|
||||
ClusterName: clusterOpts.AwsClusterName,
|
||||
RoleARN: clusterOpts.AwsRoleArn,
|
||||
}
|
||||
} else if clusterOpts.ExecProviderCommand != "" {
|
||||
execProviderConf = &argoappv1.ExecProviderConfig{
|
||||
Command: clusterOpts.ExecProviderCommand,
|
||||
Args: clusterOpts.ExecProviderArgs,
|
||||
Env: clusterOpts.ExecProviderEnv,
|
||||
APIVersion: clusterOpts.ExecProviderAPIVersion,
|
||||
InstallHint: clusterOpts.ExecProviderInstallHint,
|
||||
}
|
||||
} else if bearerToken == "" {
|
||||
bearerToken = "bearer-token"
|
||||
}
|
||||
if clusterOpts.Name != "" {
|
||||
contextName = clusterOpts.Name
|
||||
}
|
||||
clst := cmdutil.NewCluster(contextName, clusterOpts.Namespaces, conf, bearerToken, awsAuthConf, execProviderConf)
|
||||
if clusterOpts.InCluster {
|
||||
clst.Server = common.KubernetesInternalAPIServerAddr
|
||||
}
|
||||
if clusterOpts.Shard >= 0 {
|
||||
clst.Shard = &clusterOpts.Shard
|
||||
}
|
||||
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), kubeClientset, ArgoCDNamespace)
|
||||
argoDB := db.NewDB(ArgoCDNamespace, settingsMgr, kubeClientset)
|
||||
|
||||
_, err = argoDB.CreateCluster(context.Background(), clst)
|
||||
errors.CheckError(err)
|
||||
|
||||
secName, err := db.ServerToSecretName(clst.Server)
|
||||
errors.CheckError(err)
|
||||
|
||||
secret, err := kubeClientset.CoreV1().Secrets(ArgoCDNamespace).Get(context.Background(), secName, v1.GetOptions{})
|
||||
errors.CheckError(err)
|
||||
|
||||
cmdutil.ConvertSecretData(secret)
|
||||
var printResources []interface{}
|
||||
printResources = append(printResources, secret)
|
||||
errors.CheckError(cmdutil.PrintResources(printResources, outputFormat))
|
||||
},
|
||||
}
|
||||
command.PersistentFlags().StringVar(&pathOpts.LoadingRules.ExplicitPath, pathOpts.ExplicitFileFlag, pathOpts.LoadingRules.ExplicitPath, "use a particular kubeconfig file")
|
||||
command.Flags().StringVar(&bearerToken, "bearer-token", "", "Authentication token that should be used to access K8S API server")
|
||||
command.Flags().StringVarP(&outputFormat, "output", "o", "yaml", "Output format. One of: json|yaml")
|
||||
cmdutil.AddClusterFlags(command, &clusterOpts)
|
||||
return command
|
||||
}
|
||||
|
||||
func NewGenRepoConfigCommand() *cobra.Command {
|
||||
var (
|
||||
repoOpts cmdutil.RepoOptions
|
||||
outputFormat string
|
||||
)
|
||||
|
||||
// For better readability and easier formatting
|
||||
var repoAddExamples = `
|
||||
# Add a Git repository via SSH using a private key for authentication, ignoring the server's host key:
|
||||
argocd-util config repo git@git.example.com:repos/repo --insecure-ignore-host-key --ssh-private-key-path ~/id_rsa
|
||||
|
||||
# Add a Git repository via SSH on a non-default port - need to use ssh:// style URLs here
|
||||
argocd-util config repo ssh://git@git.example.com:2222/repos/repo --ssh-private-key-path ~/id_rsa
|
||||
|
||||
# Add a private Git repository via HTTPS using username/password and TLS client certificates:
|
||||
argocd-util config repo https://git.example.com/repos/repo --username git --password secret --tls-client-cert-path ~/mycert.crt --tls-client-cert-key-path ~/mycert.key
|
||||
|
||||
# Add a private Git repository via HTTPS using username/password without verifying the server's TLS certificate
|
||||
argocd-util config repo https://git.example.com/repos/repo --username git --password secret --insecure-skip-server-verification
|
||||
|
||||
# Add a public Helm repository named 'stable' via HTTPS
|
||||
argocd-util config repo https://kubernetes-charts.storage.googleapis.com --type helm --name stable
|
||||
|
||||
# Add a private Helm repository named 'stable' via HTTPS
|
||||
argocd-util config repo https://kubernetes-charts.storage.googleapis.com --type helm --name stable --username test --password test
|
||||
|
||||
# Add a private Helm OCI-based repository named 'stable' via HTTPS
|
||||
argocd-util config repo helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type helm --name stable --enable-oci --username test --password test
|
||||
`
|
||||
|
||||
var command = &cobra.Command{
|
||||
Use: "repo REPOURL",
|
||||
Short: "Generate declarative config for a repo",
|
||||
Example: repoAddExamples,
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if len(args) != 1 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Repository URL
|
||||
repoOpts.Repo.Repo = args[0]
|
||||
|
||||
// Specifying ssh-private-key-path is only valid for SSH repositories
|
||||
if repoOpts.SshPrivateKeyPath != "" {
|
||||
if ok, _ := git.IsSSHURL(repoOpts.Repo.Repo); ok {
|
||||
keyData, err := ioutil.ReadFile(repoOpts.SshPrivateKeyPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
repoOpts.Repo.SSHPrivateKey = string(keyData)
|
||||
} else {
|
||||
err := fmt.Errorf("--ssh-private-key-path is only supported for SSH repositories.")
|
||||
errors.CheckError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// tls-client-cert-path and tls-client-cert-key-key-path must always be
|
||||
// specified together
|
||||
if (repoOpts.TlsClientCertPath != "" && repoOpts.TlsClientCertKeyPath == "") || (repoOpts.TlsClientCertPath == "" && repoOpts.TlsClientCertKeyPath != "") {
|
||||
err := fmt.Errorf("--tls-client-cert-path and --tls-client-cert-key-path must be specified together")
|
||||
errors.CheckError(err)
|
||||
}
|
||||
|
||||
// Specifying tls-client-cert-path is only valid for HTTPS repositories
|
||||
if repoOpts.TlsClientCertPath != "" {
|
||||
if git.IsHTTPSURL(repoOpts.Repo.Repo) {
|
||||
tlsCertData, err := ioutil.ReadFile(repoOpts.TlsClientCertPath)
|
||||
errors.CheckError(err)
|
||||
tlsCertKey, err := ioutil.ReadFile(repoOpts.TlsClientCertKeyPath)
|
||||
errors.CheckError(err)
|
||||
repoOpts.Repo.TLSClientCertData = string(tlsCertData)
|
||||
repoOpts.Repo.TLSClientCertKey = string(tlsCertKey)
|
||||
} else {
|
||||
err := fmt.Errorf("--tls-client-cert-path is only supported for HTTPS repositories")
|
||||
errors.CheckError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Set repository connection properties only when creating repository, not
|
||||
// when creating repository credentials.
|
||||
// InsecureIgnoreHostKey is deprecated and only here for backwards compat
|
||||
repoOpts.Repo.InsecureIgnoreHostKey = repoOpts.InsecureIgnoreHostKey
|
||||
repoOpts.Repo.Insecure = repoOpts.InsecureSkipServerVerification
|
||||
repoOpts.Repo.EnableLFS = repoOpts.EnableLfs
|
||||
repoOpts.Repo.EnableOCI = repoOpts.EnableOci
|
||||
|
||||
if repoOpts.Repo.Type == "helm" && repoOpts.Repo.Name == "" {
|
||||
errors.CheckError(fmt.Errorf("must specify --name for repos of type 'helm'"))
|
||||
}
|
||||
|
||||
// If the user set a username, but didn't supply password via --password,
|
||||
// then we prompt for it
|
||||
if repoOpts.Repo.Username != "" && repoOpts.Repo.Password == "" {
|
||||
repoOpts.Repo.Password = cli.PromptPassword(repoOpts.Repo.Password)
|
||||
}
|
||||
|
||||
argoCDCM := &apiv1.ConfigMap{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "ConfigMap",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: common.ArgoCDConfigMapName,
|
||||
Namespace: ArgoCDNamespace,
|
||||
Labels: map[string]string{
|
||||
"app.kubernetes.io/part-of": "argocd",
|
||||
},
|
||||
},
|
||||
}
|
||||
kubeClientset := fake.NewSimpleClientset(argoCDCM)
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), kubeClientset, ArgoCDNamespace)
|
||||
argoDB := db.NewDB(ArgoCDNamespace, settingsMgr, kubeClientset)
|
||||
|
||||
var printResources []interface{}
|
||||
_, err := argoDB.CreateRepository(context.Background(), &repoOpts.Repo)
|
||||
errors.CheckError(err)
|
||||
|
||||
secret, err := kubeClientset.CoreV1().Secrets(ArgoCDNamespace).Get(context.Background(), db.RepoURLToSecretName(repoSecretPrefix, repoOpts.Repo.Repo), v1.GetOptions{})
|
||||
if err != nil {
|
||||
if !apierr.IsNotFound(err) {
|
||||
errors.CheckError(err)
|
||||
}
|
||||
} else {
|
||||
cmdutil.ConvertSecretData(secret)
|
||||
printResources = append(printResources, secret)
|
||||
}
|
||||
|
||||
cm, err := kubeClientset.CoreV1().ConfigMaps(ArgoCDNamespace).Get(context.Background(), common.ArgoCDConfigMapName, v1.GetOptions{})
|
||||
errors.CheckError(err)
|
||||
|
||||
printResources = append(printResources, cm)
|
||||
errors.CheckError(cmdutil.PrintResources(printResources, outputFormat))
|
||||
},
|
||||
}
|
||||
command.Flags().StringVarP(&outputFormat, "output", "o", "yaml", "Output format. One of: json|yaml")
|
||||
cmdutil.AddRepoFlags(command, &repoOpts)
|
||||
return command
|
||||
}
|
||||
144
cmd/argocd-util/commands/project_allowlist.go
Normal file
144
cmd/argocd-util/commands/project_allowlist.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/spf13/cobra"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
|
||||
// load the gcp plugin (required to authenticate against GKE clusters).
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
// load the oidc plugin (required to authenticate with OpenID Connect).
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||
// load the azure plugin (required to authenticate with AKS clusters).
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/azure"
|
||||
)
|
||||
|
||||
// NewProjectAllowListGenCommand generates a project from clusterRole
|
||||
func NewProjectAllowListGenCommand() *cobra.Command {
|
||||
var (
|
||||
clientConfig clientcmd.ClientConfig
|
||||
out string
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "generate-allow-list CLUSTERROLE_PATH PROJ_NAME",
|
||||
Short: "Generates project allow list from the specified clusterRole file",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
clusterRoleFileName := args[0]
|
||||
projName := args[1]
|
||||
|
||||
var writer io.Writer
|
||||
if out == "-" {
|
||||
writer = os.Stdout
|
||||
} else {
|
||||
f, err := os.Create(out)
|
||||
errors.CheckError(err)
|
||||
bw := bufio.NewWriter(f)
|
||||
writer = bw
|
||||
defer func() {
|
||||
err = bw.Flush()
|
||||
errors.CheckError(err)
|
||||
err = f.Close()
|
||||
errors.CheckError(err)
|
||||
}()
|
||||
}
|
||||
|
||||
globalProj := generateProjectAllowList(clientConfig, clusterRoleFileName, projName)
|
||||
|
||||
yamlBytes, err := yaml.Marshal(globalProj)
|
||||
errors.CheckError(err)
|
||||
|
||||
_, err = writer.Write(yamlBytes)
|
||||
errors.CheckError(err)
|
||||
},
|
||||
}
|
||||
clientConfig = cli.AddKubectlFlagsToCmd(command)
|
||||
command.Flags().StringVarP(&out, "out", "o", "-", "Output to the specified file instead of stdout")
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
func generateProjectAllowList(clientConfig clientcmd.ClientConfig, clusterRoleFileName string, projName string) v1alpha1.AppProject {
|
||||
yamlBytes, err := ioutil.ReadFile(clusterRoleFileName)
|
||||
errors.CheckError(err)
|
||||
var obj unstructured.Unstructured
|
||||
err = yaml.Unmarshal(yamlBytes, &obj)
|
||||
errors.CheckError(err)
|
||||
|
||||
clusterRole := &rbacv1.ClusterRole{}
|
||||
err = scheme.Scheme.Convert(&obj, clusterRole, nil)
|
||||
errors.CheckError(err)
|
||||
|
||||
config, err := clientConfig.ClientConfig()
|
||||
errors.CheckError(err)
|
||||
disco, err := discovery.NewDiscoveryClientForConfig(config)
|
||||
errors.CheckError(err)
|
||||
serverResources, err := disco.ServerPreferredResources()
|
||||
errors.CheckError(err)
|
||||
|
||||
resourceList := make([]metav1.GroupKind, 0)
|
||||
for _, rule := range clusterRole.Rules {
|
||||
if len(rule.APIGroups) <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
canCreate := false
|
||||
for _, verb := range rule.Verbs {
|
||||
if strings.EqualFold(verb, "Create") {
|
||||
canCreate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !canCreate {
|
||||
continue
|
||||
}
|
||||
|
||||
ruleApiGroup := rule.APIGroups[0]
|
||||
for _, ruleResource := range rule.Resources {
|
||||
for _, apiResourcesList := range serverResources {
|
||||
gv, err := schema.ParseGroupVersion(apiResourcesList.GroupVersion)
|
||||
if err != nil {
|
||||
gv = schema.GroupVersion{}
|
||||
}
|
||||
if ruleApiGroup == gv.Group {
|
||||
for _, apiResource := range apiResourcesList.APIResources {
|
||||
if apiResource.Name == ruleResource {
|
||||
resourceList = append(resourceList, metav1.GroupKind{Group: ruleApiGroup, Kind: apiResource.Kind})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
globalProj := v1alpha1.AppProject{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "AppProject",
|
||||
APIVersion: "argoproj.io/v1alpha1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: projName},
|
||||
Spec: v1alpha1.AppProjectSpec{},
|
||||
}
|
||||
globalProj.Spec.NamespaceResourceWhitelist = resourceList
|
||||
return globalProj
|
||||
}
|
||||
57
cmd/argocd-util/commands/project_allowlist_test.go
Normal file
57
cmd/argocd-util/commands/project_allowlist_test.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/undefinedlabs/go-mpatch"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/discovery"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
func TestProjectAllowListGen(t *testing.T) {
|
||||
useMock := true
|
||||
rules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
overrides := &clientcmd.ConfigOverrides{}
|
||||
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides)
|
||||
|
||||
if useMock {
|
||||
var patchClientConfig *mpatch.Patch
|
||||
patchClientConfig, err := mpatch.PatchInstanceMethodByName(reflect.TypeOf(clientConfig), "ClientConfig", func(*clientcmd.DeferredLoadingClientConfig) (*restclient.Config, error) {
|
||||
return nil, nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
patch, err := mpatch.PatchMethod(discovery.NewDiscoveryClientForConfig, func(c *restclient.Config) (*discovery.DiscoveryClient, error) {
|
||||
return &discovery.DiscoveryClient{LegacyPrefix: "/api"}, nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
var patchSeverPreferedResources *mpatch.Patch
|
||||
discoClient := &discovery.DiscoveryClient{}
|
||||
patchSeverPreferedResources, err = mpatch.PatchInstanceMethodByName(reflect.TypeOf(discoClient), "ServerPreferredResources", func(*discovery.DiscoveryClient) ([]*metav1.APIResourceList, error) {
|
||||
res := metav1.APIResource{
|
||||
Name: "services",
|
||||
Kind: "Service",
|
||||
}
|
||||
resourceList := []*metav1.APIResourceList{{APIResources: []metav1.APIResource{res}}}
|
||||
return resourceList, nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
err = patchClientConfig.Unpatch()
|
||||
assert.NoError(t, err)
|
||||
err = patch.Unpatch()
|
||||
assert.NoError(t, err)
|
||||
err = patchSeverPreferedResources.Unpatch()
|
||||
err = patch.Unpatch()
|
||||
}()
|
||||
}
|
||||
|
||||
globalProj := generateProjectAllowList(clientConfig, "testdata/test_clusterrole.yaml", "testproj")
|
||||
assert.True(t, len(globalProj.Spec.NamespaceResourceWhitelist) > 0)
|
||||
}
|
||||
@@ -11,9 +11,8 @@ import (
|
||||
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
|
||||
appclient "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/typed/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/diff"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
"github.com/spf13/cobra"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -22,13 +21,15 @@ import (
|
||||
|
||||
func NewProjectsCommand() *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "projects",
|
||||
Use: "projects",
|
||||
Short: "Utility commands operate on ArgoCD Projects",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
c.HelpFunc()(c, args)
|
||||
},
|
||||
}
|
||||
|
||||
command.AddCommand(NewUpdatePolicyRuleCommand())
|
||||
command.AddCommand(NewProjectAllowListGenCommand())
|
||||
return command
|
||||
}
|
||||
|
||||
@@ -70,7 +71,7 @@ func saveProject(updated v1alpha1.AppProject, orig v1alpha1.AppProject, projects
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = diff.PrintDiff(updated.Name, target, live)
|
||||
_ = cli.PrintDiff(updated.Name, target, live)
|
||||
if !dryRun {
|
||||
_, err = projectsIf.Update(context.Background(), &updated, v1.UpdateOptions{})
|
||||
if err != nil {
|
||||
|
||||
374
cmd/argocd-util/commands/rbac.go
Normal file
374
cmd/argocd-util/commands/rbac.go
Normal file
@@ -0,0 +1,374 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
"github.com/argoproj/argo-cd/server/rbacpolicy"
|
||||
"github.com/argoproj/argo-cd/util/assets"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/rbac"
|
||||
)
|
||||
|
||||
// Provide a mapping of short-hand resource names to their RBAC counterparts
|
||||
var resourceMap map[string]string = map[string]string{
|
||||
"account": rbacpolicy.ResourceAccounts,
|
||||
"app": rbacpolicy.ResourceApplications,
|
||||
"apps": rbacpolicy.ResourceApplications,
|
||||
"application": rbacpolicy.ResourceApplications,
|
||||
"cert": rbacpolicy.ResourceCertificates,
|
||||
"certs": rbacpolicy.ResourceCertificates,
|
||||
"certificate": rbacpolicy.ResourceCertificates,
|
||||
"cluster": rbacpolicy.ResourceClusters,
|
||||
"gpgkey": rbacpolicy.ResourceGPGKeys,
|
||||
"key": rbacpolicy.ResourceGPGKeys,
|
||||
"proj": rbacpolicy.ResourceProjects,
|
||||
"projs": rbacpolicy.ResourceProjects,
|
||||
"project": rbacpolicy.ResourceProjects,
|
||||
"repo": rbacpolicy.ResourceRepositories,
|
||||
"repos": rbacpolicy.ResourceRepositories,
|
||||
"repository": rbacpolicy.ResourceRepositories,
|
||||
}
|
||||
|
||||
// List of allowed RBAC resources
|
||||
var validRBACResources map[string]bool = map[string]bool{
|
||||
rbacpolicy.ResourceAccounts: true,
|
||||
rbacpolicy.ResourceApplications: true,
|
||||
rbacpolicy.ResourceCertificates: true,
|
||||
rbacpolicy.ResourceClusters: true,
|
||||
rbacpolicy.ResourceGPGKeys: true,
|
||||
rbacpolicy.ResourceProjects: true,
|
||||
rbacpolicy.ResourceRepositories: true,
|
||||
}
|
||||
|
||||
// List of allowed RBAC actions
|
||||
var validRBACActions map[string]bool = map[string]bool{
|
||||
rbacpolicy.ActionAction: true,
|
||||
rbacpolicy.ActionCreate: true,
|
||||
rbacpolicy.ActionDelete: true,
|
||||
rbacpolicy.ActionGet: true,
|
||||
rbacpolicy.ActionOverride: true,
|
||||
rbacpolicy.ActionSync: true,
|
||||
rbacpolicy.ActionUpdate: true,
|
||||
}
|
||||
|
||||
// NewRBACCommand is the command for 'rbac'
|
||||
func NewRBACCommand() *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "rbac",
|
||||
Short: "Validate and test RBAC configuration",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
c.HelpFunc()(c, args)
|
||||
},
|
||||
}
|
||||
command.AddCommand(NewRBACCanCommand())
|
||||
command.AddCommand(NewRBACValidateCommand())
|
||||
return command
|
||||
}
|
||||
|
||||
// NewRBACCanRoleCommand is the command for 'rbac can-role'
|
||||
func NewRBACCanCommand() *cobra.Command {
|
||||
var (
|
||||
policyFile string
|
||||
defaultRole string
|
||||
useBuiltin bool
|
||||
strict bool
|
||||
quiet bool
|
||||
subject string
|
||||
action string
|
||||
resource string
|
||||
subResource string
|
||||
clientConfig clientcmd.ClientConfig
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "can ROLE/SUBJECT ACTION RESOURCE [SUB-RESOURCE]",
|
||||
Short: "Check RBAC permissions for a role or subject",
|
||||
Long: `
|
||||
Check whether a given role or subject has appropriate RBAC permissions to do
|
||||
something.
|
||||
`,
|
||||
Example: `
|
||||
# Check whether role some:role has permissions to create an application in the
|
||||
# 'default' project, using a local policy.csv file
|
||||
argocd-util rbac can some:role create application 'default/app' --policy-file policy.csv
|
||||
|
||||
# Policy file can also be K8s config map with data keys like argocd-rbac-cm,
|
||||
# i.e. 'policy.csv' and (optionally) 'policy.default'
|
||||
argocd-util rbac can some:role create application 'default/app' --policy-file argocd-rbac-cm.yaml
|
||||
|
||||
# If --policy-file is not given, the ConfigMap 'argocd-rbac-cm' from K8s is
|
||||
# used. You need to specify the argocd namespace, and make sure that your
|
||||
# current Kubernetes context is pointing to the cluster Argo CD is running in
|
||||
argocd-util rbac can some:role create application 'default/app' --namespace argocd
|
||||
|
||||
# You can override a possibly configured default role
|
||||
argocd-util rbac can someuser create application 'default/app' --default-role role:readonly
|
||||
|
||||
`,
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if len(args) < 3 || len(args) > 4 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
subject = args[0]
|
||||
action = args[1]
|
||||
resource = args[2]
|
||||
if len(args) > 3 {
|
||||
subResource = args[3]
|
||||
}
|
||||
|
||||
userPolicy := ""
|
||||
builtinPolicy := ""
|
||||
|
||||
var newDefaultRole string
|
||||
|
||||
namespace, nsOverride, err := clientConfig.Namespace()
|
||||
if err != nil {
|
||||
log.Fatalf("could not create k8s client: %v", err)
|
||||
}
|
||||
|
||||
// Exactly one of --namespace or --policy-file must be given.
|
||||
if (!nsOverride && policyFile == "") || (nsOverride && policyFile != "") {
|
||||
c.HelpFunc()(c, args)
|
||||
log.Fatalf("please provide exactly one of --policy-file or --namespace")
|
||||
}
|
||||
|
||||
restConfig, err := clientConfig.ClientConfig()
|
||||
if err != nil {
|
||||
log.Fatalf("could not create k8s client: %v", err)
|
||||
}
|
||||
realClientset, err := kubernetes.NewForConfig(restConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("could not create k8s client: %v", err)
|
||||
}
|
||||
|
||||
userPolicy, newDefaultRole = getPolicy(policyFile, realClientset, namespace)
|
||||
|
||||
// Use built-in policy as augmentation if requested
|
||||
if useBuiltin {
|
||||
builtinPolicy = assets.BuiltinPolicyCSV
|
||||
}
|
||||
|
||||
// If no explicit default role was given, but we have one defined from
|
||||
// a policy, use this to check for enforce.
|
||||
if newDefaultRole != "" && defaultRole == "" {
|
||||
defaultRole = newDefaultRole
|
||||
}
|
||||
|
||||
res := checkPolicy(subject, action, resource, subResource, builtinPolicy, userPolicy, defaultRole, strict)
|
||||
if res {
|
||||
if !quiet {
|
||||
fmt.Println("Yes")
|
||||
}
|
||||
os.Exit(0)
|
||||
} else {
|
||||
if !quiet {
|
||||
fmt.Println("No")
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
clientConfig = cli.AddKubectlFlagsToCmd(command)
|
||||
command.Flags().StringVar(&policyFile, "policy-file", "", "path to the policy file to use")
|
||||
command.Flags().StringVar(&defaultRole, "default-role", "", "name of the default role to use")
|
||||
command.Flags().BoolVar(&useBuiltin, "use-builtin-policy", true, "whether to also use builtin-policy")
|
||||
command.Flags().BoolVar(&strict, "strict", true, "whether to perform strict check on action and resource names")
|
||||
command.Flags().BoolVarP(&quiet, "quiet", "q", false, "quiet mode - do not print results to stdout")
|
||||
return command
|
||||
}
|
||||
|
||||
// NewRBACValidateCommand returns a new rbac validate command
|
||||
func NewRBACValidateCommand() *cobra.Command {
|
||||
var (
|
||||
policyFile string
|
||||
)
|
||||
|
||||
var command = &cobra.Command{
|
||||
Use: "validate --policy-file=POLICYFILE",
|
||||
Short: "Validate RBAC policy",
|
||||
Long: `
|
||||
Validates an RBAC policy for being syntactically correct. The policy must be
|
||||
a local file, and in either CSV or K8s ConfigMap format.
|
||||
`,
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if policyFile == "" {
|
||||
c.HelpFunc()(c, args)
|
||||
log.Fatalf("Please specify policy to validate using --policy-file")
|
||||
}
|
||||
userPolicy, _ := getPolicy(policyFile, nil, "")
|
||||
if userPolicy != "" {
|
||||
if err := rbac.ValidatePolicy(userPolicy); err == nil {
|
||||
fmt.Printf("Policy is valid.\n")
|
||||
os.Exit(0)
|
||||
} else {
|
||||
fmt.Printf("Policy is invalid: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
command.Flags().StringVar(&policyFile, "policy-file", "", "path to the policy file to use")
|
||||
return command
|
||||
}
|
||||
|
||||
// Load user policy file if requested or use Kubernetes client to get the
|
||||
// appropriate ConfigMap from the current context
|
||||
func getPolicy(policyFile string, kubeClient kubernetes.Interface, namespace string) (userPolicy string, defaultRole string) {
|
||||
var err error
|
||||
if policyFile != "" {
|
||||
// load from file
|
||||
userPolicy, defaultRole, err = getPolicyFromFile(policyFile)
|
||||
if err != nil {
|
||||
log.Fatalf("could not read policy file: %v", err)
|
||||
}
|
||||
} else {
|
||||
cm, err := getPolicyConfigMap(kubeClient, namespace)
|
||||
if err != nil {
|
||||
log.Fatalf("could not get configmap: %v", err)
|
||||
}
|
||||
userPolicy, defaultRole = getPolicyFromConfigMap(cm)
|
||||
}
|
||||
|
||||
return userPolicy, defaultRole
|
||||
}
|
||||
|
||||
// getPolicyFromFile loads a RBAC policy from given path
|
||||
func getPolicyFromFile(policyFile string) (string, string, error) {
|
||||
var (
|
||||
userPolicy string
|
||||
defaultRole string
|
||||
)
|
||||
|
||||
upol, err := ioutil.ReadFile(policyFile)
|
||||
if err != nil {
|
||||
log.Fatalf("error opening policy file: %v", err)
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// Try to unmarshal the input file as ConfigMap first. If it succeeds, we
|
||||
// assume config map input. Otherwise, we treat it as
|
||||
var upolCM *corev1.ConfigMap
|
||||
err = yaml.Unmarshal(upol, &upolCM)
|
||||
if err != nil {
|
||||
userPolicy = string(upol)
|
||||
} else {
|
||||
userPolicy, defaultRole = getPolicyFromConfigMap(upolCM)
|
||||
}
|
||||
|
||||
return userPolicy, defaultRole, nil
|
||||
}
|
||||
|
||||
// Retrieve policy information from a ConfigMap
|
||||
func getPolicyFromConfigMap(cm *corev1.ConfigMap) (string, string) {
|
||||
var (
|
||||
userPolicy string
|
||||
defaultRole string
|
||||
ok bool
|
||||
)
|
||||
userPolicy, ok = cm.Data[rbac.ConfigMapPolicyCSVKey]
|
||||
if !ok {
|
||||
userPolicy = ""
|
||||
}
|
||||
if defaultRole == "" {
|
||||
defaultRole, ok = cm.Data[rbac.ConfigMapPolicyDefaultKey]
|
||||
if !ok {
|
||||
defaultRole = ""
|
||||
}
|
||||
}
|
||||
|
||||
return userPolicy, defaultRole
|
||||
}
|
||||
|
||||
// getPolicyConfigMap fetches the RBAC config map from K8s cluster
|
||||
func getPolicyConfigMap(client kubernetes.Interface, namespace string) (*corev1.ConfigMap, error) {
|
||||
cm, err := client.CoreV1().ConfigMaps(namespace).Get(context.Background(), common.ArgoCDRBACConfigMapName, v1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cm, nil
|
||||
}
|
||||
|
||||
// checkPolicy checks whether given subject is allowed to execute specified
|
||||
// action against specified resource
|
||||
func checkPolicy(subject, action, resource, subResource, builtinPolicy, userPolicy, defaultRole string, strict bool) bool {
|
||||
enf := rbac.NewEnforcer(nil, "argocd", "argocd-rbac-cm", nil)
|
||||
enf.SetDefaultRole(defaultRole)
|
||||
if builtinPolicy != "" {
|
||||
if err := enf.SetBuiltinPolicy(builtinPolicy); err != nil {
|
||||
log.Fatalf("could not set built-in policy: %v", err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
if userPolicy != "" {
|
||||
if err := rbac.ValidatePolicy(userPolicy); err != nil {
|
||||
log.Fatalf("invalid user policy: %v", err)
|
||||
return false
|
||||
}
|
||||
if err := enf.SetUserPolicy(userPolicy); err != nil {
|
||||
log.Fatalf("could not set user policy: %v", err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// User could have used a mutation of the resource name (i.e. 'cert' for
|
||||
// 'certificate') - let's resolve it to the valid resource.
|
||||
realResource := resolveRBACResourceName(resource)
|
||||
|
||||
// If in strict mode, validate that given RBAC resource and action are
|
||||
// actually valid tokens.
|
||||
if strict {
|
||||
if !isValidRBACResource(realResource) {
|
||||
log.Fatalf("error in RBAC request: '%s' is not a valid resource name", realResource)
|
||||
}
|
||||
if !isValidRBACAction(action) {
|
||||
log.Fatalf("error in RBAC request: '%s' is not a valid action name", action)
|
||||
}
|
||||
}
|
||||
|
||||
// Application resources have a special notation - for simplicity's sake,
|
||||
// if user gives no sub-resource (or specifies simple '*'), we construct
|
||||
// the required notation by setting subresource to '*/*'.
|
||||
if realResource == rbacpolicy.ResourceApplications {
|
||||
if subResource == "*" || subResource == "" {
|
||||
subResource = "*/*"
|
||||
}
|
||||
}
|
||||
|
||||
return enf.Enforce(subject, realResource, action, subResource)
|
||||
}
|
||||
|
||||
// resolveRBACResourceName resolves a user supplied value to a valid RBAC
|
||||
// resource name. If no mapping is found, returns the value verbatim.
|
||||
func resolveRBACResourceName(name string) string {
|
||||
if res, ok := resourceMap[name]; ok {
|
||||
return res
|
||||
} else {
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
// isValidRBACAction checks whether a given action is a valid RBAC action
|
||||
func isValidRBACAction(action string) bool {
|
||||
_, ok := validRBACActions[action]
|
||||
return ok
|
||||
}
|
||||
|
||||
// isValidRBACResource checks whether a given resource is a valid RBAC resource
|
||||
func isValidRBACResource(resource string) bool {
|
||||
_, ok := validRBACResources[resource]
|
||||
return ok
|
||||
}
|
||||
91
cmd/argocd-util/commands/rbac_test.go
Normal file
91
cmd/argocd-util/commands/rbac_test.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
|
||||
"github.com/argoproj/argo-cd/util/assets"
|
||||
)
|
||||
|
||||
func Test_isValidRBACAction(t *testing.T) {
|
||||
for k := range validRBACActions {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
ok := isValidRBACAction(k)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
}
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
ok := isValidRBACAction("invalid")
|
||||
assert.False(t, ok)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_isValidRBACResource(t *testing.T) {
|
||||
for k := range validRBACResources {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
ok := isValidRBACResource(k)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
}
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
ok := isValidRBACResource("invalid")
|
||||
assert.False(t, ok)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_PolicyFromCSV(t *testing.T) {
|
||||
uPol, dRole := getPolicy("testdata/rbac/policy.csv", nil, "")
|
||||
require.NotEmpty(t, uPol)
|
||||
require.Empty(t, dRole)
|
||||
}
|
||||
|
||||
func Test_PolicyFromYAML(t *testing.T) {
|
||||
uPol, dRole := getPolicy("testdata/rbac/argocd-rbac-cm.yaml", nil, "")
|
||||
require.NotEmpty(t, uPol)
|
||||
require.Equal(t, "role:unknown", dRole)
|
||||
}
|
||||
|
||||
func Test_PolicyFromK8s(t *testing.T) {
|
||||
data, err := ioutil.ReadFile("testdata/rbac/policy.csv")
|
||||
require.NoError(t, err)
|
||||
kubeclientset := fake.NewSimpleClientset(&v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "argocd-rbac-cm",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"policy.csv": string(data),
|
||||
"policy.default": "role:unknown",
|
||||
},
|
||||
})
|
||||
uPol, dRole := getPolicy("", kubeclientset, "argocd")
|
||||
require.NotEmpty(t, uPol)
|
||||
require.Equal(t, "role:unknown", dRole)
|
||||
|
||||
t.Run("get applications", func(t *testing.T) {
|
||||
ok := checkPolicy("role:user", "get", "applications", "*/*", assets.BuiltinPolicyCSV, uPol, dRole, true)
|
||||
require.True(t, ok)
|
||||
})
|
||||
t.Run("get clusters", func(t *testing.T) {
|
||||
ok := checkPolicy("role:user", "get", "clusters", "*", assets.BuiltinPolicyCSV, uPol, dRole, true)
|
||||
require.True(t, ok)
|
||||
})
|
||||
t.Run("get certificates", func(t *testing.T) {
|
||||
ok := checkPolicy("role:user", "get", "certificates", "*", assets.BuiltinPolicyCSV, uPol, dRole, true)
|
||||
require.False(t, ok)
|
||||
})
|
||||
t.Run("get certificates by default role", func(t *testing.T) {
|
||||
ok := checkPolicy("role:user", "get", "certificates", "*", assets.BuiltinPolicyCSV, uPol, "role:readonly", true)
|
||||
require.True(t, ok)
|
||||
})
|
||||
t.Run("get certificates by default role without builtin policy", func(t *testing.T) {
|
||||
ok := checkPolicy("role:user", "get", "certificates", "*", "", uPol, "role:readonly", true)
|
||||
require.False(t, ok)
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -12,9 +12,7 @@ import (
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/diff"
|
||||
healthutil "github.com/argoproj/gitops-engine/pkg/health"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
"github.com/ghodss/yaml"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -29,6 +27,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/util/argo/normalizers"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/lua"
|
||||
"github.com/argoproj/argo-cd/util/settings"
|
||||
)
|
||||
@@ -423,7 +422,7 @@ argocd-util settings resource-overrides ignore-differences ./deploy.yaml --argoc
|
||||
}
|
||||
|
||||
_, _ = fmt.Printf("Following fields are ignored:\n\n")
|
||||
_ = diff.PrintDiff(res.GetName(), &res, normalizedRes)
|
||||
_ = cli.PrintDiff(res.GetName(), &res, normalizedRes)
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -538,7 +537,7 @@ argocd-util settings resource-overrides action run /tmp/deploy.yaml restart --ar
|
||||
}
|
||||
|
||||
_, _ = fmt.Printf("Following fields have been changed:\n\n")
|
||||
_ = diff.PrintDiff(res.GetName(), &res, modifiedRes)
|
||||
_ = cli.PrintDiff(res.GetName(), &res, modifiedRes)
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
utils "github.com/argoproj/argo-cd/util/io"
|
||||
"github.com/argoproj/argo-cd/util/settings"
|
||||
|
||||
utils "github.com/argoproj/gitops-engine/pkg/utils/io"
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
19
cmd/argocd-util/commands/testdata/rbac/argocd-rbac-cm.yaml
vendored
Normal file
19
cmd/argocd-util/commands/testdata/rbac/argocd-rbac-cm.yaml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
apiVersion: v1
|
||||
data:
|
||||
policy.csv: |
|
||||
p, role:user, clusters, get, *, allow
|
||||
p, role:user, clusters, get, https://kubernetes*, deny
|
||||
p, role:user, projects, get, *, allow
|
||||
p, role:user, applications, get, *, allow
|
||||
p, role:user, applications, create, */*, allow
|
||||
p, role:user, applications, delete, *, allow
|
||||
p, role:user, applications, delete, */guestbook, deny
|
||||
g, test, role:user
|
||||
policy.default: role:unknown
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-rbac-cm
|
||||
app.kubernetes.io/part-of: argocd
|
||||
name: argocd-rbac-cm
|
||||
namespace: argocd
|
||||
9
cmd/argocd-util/commands/testdata/rbac/policy.csv
vendored
Normal file
9
cmd/argocd-util/commands/testdata/rbac/policy.csv
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
p, role:user, clusters, get, *, allow
|
||||
p, role:user, clusters, get, https://kubernetes*, deny
|
||||
p, role:user, projects, get, *, allow
|
||||
p, role:user, applications, get, *, allow
|
||||
p, role:user, applications, create, */*, allow
|
||||
p, role:user, applications, delete, *, allow
|
||||
p, role:user, applications, delete, */guestbook, deny
|
||||
p, role:test, certificates, get, *, allow
|
||||
g, test, role:user
|
||||
|
787
cmd/argocd-util/commands/testdata/test_clusterrole.yaml
vendored
Normal file
787
cmd/argocd-util/commands/testdata/test_clusterrole.yaml
vendored
Normal file
@@ -0,0 +1,787 @@
|
||||
aggregationRule:
|
||||
clusterRoleSelectors:
|
||||
- matchLabels:
|
||||
rbac.authorization.k8s.io/aggregate-to-admin: "true"
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
kubernetes.io/bootstrapping: rbac-defaults
|
||||
name: admin
|
||||
rules:
|
||||
- apiGroups:
|
||||
- argoproj.io
|
||||
resources:
|
||||
- workflows
|
||||
- workflows/finalizers
|
||||
- workflowtemplates
|
||||
- workflowtemplates/finalizers
|
||||
- cronworkflows
|
||||
- cronworkflows/finalizers
|
||||
- clusterworkflowtemplates
|
||||
- clusterworkflowtemplates/finalizers
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- deletecollection
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- argoproj.io
|
||||
resources:
|
||||
- gateways
|
||||
- gateways/finalizers
|
||||
- sensors
|
||||
- sensors/finalizers
|
||||
- eventsources
|
||||
- eventsources/finalizers
|
||||
- eventbuses
|
||||
- eventbuses/finalizers
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- deletecollection
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- argoproj.io
|
||||
resources:
|
||||
- rollouts
|
||||
- rollouts/scale
|
||||
- experiments
|
||||
- analysistemplates
|
||||
- clusteranalysistemplates
|
||||
- analysisruns
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- deletecollection
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- metrics.k8s.io
|
||||
resources:
|
||||
- pods
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- iammanager.keikoproj.io
|
||||
resources:
|
||||
- iamroles
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods/attach
|
||||
- pods/exec
|
||||
- pods/portforward
|
||||
- pods/proxy
|
||||
- secrets
|
||||
- services/proxy
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- serviceaccounts
|
||||
verbs:
|
||||
- impersonate
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods
|
||||
- pods/attach
|
||||
- pods/exec
|
||||
- pods/portforward
|
||||
- pods/proxy
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- deletecollection
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
- endpoints
|
||||
- persistentvolumeclaims
|
||||
- replicationcontrollers
|
||||
- replicationcontrollers/scale
|
||||
- secrets
|
||||
- serviceaccounts
|
||||
- services
|
||||
- services/proxy
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- deletecollection
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- daemonsets
|
||||
- deployments
|
||||
- deployments/rollback
|
||||
- deployments/scale
|
||||
- replicasets
|
||||
- replicasets/scale
|
||||
- statefulsets
|
||||
- statefulsets/scale
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- deletecollection
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- autoscaling
|
||||
resources:
|
||||
- horizontalpodautoscalers
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- deletecollection
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- cronjobs
|
||||
- jobs
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- deletecollection
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- daemonsets
|
||||
- deployments
|
||||
- deployments/rollback
|
||||
- deployments/scale
|
||||
- ingresses
|
||||
- networkpolicies
|
||||
- replicasets
|
||||
- replicasets/scale
|
||||
- replicationcontrollers/scale
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- deletecollection
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- policy
|
||||
resources:
|
||||
- poddisruptionbudgets
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- deletecollection
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- networkpolicies
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- deletecollection
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- ingresses
|
||||
verbs:
|
||||
- create
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- ingresses
|
||||
verbs:
|
||||
- delete
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- ingresses
|
||||
verbs:
|
||||
- deletecollection
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- ingresses
|
||||
verbs:
|
||||
- patch
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- ingresses
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- argoproj.io
|
||||
resources:
|
||||
- workflows
|
||||
- workflows/finalizers
|
||||
- workflowtemplates
|
||||
- workflowtemplates/finalizers
|
||||
- cronworkflows
|
||||
- cronworkflows/finalizers
|
||||
- clusterworkflowtemplates
|
||||
- clusterworkflowtemplates/finalizers
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- argoproj.io
|
||||
resources:
|
||||
- gateways
|
||||
- gateways/finalizers
|
||||
- sensors
|
||||
- sensors/finalizers
|
||||
- eventsources
|
||||
- eventsources/finalizers
|
||||
- eventbuses
|
||||
- eventbuses/finalizers
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- argoproj.io
|
||||
resources:
|
||||
- rollouts
|
||||
- rollouts/scale
|
||||
- experiments
|
||||
- analysistemplates
|
||||
- clusteranalysistemplates
|
||||
- analysisruns
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resourceNames:
|
||||
- prometheus-k8s-prometheus-1
|
||||
- prometheus-k8s-prometheus-0
|
||||
resources:
|
||||
- pods/portforward
|
||||
verbs:
|
||||
- create
|
||||
- apiGroups:
|
||||
- networking.istio.io
|
||||
resources:
|
||||
- virtualservices
|
||||
- destinationrules
|
||||
- serviceentries
|
||||
- envoyfilters
|
||||
- gateways
|
||||
- sidecars
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
- endpoints
|
||||
- persistentvolumeclaims
|
||||
- pods
|
||||
- replicationcontrollers
|
||||
- replicationcontrollers/scale
|
||||
- serviceaccounts
|
||||
- services
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- bindings
|
||||
- events
|
||||
- limitranges
|
||||
- namespaces/status
|
||||
- pods/log
|
||||
- pods/status
|
||||
- replicationcontrollers/status
|
||||
- resourcequotas
|
||||
- resourcequotas/status
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- namespaces
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- daemonsets
|
||||
- deployments
|
||||
- deployments/scale
|
||||
- replicasets
|
||||
- replicasets/scale
|
||||
- statefulsets
|
||||
- statefulsets/scale
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- autoscaling
|
||||
resources:
|
||||
- horizontalpodautoscalers
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- cronjobs
|
||||
- jobs
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- daemonsets
|
||||
- deployments
|
||||
- deployments/scale
|
||||
- ingresses
|
||||
- networkpolicies
|
||||
- replicasets
|
||||
- replicasets/scale
|
||||
- replicationcontrollers/scale
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- policy
|
||||
resources:
|
||||
- poddisruptionbudgets
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- networkpolicies
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- controllerrevisions
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- controllerrevisions
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- controllerrevisions
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- ingresses
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- ingresses
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- ingresses
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- persistentvolumeclaims/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- persistentvolumeclaims/status
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- persistentvolumeclaims/status
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- services/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- services/status
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- services/status
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- daemonsets/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- daemonsets/status
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- daemonsets/status
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- deployments/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- deployments/status
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- deployments/status
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- replicasets/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- replicasets/status
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- replicasets/status
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- statefulsets/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- statefulsets/status
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- statefulsets/status
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- autoscaling
|
||||
resources:
|
||||
- horizontalpodautoscalers/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- autoscaling
|
||||
resources:
|
||||
- horizontalpodautoscalers/status
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- autoscaling
|
||||
resources:
|
||||
- horizontalpodautoscalers/status
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- cronjobs/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- cronjobs/status
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- cronjobs/status
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- jobs/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- jobs/status
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- jobs/status
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- daemonsets/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- daemonsets/status
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- daemonsets/status
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- deployments/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- deployments/status
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- deployments/status
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- ingresses/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- ingresses/status
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- ingresses/status
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- replicasets/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- replicasets/status
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- replicasets/status
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- policy
|
||||
resources:
|
||||
- poddisruptionbudgets/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- policy
|
||||
resources:
|
||||
- poddisruptionbudgets/status
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- policy
|
||||
resources:
|
||||
- poddisruptionbudgets/status
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- ingresses/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- ingresses/status
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- ingresses/status
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- events
|
||||
verbs:
|
||||
- create
|
||||
- patch
|
||||
- apiGroups:
|
||||
- extensions
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- ingresses/status
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- monitoring.coreos.com
|
||||
resources:
|
||||
- prometheusrules
|
||||
verbs:
|
||||
- get
|
||||
- watch
|
||||
- list
|
||||
- update
|
||||
- delete
|
||||
- create
|
||||
- apiGroups:
|
||||
- hpa.orkaproj.io
|
||||
resources:
|
||||
- hpaalgoes
|
||||
verbs:
|
||||
- get
|
||||
- watch
|
||||
- list
|
||||
- update
|
||||
- delete
|
||||
- create
|
||||
- apiGroups:
|
||||
- networking.istio.io
|
||||
resources:
|
||||
- virtualservices
|
||||
- destinationrules
|
||||
- serviceentries
|
||||
- envoyfilters
|
||||
- gateways
|
||||
- sidecars
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- create
|
||||
- update
|
||||
- delete
|
||||
- patch
|
||||
- watch
|
||||
- apiGroups:
|
||||
- authorization.k8s.io
|
||||
resources:
|
||||
- localsubjectaccessreviews
|
||||
verbs:
|
||||
- create
|
||||
- apiGroups:
|
||||
- rbac.authorization.k8s.io
|
||||
resources:
|
||||
- rolebindings
|
||||
- roles
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- deletecollection
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
@@ -10,8 +10,6 @@ import (
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/io"
|
||||
timeutil "github.com/argoproj/pkg/time"
|
||||
"github.com/ghodss/yaml"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -23,6 +21,8 @@ import (
|
||||
"github.com/argoproj/argo-cd/pkg/apiclient/session"
|
||||
"github.com/argoproj/argo-cd/server/rbacpolicy"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/io"
|
||||
"github.com/argoproj/argo-cd/util/localconfig"
|
||||
sessionutil "github.com/argoproj/argo-cd/util/session"
|
||||
)
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
@@ -15,39 +12,43 @@ import (
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/diff"
|
||||
"github.com/argoproj/gitops-engine/pkg/health"
|
||||
"github.com/argoproj/gitops-engine/pkg/sync/hook"
|
||||
"github.com/argoproj/gitops-engine/pkg/sync/ignore"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
argoio "github.com/argoproj/gitops-engine/pkg/utils/io"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/mattn/go-isatty"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
cmdutil "github.com/argoproj/argo-cd/cmd/util"
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
"github.com/argoproj/argo-cd/controller"
|
||||
"github.com/argoproj/argo-cd/pkg/apiclient"
|
||||
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
|
||||
"github.com/argoproj/argo-cd/pkg/apiclient/application"
|
||||
applicationpkg "github.com/argoproj/argo-cd/pkg/apiclient/application"
|
||||
clusterpkg "github.com/argoproj/argo-cd/pkg/apiclient/cluster"
|
||||
projectpkg "github.com/argoproj/argo-cd/pkg/apiclient/project"
|
||||
"github.com/argoproj/argo-cd/pkg/apiclient/settings"
|
||||
settingspkg "github.com/argoproj/argo-cd/pkg/apiclient/settings"
|
||||
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
repoapiclient "github.com/argoproj/argo-cd/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/reposerver/repository"
|
||||
"github.com/argoproj/argo-cd/util/argo"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/config"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/git"
|
||||
argoio "github.com/argoproj/argo-cd/util/io"
|
||||
argokube "github.com/argoproj/argo-cd/util/kube"
|
||||
"github.com/argoproj/argo-cd/util/templates"
|
||||
"github.com/argoproj/argo-cd/util/text/label"
|
||||
@@ -94,13 +95,14 @@ func NewApplicationCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
|
||||
command.AddCommand(NewApplicationPatchResourceCommand(clientOpts))
|
||||
command.AddCommand(NewApplicationResourceActionsCommand(clientOpts))
|
||||
command.AddCommand(NewApplicationListResourcesCommand(clientOpts))
|
||||
command.AddCommand(NewApplicationLogsCommand(clientOpts))
|
||||
return command
|
||||
}
|
||||
|
||||
// NewApplicationCreateCommand returns a new instance of an `argocd app create` command
|
||||
func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
appOpts appOptions
|
||||
appOpts cmdutil.AppOptions
|
||||
fileURL string
|
||||
appName string
|
||||
upsert bool
|
||||
@@ -123,53 +125,17 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
|
||||
argocd app create nginx-ingress --repo https://kubernetes-charts.storage.googleapis.com --helm-chart nginx-ingress --revision 1.24.3 --dest-namespace default --dest-server https://kubernetes.default.svc
|
||||
|
||||
# Create a Kustomize app
|
||||
argocd app create kustomize-guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path kustomize-guestbook --dest-namespace default --dest-server https://kubernetes.default.svc --kustomize-image gcr.io/heptio-images/ks-guestbook-demo=0.1
|
||||
argocd app create kustomize-guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path kustomize-guestbook --dest-namespace default --dest-server https://kubernetes.default.svc --kustomize-image gcr.io/heptio-images/ks-guestbook-demo:0.1
|
||||
|
||||
# Create a app using a custom tool:
|
||||
argocd app create ksane --repo https://github.com/argoproj/argocd-example-apps.git --path plugins/kasane --dest-namespace default --dest-server https://kubernetes.default.svc --config-management-plugin kasane
|
||||
`,
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
var app argoappv1.Application
|
||||
argocdClient := argocdclient.NewClientOrDie(clientOpts)
|
||||
if fileURL == "-" {
|
||||
// read stdin
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
err := config.UnmarshalReader(reader, &app)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to read manifest from stdin: %v", err)
|
||||
}
|
||||
} else if fileURL != "" {
|
||||
// read uri
|
||||
parsedURL, err := url.ParseRequestURI(fileURL)
|
||||
if err != nil || !(parsedURL.Scheme == "http" || parsedURL.Scheme == "https") {
|
||||
err = config.UnmarshalLocalFile(fileURL, &app)
|
||||
} else {
|
||||
err = config.UnmarshalRemoteFile(fileURL, &app)
|
||||
}
|
||||
errors.CheckError(err)
|
||||
if len(args) == 1 && args[0] != app.Name {
|
||||
log.Fatalf("app name '%s' does not match app spec metadata.name '%s'", args[0], app.Name)
|
||||
}
|
||||
if appName != "" && appName != app.Name {
|
||||
log.Fatalf("--name argument '%s' does not match app spec metadata.name '%s'", appName, app.Name)
|
||||
}
|
||||
} else {
|
||||
// read arguments
|
||||
if len(args) == 1 {
|
||||
if appName != "" && appName != args[0] {
|
||||
log.Fatalf("--name argument '%s' does not match app name %s", appName, args[0])
|
||||
}
|
||||
appName = args[0]
|
||||
}
|
||||
app = argoappv1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: appName,
|
||||
},
|
||||
}
|
||||
setAppSpecOptions(c.Flags(), &app.Spec, &appOpts)
|
||||
setParameterOverrides(&app, appOpts.parameters)
|
||||
setLabels(&app, labels)
|
||||
}
|
||||
|
||||
app, err := cmdutil.ConstructApp(fileURL, appName, labels, args, appOpts, c.Flags())
|
||||
errors.CheckError(err)
|
||||
|
||||
if app.Name == "" {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
@@ -178,9 +144,9 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
|
||||
conn, appIf := argocdClient.NewApplicationClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
appCreateRequest := applicationpkg.ApplicationCreateRequest{
|
||||
Application: app,
|
||||
Application: *app,
|
||||
Upsert: &upsert,
|
||||
Validate: &appOpts.validate,
|
||||
Validate: &appOpts.Validate,
|
||||
}
|
||||
created, err := appIf.Create(context.Background(), &appCreateRequest)
|
||||
errors.CheckError(err)
|
||||
@@ -196,16 +162,10 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
addAppFlags(command, &appOpts)
|
||||
cmdutil.AddAppFlags(command, &appOpts)
|
||||
return command
|
||||
}
|
||||
|
||||
func setLabels(app *argoappv1.Application, labels []string) {
|
||||
mapLabels, err := label.Parse(labels)
|
||||
errors.CheckError(err)
|
||||
app.SetLabels(mapLabels)
|
||||
}
|
||||
|
||||
func getInfos(infos []string) []*argoappv1.Info {
|
||||
mapInfos, err := label.Parse(infos)
|
||||
errors.CheckError(err)
|
||||
@@ -304,6 +264,90 @@ func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
|
||||
return command
|
||||
}
|
||||
|
||||
// NewApplicationLogsCommand returns logs of application pods
|
||||
func NewApplicationLogsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
group string
|
||||
kind string
|
||||
namespace string
|
||||
resourceName string
|
||||
follow bool
|
||||
tail int64
|
||||
sinceSeconds int64
|
||||
untilTime string
|
||||
filter string
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "logs APPNAME",
|
||||
Short: "Get logs of application pods",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
acdClient := argocdclient.NewClientOrDie(clientOpts)
|
||||
conn, appIf := acdClient.NewApplicationClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
appName := args[0]
|
||||
|
||||
retry := true
|
||||
for retry {
|
||||
retry = false
|
||||
stream, err := appIf.PodLogs(context.Background(), &applicationpkg.ApplicationPodLogsQuery{
|
||||
Name: &appName,
|
||||
Group: &group,
|
||||
Namespace: namespace,
|
||||
Kind: &kind,
|
||||
ResourceName: &resourceName,
|
||||
Follow: follow,
|
||||
TailLines: tail,
|
||||
SinceSeconds: sinceSeconds,
|
||||
UntilTime: &untilTime,
|
||||
Filter: &filter,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("failed to get pod logs: %v", err)
|
||||
}
|
||||
for {
|
||||
msg, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
st, ok := status.FromError(err)
|
||||
if !ok {
|
||||
log.Fatalf("stream read failed: %v", err)
|
||||
}
|
||||
if st.Code() == codes.Unavailable && follow {
|
||||
retry = true
|
||||
sinceSeconds = 1
|
||||
break
|
||||
}
|
||||
log.Fatalf("stream read failed: %v", err)
|
||||
}
|
||||
if !msg.Last {
|
||||
fmt.Println(msg.Content)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} //Done with receive message
|
||||
} //Done with retry
|
||||
},
|
||||
}
|
||||
|
||||
command.Flags().StringVar(&group, "group", "", "Resource group")
|
||||
command.Flags().StringVar(&kind, "kind", "", "Resource kind")
|
||||
command.Flags().StringVar(&namespace, "namespace", "", "Resource namespace")
|
||||
command.Flags().StringVar(&resourceName, "name", "", "Resource name")
|
||||
command.Flags().BoolVar(&follow, "follow", false, "Specify if the logs should be streamed")
|
||||
command.Flags().Int64Var(&tail, "tail", 0, "The number of lines from the end of the logs to show")
|
||||
command.Flags().Int64Var(&sinceSeconds, "since-seconds", 0, "A relative time in seconds before the current time from which to show logs")
|
||||
command.Flags().StringVar(&untilTime, "until-time", "", "Show logs until this time")
|
||||
command.Flags().StringVar(&filter, "filter", "", "Show logs contain this string")
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
func printAppSummaryTable(app *argoappv1.Application, appURL string, windows *argoappv1.SyncWindows) {
|
||||
fmt.Printf(printOpFmtStr, "Name:", app.Name)
|
||||
fmt.Printf(printOpFmtStr, "Project:", app.Spec.GetProject())
|
||||
@@ -402,8 +446,8 @@ func printAppConditions(w io.Writer, app *argoappv1.Application) {
|
||||
}
|
||||
}
|
||||
|
||||
// appURL returns the URL of an application
|
||||
func appURL(acdClient argocdclient.Client, appName string) string {
|
||||
// appURLDefault returns the default URL of an application
|
||||
func appURLDefault(acdClient argocdclient.Client, appName string) string {
|
||||
var scheme string
|
||||
opts := acdClient.ClientOptions()
|
||||
server := opts.ServerAddr
|
||||
@@ -418,13 +462,26 @@ func appURL(acdClient argocdclient.Client, appName string) string {
|
||||
return fmt.Sprintf("%s://%s/applications/%s", scheme, server, appName)
|
||||
}
|
||||
|
||||
// appURL returns the URL of an application
|
||||
func appURL(acdClient argocdclient.Client, appName string) string {
|
||||
conn, settingsIf := acdClient.NewSettingsClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
argoSettings, err := settingsIf.Get(context.Background(), &settingspkg.SettingsQuery{})
|
||||
errors.CheckError(err)
|
||||
|
||||
if argoSettings.URL != "" {
|
||||
return fmt.Sprintf("%s/applications/%s", argoSettings.URL, appName)
|
||||
}
|
||||
return appURLDefault(acdClient, appName)
|
||||
}
|
||||
|
||||
func truncateString(str string, num int) string {
|
||||
bnoden := str
|
||||
if len(str) > num {
|
||||
if utf8.RuneCountInString(str) > num {
|
||||
if num > 3 {
|
||||
num -= 3
|
||||
}
|
||||
bnoden = str[0:num] + "..."
|
||||
bnoden = string([]rune(str)[0:num]) + "..."
|
||||
}
|
||||
return bnoden
|
||||
}
|
||||
@@ -453,7 +510,7 @@ func printParams(app *argoappv1.Application) {
|
||||
// NewApplicationSetCommand returns a new instance of an `argocd app set` command
|
||||
func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
appOpts appOptions
|
||||
appOpts cmdutil.AppOptions
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "set APPNAME",
|
||||
@@ -470,324 +527,25 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
|
||||
defer argoio.Close(conn)
|
||||
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{Name: &appName})
|
||||
errors.CheckError(err)
|
||||
visited := setAppSpecOptions(c.Flags(), &app.Spec, &appOpts)
|
||||
visited := cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts)
|
||||
if visited == 0 {
|
||||
log.Error("Please set at least one option to update")
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
setParameterOverrides(app, appOpts.parameters)
|
||||
setParameterOverrides(app, appOpts.Parameters)
|
||||
_, err = appIf.UpdateSpec(ctx, &applicationpkg.ApplicationUpdateSpecRequest{
|
||||
Name: &app.Name,
|
||||
Spec: app.Spec,
|
||||
Validate: &appOpts.validate,
|
||||
Validate: &appOpts.Validate,
|
||||
})
|
||||
errors.CheckError(err)
|
||||
},
|
||||
}
|
||||
addAppFlags(command, &appOpts)
|
||||
cmdutil.AddAppFlags(command, &appOpts)
|
||||
return command
|
||||
}
|
||||
|
||||
func setAppSpecOptions(flags *pflag.FlagSet, spec *argoappv1.ApplicationSpec, appOpts *appOptions) int {
|
||||
visited := 0
|
||||
flags.Visit(func(f *pflag.Flag) {
|
||||
visited++
|
||||
switch f.Name {
|
||||
case "repo":
|
||||
spec.Source.RepoURL = appOpts.repoURL
|
||||
case "path":
|
||||
spec.Source.Path = appOpts.appPath
|
||||
case "helm-chart":
|
||||
spec.Source.Chart = appOpts.chart
|
||||
case "env":
|
||||
setKsonnetOpt(&spec.Source, &appOpts.env)
|
||||
case "revision":
|
||||
spec.Source.TargetRevision = appOpts.revision
|
||||
case "revision-history-limit":
|
||||
i := int64(appOpts.revisionHistoryLimit)
|
||||
spec.RevisionHistoryLimit = &i
|
||||
case "values":
|
||||
setHelmOpt(&spec.Source, helmOpts{valueFiles: appOpts.valuesFiles})
|
||||
case "values-literal-file":
|
||||
var data []byte
|
||||
|
||||
// read uri
|
||||
parsedURL, err := url.ParseRequestURI(appOpts.values)
|
||||
if err != nil || !(parsedURL.Scheme == "http" || parsedURL.Scheme == "https") {
|
||||
data, err = ioutil.ReadFile(appOpts.values)
|
||||
} else {
|
||||
data, err = config.ReadRemoteFile(appOpts.values)
|
||||
}
|
||||
errors.CheckError(err)
|
||||
setHelmOpt(&spec.Source, helmOpts{values: string(data)})
|
||||
case "release-name":
|
||||
setHelmOpt(&spec.Source, helmOpts{releaseName: appOpts.releaseName})
|
||||
case "helm-set":
|
||||
setHelmOpt(&spec.Source, helmOpts{helmSets: appOpts.helmSets})
|
||||
case "helm-set-string":
|
||||
setHelmOpt(&spec.Source, helmOpts{helmSetStrings: appOpts.helmSetStrings})
|
||||
case "helm-set-file":
|
||||
setHelmOpt(&spec.Source, helmOpts{helmSetFiles: appOpts.helmSetFiles})
|
||||
case "directory-recurse":
|
||||
spec.Source.Directory = &argoappv1.ApplicationSourceDirectory{Recurse: appOpts.directoryRecurse}
|
||||
case "config-management-plugin":
|
||||
spec.Source.Plugin = &argoappv1.ApplicationSourcePlugin{Name: appOpts.configManagementPlugin}
|
||||
case "dest-name":
|
||||
spec.Destination.Name = appOpts.destName
|
||||
case "dest-server":
|
||||
spec.Destination.Server = appOpts.destServer
|
||||
case "dest-namespace":
|
||||
spec.Destination.Namespace = appOpts.destNamespace
|
||||
case "project":
|
||||
spec.Project = appOpts.project
|
||||
case "nameprefix":
|
||||
setKustomizeOpt(&spec.Source, kustomizeOpts{namePrefix: appOpts.namePrefix})
|
||||
case "namesuffix":
|
||||
setKustomizeOpt(&spec.Source, kustomizeOpts{nameSuffix: appOpts.nameSuffix})
|
||||
case "kustomize-image":
|
||||
setKustomizeOpt(&spec.Source, kustomizeOpts{images: appOpts.kustomizeImages})
|
||||
case "kustomize-version":
|
||||
setKustomizeOpt(&spec.Source, kustomizeOpts{version: appOpts.kustomizeVersion})
|
||||
case "jsonnet-tla-str":
|
||||
setJsonnetOpt(&spec.Source, appOpts.jsonnetTlaStr, false)
|
||||
case "jsonnet-tla-code":
|
||||
setJsonnetOpt(&spec.Source, appOpts.jsonnetTlaCode, true)
|
||||
case "jsonnet-ext-var-str":
|
||||
setJsonnetOptExtVar(&spec.Source, appOpts.jsonnetExtVarStr, false)
|
||||
case "jsonnet-ext-var-code":
|
||||
setJsonnetOptExtVar(&spec.Source, appOpts.jsonnetExtVarCode, true)
|
||||
case "jsonnet-libs":
|
||||
setJsonnetOptLibs(&spec.Source, appOpts.jsonnetLibs)
|
||||
case "sync-policy":
|
||||
switch appOpts.syncPolicy {
|
||||
case "none":
|
||||
if spec.SyncPolicy != nil {
|
||||
spec.SyncPolicy.Automated = nil
|
||||
}
|
||||
if spec.SyncPolicy.IsZero() {
|
||||
spec.SyncPolicy = nil
|
||||
}
|
||||
case "automated", "automatic", "auto":
|
||||
if spec.SyncPolicy == nil {
|
||||
spec.SyncPolicy = &argoappv1.SyncPolicy{}
|
||||
}
|
||||
spec.SyncPolicy.Automated = &argoappv1.SyncPolicyAutomated{}
|
||||
default:
|
||||
log.Fatalf("Invalid sync-policy: %s", appOpts.syncPolicy)
|
||||
}
|
||||
case "sync-option":
|
||||
if spec.SyncPolicy == nil {
|
||||
spec.SyncPolicy = &argoappv1.SyncPolicy{}
|
||||
}
|
||||
for _, option := range appOpts.syncOptions {
|
||||
// `!` means remove the option
|
||||
if strings.HasPrefix(option, "!") {
|
||||
option = strings.TrimPrefix(option, "!")
|
||||
spec.SyncPolicy.SyncOptions = spec.SyncPolicy.SyncOptions.RemoveOption(option)
|
||||
} else {
|
||||
spec.SyncPolicy.SyncOptions = spec.SyncPolicy.SyncOptions.AddOption(option)
|
||||
}
|
||||
}
|
||||
if spec.SyncPolicy.IsZero() {
|
||||
spec.SyncPolicy = nil
|
||||
}
|
||||
}
|
||||
})
|
||||
if flags.Changed("auto-prune") {
|
||||
if spec.SyncPolicy == nil || spec.SyncPolicy.Automated == nil {
|
||||
log.Fatal("Cannot set --auto-prune: application not configured with automatic sync")
|
||||
}
|
||||
spec.SyncPolicy.Automated.Prune = appOpts.autoPrune
|
||||
}
|
||||
if flags.Changed("self-heal") {
|
||||
if spec.SyncPolicy == nil || spec.SyncPolicy.Automated == nil {
|
||||
log.Fatal("Cannot set --self-heal: application not configured with automatic sync")
|
||||
}
|
||||
spec.SyncPolicy.Automated.SelfHeal = appOpts.selfHeal
|
||||
}
|
||||
|
||||
return visited
|
||||
}
|
||||
|
||||
func setKsonnetOpt(src *argoappv1.ApplicationSource, env *string) {
|
||||
if src.Ksonnet == nil {
|
||||
src.Ksonnet = &argoappv1.ApplicationSourceKsonnet{}
|
||||
}
|
||||
if env != nil {
|
||||
src.Ksonnet.Environment = *env
|
||||
}
|
||||
if src.Ksonnet.IsZero() {
|
||||
src.Ksonnet = nil
|
||||
}
|
||||
}
|
||||
|
||||
type kustomizeOpts struct {
|
||||
namePrefix string
|
||||
nameSuffix string
|
||||
images []string
|
||||
version string
|
||||
}
|
||||
|
||||
func setKustomizeOpt(src *argoappv1.ApplicationSource, opts kustomizeOpts) {
|
||||
if src.Kustomize == nil {
|
||||
src.Kustomize = &argoappv1.ApplicationSourceKustomize{}
|
||||
}
|
||||
src.Kustomize.Version = opts.version
|
||||
src.Kustomize.NamePrefix = opts.namePrefix
|
||||
src.Kustomize.NameSuffix = opts.nameSuffix
|
||||
for _, image := range opts.images {
|
||||
src.Kustomize.MergeImage(argoappv1.KustomizeImage(image))
|
||||
}
|
||||
if src.Kustomize.IsZero() {
|
||||
src.Kustomize = nil
|
||||
}
|
||||
}
|
||||
|
||||
type helmOpts struct {
|
||||
valueFiles []string
|
||||
values string
|
||||
releaseName string
|
||||
helmSets []string
|
||||
helmSetStrings []string
|
||||
helmSetFiles []string
|
||||
}
|
||||
|
||||
func setHelmOpt(src *argoappv1.ApplicationSource, opts helmOpts) {
|
||||
if src.Helm == nil {
|
||||
src.Helm = &argoappv1.ApplicationSourceHelm{}
|
||||
}
|
||||
if len(opts.valueFiles) > 0 {
|
||||
src.Helm.ValueFiles = opts.valueFiles
|
||||
}
|
||||
if len(opts.values) > 0 {
|
||||
src.Helm.Values = opts.values
|
||||
}
|
||||
if opts.releaseName != "" {
|
||||
src.Helm.ReleaseName = opts.releaseName
|
||||
}
|
||||
for _, text := range opts.helmSets {
|
||||
p, err := argoappv1.NewHelmParameter(text, false)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
src.Helm.AddParameter(*p)
|
||||
}
|
||||
for _, text := range opts.helmSetStrings {
|
||||
p, err := argoappv1.NewHelmParameter(text, true)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
src.Helm.AddParameter(*p)
|
||||
}
|
||||
for _, text := range opts.helmSetFiles {
|
||||
p, err := argoappv1.NewHelmFileParameter(text)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
src.Helm.AddFileParameter(*p)
|
||||
}
|
||||
if src.Helm.IsZero() {
|
||||
src.Helm = nil
|
||||
}
|
||||
}
|
||||
|
||||
func setJsonnetOpt(src *argoappv1.ApplicationSource, tlaParameters []string, code bool) {
|
||||
if src.Directory == nil {
|
||||
src.Directory = &argoappv1.ApplicationSourceDirectory{}
|
||||
}
|
||||
for _, j := range tlaParameters {
|
||||
src.Directory.Jsonnet.TLAs = append(src.Directory.Jsonnet.TLAs, argoappv1.NewJsonnetVar(j, code))
|
||||
}
|
||||
}
|
||||
|
||||
func setJsonnetOptExtVar(src *argoappv1.ApplicationSource, jsonnetExtVar []string, code bool) {
|
||||
if src.Directory == nil {
|
||||
src.Directory = &argoappv1.ApplicationSourceDirectory{}
|
||||
}
|
||||
for _, j := range jsonnetExtVar {
|
||||
src.Directory.Jsonnet.ExtVars = append(src.Directory.Jsonnet.ExtVars, argoappv1.NewJsonnetVar(j, code))
|
||||
}
|
||||
}
|
||||
func setJsonnetOptLibs(src *argoappv1.ApplicationSource, libs []string) {
|
||||
if src.Directory == nil {
|
||||
src.Directory = &argoappv1.ApplicationSourceDirectory{}
|
||||
}
|
||||
src.Directory.Jsonnet.Libs = append(src.Directory.Jsonnet.Libs, libs...)
|
||||
}
|
||||
|
||||
type appOptions struct {
|
||||
repoURL string
|
||||
appPath string
|
||||
chart string
|
||||
env string
|
||||
revision string
|
||||
revisionHistoryLimit int
|
||||
destName string
|
||||
destServer string
|
||||
destNamespace string
|
||||
parameters []string
|
||||
valuesFiles []string
|
||||
values string
|
||||
releaseName string
|
||||
helmSets []string
|
||||
helmSetStrings []string
|
||||
helmSetFiles []string
|
||||
project string
|
||||
syncPolicy string
|
||||
syncOptions []string
|
||||
autoPrune bool
|
||||
selfHeal bool
|
||||
namePrefix string
|
||||
nameSuffix string
|
||||
directoryRecurse bool
|
||||
configManagementPlugin string
|
||||
jsonnetTlaStr []string
|
||||
jsonnetTlaCode []string
|
||||
jsonnetExtVarStr []string
|
||||
jsonnetExtVarCode []string
|
||||
jsonnetLibs []string
|
||||
kustomizeImages []string
|
||||
kustomizeVersion string
|
||||
validate bool
|
||||
}
|
||||
|
||||
func addAppFlags(command *cobra.Command, opts *appOptions) {
|
||||
command.Flags().StringVar(&opts.repoURL, "repo", "", "Repository URL, ignored if a file is set")
|
||||
command.Flags().StringVar(&opts.appPath, "path", "", "Path in repository to the app directory, ignored if a file is set")
|
||||
command.Flags().StringVar(&opts.chart, "helm-chart", "", "Helm Chart name")
|
||||
command.Flags().StringVar(&opts.env, "env", "", "Application environment to monitor")
|
||||
command.Flags().StringVar(&opts.revision, "revision", "", "The tracking source branch, tag, commit or Helm chart version the application will sync to")
|
||||
command.Flags().IntVar(&opts.revisionHistoryLimit, "revision-history-limit", common.RevisionHistoryLimit, "How many items to keep in revision history")
|
||||
command.Flags().StringVar(&opts.destServer, "dest-server", "", "K8s cluster URL (e.g. https://kubernetes.default.svc)")
|
||||
command.Flags().StringVar(&opts.destName, "dest-name", "", "K8s cluster Name (e.g. minikube)")
|
||||
command.Flags().StringVar(&opts.destNamespace, "dest-namespace", "", "K8s target namespace (overrides the namespace specified in the ksonnet app.yaml)")
|
||||
command.Flags().StringArrayVarP(&opts.parameters, "parameter", "p", []string{}, "set a parameter override (e.g. -p guestbook=image=example/guestbook:latest)")
|
||||
command.Flags().StringArrayVar(&opts.valuesFiles, "values", []string{}, "Helm values file(s) to use")
|
||||
command.Flags().StringVar(&opts.values, "values-literal-file", "", "Filename or URL to import as a literal Helm values block")
|
||||
command.Flags().StringVar(&opts.releaseName, "release-name", "", "Helm release-name")
|
||||
command.Flags().StringArrayVar(&opts.helmSets, "helm-set", []string{}, "Helm set values on the command line (can be repeated to set several values: --helm-set key1=val1 --helm-set key2=val2)")
|
||||
command.Flags().StringArrayVar(&opts.helmSetStrings, "helm-set-string", []string{}, "Helm set STRING values on the command line (can be repeated to set several values: --helm-set-string key1=val1 --helm-set-string key2=val2)")
|
||||
command.Flags().StringArrayVar(&opts.helmSetFiles, "helm-set-file", []string{}, "Helm set values from respective files specified via the command line (can be repeated to set several values: --helm-set-file key1=path1 --helm-set-file key2=path2)")
|
||||
command.Flags().StringVar(&opts.project, "project", "", "Application project name")
|
||||
command.Flags().StringVar(&opts.syncPolicy, "sync-policy", "", "Set the sync policy (one of: none, automated (aliases of automated: auto, automatic))")
|
||||
command.Flags().StringArrayVar(&opts.syncOptions, "sync-option", []string{}, "Add or remove a sync options, e.g add `Prune=false`. Remove using `!` prefix, e.g. `!Prune=false`")
|
||||
command.Flags().BoolVar(&opts.autoPrune, "auto-prune", false, "Set automatic pruning when sync is automated")
|
||||
command.Flags().BoolVar(&opts.selfHeal, "self-heal", false, "Set self healing when sync is automated")
|
||||
command.Flags().StringVar(&opts.namePrefix, "nameprefix", "", "Kustomize nameprefix")
|
||||
command.Flags().StringVar(&opts.nameSuffix, "namesuffix", "", "Kustomize namesuffix")
|
||||
command.Flags().StringVar(&opts.kustomizeVersion, "kustomize-version", "", "Kustomize version")
|
||||
command.Flags().BoolVar(&opts.directoryRecurse, "directory-recurse", false, "Recurse directory")
|
||||
command.Flags().StringVar(&opts.configManagementPlugin, "config-management-plugin", "", "Config management plugin name")
|
||||
command.Flags().StringArrayVar(&opts.jsonnetTlaStr, "jsonnet-tla-str", []string{}, "Jsonnet top level string arguments")
|
||||
command.Flags().StringArrayVar(&opts.jsonnetTlaCode, "jsonnet-tla-code", []string{}, "Jsonnet top level code arguments")
|
||||
command.Flags().StringArrayVar(&opts.jsonnetExtVarStr, "jsonnet-ext-var-str", []string{}, "Jsonnet string ext var")
|
||||
command.Flags().StringArrayVar(&opts.jsonnetExtVarCode, "jsonnet-ext-var-code", []string{}, "Jsonnet ext var")
|
||||
command.Flags().StringArrayVar(&opts.jsonnetLibs, "jsonnet-libs", []string{}, "Additional jsonnet libs (prefixed by repoRoot)")
|
||||
command.Flags().StringArrayVar(&opts.kustomizeImages, "kustomize-image", []string{}, "Kustomize images (e.g. --kustomize-image node:8.15.0 --kustomize-image mysql=mariadb,alpine@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d)")
|
||||
command.Flags().BoolVar(&opts.validate, "validate", true, "Validation of repo and cluster")
|
||||
}
|
||||
|
||||
// NewApplicationUnsetCommand returns a new instance of an `argocd app unset` command
|
||||
func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
@@ -798,7 +556,7 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
|
||||
namePrefix bool
|
||||
kustomizeVersion bool
|
||||
kustomizeImages []string
|
||||
appOpts appOptions
|
||||
appOpts cmdutil.AppOptions
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "unset APPNAME parameters",
|
||||
@@ -908,11 +666,11 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
|
||||
}
|
||||
}
|
||||
|
||||
setAppSpecOptions(c.Flags(), &app.Spec, &appOpts)
|
||||
cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts)
|
||||
_, err = appIf.UpdateSpec(context.Background(), &applicationpkg.ApplicationUpdateSpecRequest{
|
||||
Name: &app.Name,
|
||||
Spec: app.Spec,
|
||||
Validate: &appOpts.validate,
|
||||
Validate: &appOpts.Validate,
|
||||
})
|
||||
errors.CheckError(err)
|
||||
},
|
||||
@@ -972,7 +730,7 @@ func getLocalObjectsString(app *argoappv1.Application, local, localRepoRoot, app
|
||||
res, err := repository.GenerateManifests(local, localRepoRoot, app.Spec.Source.TargetRevision, &repoapiclient.ManifestRequest{
|
||||
Repo: &argoappv1.Repository{Repo: app.Spec.Source.RepoURL},
|
||||
AppLabelKey: appLabelKey,
|
||||
AppLabelValue: app.Name,
|
||||
AppName: app.Name,
|
||||
Namespace: app.Spec.Destination.Namespace,
|
||||
ApplicationSource: &app.Spec.Source,
|
||||
KustomizeOptions: kustomizeOptions,
|
||||
@@ -994,7 +752,7 @@ func (p *resourceInfoProvider) IsNamespaced(gk schema.GroupKind) (bool, error) {
|
||||
return p.namespacedByGk[gk], nil
|
||||
}
|
||||
|
||||
func groupLocalObjs(localObs []*unstructured.Unstructured, liveObjs []*unstructured.Unstructured, appNamespace string) map[kube.ResourceKey]*unstructured.Unstructured {
|
||||
func groupObjsByKey(localObs []*unstructured.Unstructured, liveObjs []*unstructured.Unstructured, appNamespace string) map[kube.ResourceKey]*unstructured.Unstructured {
|
||||
namespacedByGk := make(map[schema.GroupKind]bool)
|
||||
for i := range liveObjs {
|
||||
if liveObjs[i] != nil {
|
||||
@@ -1014,12 +772,19 @@ func groupLocalObjs(localObs []*unstructured.Unstructured, liveObjs []*unstructu
|
||||
return objByKey
|
||||
}
|
||||
|
||||
type objKeyLiveTarget struct {
|
||||
key kube.ResourceKey
|
||||
live *unstructured.Unstructured
|
||||
target *unstructured.Unstructured
|
||||
}
|
||||
|
||||
// NewApplicationDiffCommand returns a new instance of an `argocd app diff` command
|
||||
func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
refresh bool
|
||||
hardRefresh bool
|
||||
local string
|
||||
revision string
|
||||
localRepoRoot string
|
||||
)
|
||||
shortDesc := "Perform a diff against the target and live state."
|
||||
@@ -1043,11 +808,7 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
errors.CheckError(err)
|
||||
liveObjs, err := liveObjects(resources.Items)
|
||||
errors.CheckError(err)
|
||||
items := make([]struct {
|
||||
key kube.ResourceKey
|
||||
live *unstructured.Unstructured
|
||||
target *unstructured.Unstructured
|
||||
}, 0)
|
||||
items := make([]objKeyLiveTarget, 0)
|
||||
|
||||
conn, settingsIf := clientset.NewSettingsClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
@@ -1057,49 +818,25 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
if local != "" {
|
||||
conn, clusterIf := clientset.NewClusterClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
cluster, err := clusterIf.Get(context.Background(), &clusterpkg.ClusterQuery{Server: app.Spec.Destination.Server})
|
||||
cluster, err := clusterIf.Get(context.Background(), &clusterpkg.ClusterQuery{Name: app.Spec.Destination.Name, Server: app.Spec.Destination.Server})
|
||||
errors.CheckError(err)
|
||||
localObjs := groupLocalObjs(getLocalObjects(app, local, localRepoRoot, argoSettings.AppLabelKey, cluster.ServerVersion, argoSettings.KustomizeOptions, argoSettings.ConfigManagementPlugins), liveObjs, app.Spec.Destination.Namespace)
|
||||
for _, res := range resources.Items {
|
||||
var live = &unstructured.Unstructured{}
|
||||
err := json.Unmarshal([]byte(res.NormalizedLiveState), &live)
|
||||
localObjs := groupObjsByKey(getLocalObjects(app, local, localRepoRoot, argoSettings.AppLabelKey, cluster.ServerVersion, argoSettings.KustomizeOptions, argoSettings.ConfigManagementPlugins), liveObjs, app.Spec.Destination.Namespace)
|
||||
items = groupObjsForDiff(resources, localObjs, items, argoSettings, appName)
|
||||
} else if revision != "" {
|
||||
var unstructureds []*unstructured.Unstructured
|
||||
q := applicationpkg.ApplicationManifestQuery{
|
||||
Name: &appName,
|
||||
Revision: revision,
|
||||
}
|
||||
res, err := appIf.GetManifests(context.Background(), &q)
|
||||
errors.CheckError(err)
|
||||
for _, mfst := range res.Manifests {
|
||||
obj, err := argoappv1.UnmarshalToUnstructured(mfst)
|
||||
errors.CheckError(err)
|
||||
|
||||
key := kube.ResourceKey{Name: res.Name, Namespace: res.Namespace, Group: res.Group, Kind: res.Kind}
|
||||
if key.Kind == kube.SecretKind && key.Group == "" {
|
||||
// Don't bother comparing secrets, argo-cd doesn't have access to k8s secret data
|
||||
delete(localObjs, key)
|
||||
continue
|
||||
}
|
||||
if local, ok := localObjs[key]; ok || live != nil {
|
||||
if local != nil && !kube.IsCRD(local) {
|
||||
err = argokube.SetAppInstanceLabel(local, argoSettings.AppLabelKey, appName)
|
||||
errors.CheckError(err)
|
||||
}
|
||||
|
||||
items = append(items, struct {
|
||||
key kube.ResourceKey
|
||||
live *unstructured.Unstructured
|
||||
target *unstructured.Unstructured
|
||||
}{
|
||||
live: live,
|
||||
target: local,
|
||||
key: key,
|
||||
})
|
||||
delete(localObjs, key)
|
||||
}
|
||||
}
|
||||
for key, local := range localObjs {
|
||||
items = append(items, struct {
|
||||
key kube.ResourceKey
|
||||
live *unstructured.Unstructured
|
||||
target *unstructured.Unstructured
|
||||
}{
|
||||
live: nil,
|
||||
target: local,
|
||||
key: key,
|
||||
})
|
||||
unstructureds = append(unstructureds, obj)
|
||||
}
|
||||
groupedObjs := groupObjsByKey(unstructureds, liveObjs, app.Spec.Destination.Namespace)
|
||||
items = groupObjsForDiff(resources, groupedObjs, items, argoSettings, appName)
|
||||
} else {
|
||||
for i := range resources.Items {
|
||||
res := resources.Items[i]
|
||||
@@ -1111,15 +848,7 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
err = json.Unmarshal([]byte(res.TargetState), &target)
|
||||
errors.CheckError(err)
|
||||
|
||||
items = append(items, struct {
|
||||
key kube.ResourceKey
|
||||
live *unstructured.Unstructured
|
||||
target *unstructured.Unstructured
|
||||
}{
|
||||
live: live,
|
||||
target: target,
|
||||
key: kube.NewResourceKey(res.Group, res.Kind, res.Namespace, res.Name),
|
||||
})
|
||||
items = append(items, objKeyLiveTarget{kube.NewResourceKey(res.Group, res.Kind, res.Namespace, res.Name), live, target})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1136,7 +865,7 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
normalizer, err := argo.NewDiffNormalizer(app.Spec.IgnoreDifferences, overrides)
|
||||
errors.CheckError(err)
|
||||
|
||||
diffRes, err := diff.Diff(item.target, item.live, normalizer, diff.GetDefaultDiffOptions())
|
||||
diffRes, err := diff.Diff(item.target, item.live, diff.WithNormalizer(normalizer))
|
||||
errors.CheckError(err)
|
||||
|
||||
if diffRes.Modified || item.target == nil || item.live == nil {
|
||||
@@ -1154,7 +883,7 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
}
|
||||
|
||||
foundDiffs = true
|
||||
_ = diff.PrintDiff(item.key.Name, live, target)
|
||||
_ = cli.PrintDiff(item.key.Name, live, target)
|
||||
}
|
||||
}
|
||||
if foundDiffs {
|
||||
@@ -1166,14 +895,49 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
command.Flags().BoolVar(&refresh, "refresh", false, "Refresh application data when retrieving")
|
||||
command.Flags().BoolVar(&hardRefresh, "hard-refresh", false, "Refresh application data as well as target manifests cache")
|
||||
command.Flags().StringVar(&local, "local", "", "Compare live app to a local manifests")
|
||||
command.Flags().StringVar(&revision, "revision", "", "Compare live app to a particular revision")
|
||||
command.Flags().StringVar(&localRepoRoot, "local-repo-root", "/", "Path to the repository root. Used together with --local allows setting the repository root")
|
||||
return command
|
||||
}
|
||||
|
||||
func groupObjsForDiff(resources *application.ManagedResourcesResponse, objs map[kube.ResourceKey]*unstructured.Unstructured, items []objKeyLiveTarget, argoSettings *settings.Settings, appName string) []objKeyLiveTarget {
|
||||
for _, res := range resources.Items {
|
||||
var live = &unstructured.Unstructured{}
|
||||
err := json.Unmarshal([]byte(res.NormalizedLiveState), &live)
|
||||
errors.CheckError(err)
|
||||
|
||||
key := kube.ResourceKey{Name: res.Name, Namespace: res.Namespace, Group: res.Group, Kind: res.Kind}
|
||||
if key.Kind == kube.SecretKind && key.Group == "" {
|
||||
// Don't bother comparing secrets, argo-cd doesn't have access to k8s secret data
|
||||
delete(objs, key)
|
||||
continue
|
||||
}
|
||||
if local, ok := objs[key]; ok || live != nil {
|
||||
if local != nil && !kube.IsCRD(local) {
|
||||
err = argokube.SetAppInstanceLabel(local, argoSettings.AppLabelKey, appName)
|
||||
errors.CheckError(err)
|
||||
}
|
||||
|
||||
items = append(items, objKeyLiveTarget{key, live, local})
|
||||
delete(objs, key)
|
||||
}
|
||||
}
|
||||
for key, local := range objs {
|
||||
if key.Kind == kube.SecretKind && key.Group == "" {
|
||||
// Don't bother comparing secrets, argo-cd doesn't have access to k8s secret data
|
||||
delete(objs, key)
|
||||
continue
|
||||
}
|
||||
items = append(items, objKeyLiveTarget{key, nil, local})
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
// NewApplicationDeleteCommand returns a new instance of an `argocd app delete` command
|
||||
func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
cascade bool
|
||||
cascade bool
|
||||
noPrompt bool
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "delete APPNAME",
|
||||
@@ -1185,6 +949,13 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
|
||||
}
|
||||
conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
var isTerminal bool = isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
|
||||
var isConfirmAll bool = false
|
||||
var numOfApps = len(args)
|
||||
var promptFlag = c.Flag("yes")
|
||||
if promptFlag.Changed && promptFlag.Value.String() == "true" {
|
||||
noPrompt = true
|
||||
}
|
||||
for _, appName := range args {
|
||||
appDeleteReq := applicationpkg.ApplicationDeleteRequest{
|
||||
Name: &appName,
|
||||
@@ -1192,12 +963,41 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
|
||||
if c.Flag("cascade").Changed {
|
||||
appDeleteReq.Cascade = &cascade
|
||||
}
|
||||
_, err := appIf.Delete(context.Background(), &appDeleteReq)
|
||||
errors.CheckError(err)
|
||||
if cascade && isTerminal && !noPrompt {
|
||||
var confirmAnswer string = "n"
|
||||
var lowercaseAnswer string
|
||||
if numOfApps == 1 {
|
||||
fmt.Println("Are you sure you want to delete '" + appName + "' and all its resources? [y/n]")
|
||||
fmt.Scan(&confirmAnswer)
|
||||
lowercaseAnswer = strings.ToLower(confirmAnswer)
|
||||
} else {
|
||||
if !isConfirmAll {
|
||||
fmt.Println("Are you sure you want to delete '" + appName + "' and all its resources? [y/n/A] where 'A' is to delete all specified apps and their resources without prompting")
|
||||
fmt.Scan(&confirmAnswer)
|
||||
lowercaseAnswer = strings.ToLower(confirmAnswer)
|
||||
if lowercaseAnswer == "a" || lowercaseAnswer == "all" {
|
||||
lowercaseAnswer = "y"
|
||||
isConfirmAll = true
|
||||
}
|
||||
} else {
|
||||
lowercaseAnswer = "y"
|
||||
}
|
||||
}
|
||||
if lowercaseAnswer == "y" || lowercaseAnswer == "yes" {
|
||||
_, err := appIf.Delete(context.Background(), &appDeleteReq)
|
||||
errors.CheckError(err)
|
||||
} else {
|
||||
fmt.Println("The command to delete '" + appName + "' was cancelled.")
|
||||
}
|
||||
} else {
|
||||
_, err := appIf.Delete(context.Background(), &appDeleteReq)
|
||||
errors.CheckError(err)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
command.Flags().BoolVar(&cascade, "cascade", true, "Perform a cascaded deletion of all application resources")
|
||||
command.Flags().BoolVarP(&noPrompt, "yes", "y", false, "Turn off prompting to confirm cascaded deletion of application resources")
|
||||
return command
|
||||
}
|
||||
|
||||
@@ -1439,8 +1239,8 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
force bool
|
||||
async bool
|
||||
retryLimit int64
|
||||
retryBackoffDuration string
|
||||
retryBackoffMaxDuration string
|
||||
retryBackoffDuration time.Duration
|
||||
retryBackoffMaxDuration time.Duration
|
||||
retryBackoffFactor int64
|
||||
local string
|
||||
localRepoRoot string
|
||||
@@ -1541,7 +1341,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
|
||||
conn, clusterIf := acdClient.NewClusterClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
cluster, err := clusterIf.Get(context.Background(), &clusterpkg.ClusterQuery{Server: app.Spec.Destination.Server})
|
||||
cluster, err := clusterIf.Get(context.Background(), &clusterpkg.ClusterQuery{Name: app.Spec.Destination.Name, Server: app.Spec.Destination.Server})
|
||||
errors.CheckError(err)
|
||||
argoio.Close(conn)
|
||||
localObjsStrings = getLocalObjectsString(app, local, localRepoRoot, argoSettings.AppLabelKey, cluster.ServerVersion, argoSettings.KustomizeOptions, argoSettings.ConfigManagementPlugins)
|
||||
@@ -1570,8 +1370,8 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
syncReq.RetryStrategy = &argoappv1.RetryStrategy{
|
||||
Limit: retryLimit,
|
||||
Backoff: &argoappv1.Backoff{
|
||||
Duration: retryBackoffDuration,
|
||||
MaxDuration: retryBackoffMaxDuration,
|
||||
Duration: retryBackoffDuration.String(),
|
||||
MaxDuration: retryBackoffMaxDuration.String(),
|
||||
Factor: pointer.Int64Ptr(retryBackoffFactor),
|
||||
},
|
||||
}
|
||||
@@ -1607,8 +1407,8 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
command.Flags().StringArrayVar(&labels, "label", []string{}, "Sync only specific resources with a label. This option may be specified repeatedly.")
|
||||
command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time out after this many seconds")
|
||||
command.Flags().Int64Var(&retryLimit, "retry-limit", 0, "Max number of allowed sync retries")
|
||||
command.Flags().StringVar(&retryBackoffDuration, "retry-backoff-duration", fmt.Sprintf("%ds", common.DefaultSyncRetryDuration/time.Second), "Retry backoff base duration. Default unit is seconds, but could also be a duration (e.g. 2m, 1h)")
|
||||
command.Flags().StringVar(&retryBackoffMaxDuration, "retry-backoff-max-duration", fmt.Sprintf("%ds", common.DefaultSyncRetryMaxDuration/time.Second), "Max retry backoff duration. Default unit is seconds, but could also be a duration (e.g. 2m, 1h)")
|
||||
command.Flags().DurationVar(&retryBackoffDuration, "retry-backoff-duration", common.DefaultSyncRetryDuration, "Retry backoff base duration. Input needs to be a duration (e.g. 2m, 1h)")
|
||||
command.Flags().DurationVar(&retryBackoffMaxDuration, "retry-backoff-max-duration", common.DefaultSyncRetryMaxDuration, "Max retry backoff duration. Input needs to be a duration (e.g. 2m, 1h)")
|
||||
command.Flags().Int64Var(&retryBackoffFactor, "retry-backoff-factor", common.DefaultSyncRetryFactor, "Factor multiplies the base duration after each failed retry")
|
||||
command.Flags().StringVar(&strategy, "strategy", "", "Sync strategy (one of: apply|hook)")
|
||||
command.Flags().BoolVar(&force, "force", false, "Use a force apply")
|
||||
@@ -1833,7 +1633,7 @@ func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout
|
||||
selectedResourcesAreReady = checkResourceStatus(watchSync, watchHealth, watchOperation, watchSuspended, string(app.Status.Health.Status), string(app.Status.Sync.Status), appEvent.Application.Operation)
|
||||
}
|
||||
|
||||
if selectedResourcesAreReady && !operationInProgress {
|
||||
if selectedResourcesAreReady && (!operationInProgress || !watchOperation) {
|
||||
app = printFinalStatus(app)
|
||||
return app, nil
|
||||
}
|
||||
@@ -2172,9 +1972,9 @@ func NewApplicationEditCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
return err
|
||||
}
|
||||
|
||||
var appOpts appOptions
|
||||
setAppSpecOptions(c.Flags(), &app.Spec, &appOpts)
|
||||
_, err = appIf.UpdateSpec(context.Background(), &applicationpkg.ApplicationUpdateSpecRequest{Name: &app.Name, Spec: updatedSpec, Validate: &appOpts.validate})
|
||||
var appOpts cmdutil.AppOptions
|
||||
cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts)
|
||||
_, err = appIf.UpdateSpec(context.Background(), &applicationpkg.ApplicationUpdateSpecRequest{Name: &app.Name, Spec: updatedSpec, Validate: &appOpts.Validate})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to update application spec:\n%v", err)
|
||||
}
|
||||
@@ -2284,7 +2084,7 @@ func filterResources(command *cobra.Command, resources []*argoappv1.ResourceDiff
|
||||
if resourceName != "" && resourceName != obj.GetName() {
|
||||
continue
|
||||
}
|
||||
if kind != gvk.Kind {
|
||||
if kind != "" && kind != gvk.Kind {
|
||||
continue
|
||||
}
|
||||
deepCopy := obj.DeepCopy()
|
||||
|
||||
@@ -8,14 +8,14 @@ import (
|
||||
"strconv"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/io"
|
||||
"github.com/ghodss/yaml"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
|
||||
applicationpkg "github.com/argoproj/argo-cd/pkg/apiclient/application"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/io"
|
||||
)
|
||||
|
||||
type DisplayedAction struct {
|
||||
@@ -100,7 +100,6 @@ func NewApplicationResourceActionsListCommand(clientOpts *argocdclient.ClientOpt
|
||||
case "":
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
fmt.Fprintf(w, "GROUP\tKIND\tNAME\tACTION\tDISABLED\n")
|
||||
fmt.Println()
|
||||
for _, action := range availableActions {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", action.Group, action.Kind, action.Name, action.Action, strconv.FormatBool(action.Disabled))
|
||||
}
|
||||
|
||||
@@ -9,14 +9,14 @@ import (
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/io"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
|
||||
certificatepkg "github.com/argoproj/argo-cd/pkg/apiclient/certificate"
|
||||
appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
certutil "github.com/argoproj/argo-cd/util/cert"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/io"
|
||||
)
|
||||
|
||||
// NewCertCommand returns a new instance of an `argocd repo` command
|
||||
|
||||
@@ -3,25 +3,23 @@ package commands
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/io"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
cmdutil "github.com/argoproj/argo-cd/cmd/util"
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
|
||||
clusterpkg "github.com/argoproj/argo-cd/pkg/apiclient/cluster"
|
||||
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/util/clusterauth"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/io"
|
||||
)
|
||||
|
||||
// NewClusterCommand returns a new instance of an `argocd cluster` command
|
||||
@@ -58,14 +56,7 @@ func NewClusterCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clientc
|
||||
// NewClusterAddCommand returns a new instance of an `argocd cluster add` command
|
||||
func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clientcmd.PathOptions) *cobra.Command {
|
||||
var (
|
||||
inCluster bool
|
||||
upsert bool
|
||||
serviceAccount string
|
||||
awsRoleArn string
|
||||
awsClusterName string
|
||||
systemNamespace string
|
||||
namespaces []string
|
||||
name string
|
||||
clusterOpts cmdutil.ClusterOptions
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "add CONTEXT",
|
||||
@@ -74,7 +65,7 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
|
||||
var configAccess clientcmd.ConfigAccess = pathOpts
|
||||
if len(args) == 0 {
|
||||
log.Error("Choose a context name from:")
|
||||
printKubeContexts(configAccess)
|
||||
cmdutil.PrintKubeContexts(configAccess)
|
||||
os.Exit(1)
|
||||
}
|
||||
config, err := configAccess.GetStartingConfig()
|
||||
@@ -94,34 +85,46 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
|
||||
|
||||
managerBearerToken := ""
|
||||
var awsAuthConf *argoappv1.AWSAuthConfig
|
||||
if awsClusterName != "" {
|
||||
var execProviderConf *argoappv1.ExecProviderConfig
|
||||
if clusterOpts.AwsClusterName != "" {
|
||||
awsAuthConf = &argoappv1.AWSAuthConfig{
|
||||
ClusterName: awsClusterName,
|
||||
RoleARN: awsRoleArn,
|
||||
ClusterName: clusterOpts.AwsClusterName,
|
||||
RoleARN: clusterOpts.AwsRoleArn,
|
||||
}
|
||||
} else if clusterOpts.ExecProviderCommand != "" {
|
||||
execProviderConf = &argoappv1.ExecProviderConfig{
|
||||
Command: clusterOpts.ExecProviderCommand,
|
||||
Args: clusterOpts.ExecProviderArgs,
|
||||
Env: clusterOpts.ExecProviderEnv,
|
||||
APIVersion: clusterOpts.ExecProviderAPIVersion,
|
||||
InstallHint: clusterOpts.ExecProviderInstallHint,
|
||||
}
|
||||
} else {
|
||||
// Install RBAC resources for managing the cluster
|
||||
clientset, err := kubernetes.NewForConfig(conf)
|
||||
errors.CheckError(err)
|
||||
if serviceAccount != "" {
|
||||
managerBearerToken, err = clusterauth.GetServiceAccountBearerToken(clientset, systemNamespace, serviceAccount)
|
||||
if clusterOpts.ServiceAccount != "" {
|
||||
managerBearerToken, err = clusterauth.GetServiceAccountBearerToken(clientset, clusterOpts.SystemNamespace, clusterOpts.ServiceAccount)
|
||||
} else {
|
||||
managerBearerToken, err = clusterauth.InstallClusterManagerRBAC(clientset, systemNamespace, namespaces)
|
||||
managerBearerToken, err = clusterauth.InstallClusterManagerRBAC(clientset, clusterOpts.SystemNamespace, clusterOpts.Namespaces)
|
||||
}
|
||||
errors.CheckError(err)
|
||||
}
|
||||
conn, clusterIf := argocdclient.NewClientOrDie(clientOpts).NewClusterClientOrDie()
|
||||
defer io.Close(conn)
|
||||
if name != "" {
|
||||
contextName = name
|
||||
if clusterOpts.Name != "" {
|
||||
contextName = clusterOpts.Name
|
||||
}
|
||||
clst := newCluster(contextName, namespaces, conf, managerBearerToken, awsAuthConf)
|
||||
if inCluster {
|
||||
clst := cmdutil.NewCluster(contextName, clusterOpts.Namespaces, conf, managerBearerToken, awsAuthConf, execProviderConf)
|
||||
if clusterOpts.InCluster {
|
||||
clst.Server = common.KubernetesInternalAPIServerAddr
|
||||
}
|
||||
if clusterOpts.Shard >= 0 {
|
||||
clst.Shard = &clusterOpts.Shard
|
||||
}
|
||||
clstCreateReq := clusterpkg.ClusterCreateRequest{
|
||||
Cluster: clst,
|
||||
Upsert: upsert,
|
||||
Upsert: clusterOpts.Upsert,
|
||||
}
|
||||
_, err = clusterIf.Create(context.Background(), &clstCreateReq)
|
||||
errors.CheckError(err)
|
||||
@@ -129,100 +132,13 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
|
||||
},
|
||||
}
|
||||
command.PersistentFlags().StringVar(&pathOpts.LoadingRules.ExplicitPath, pathOpts.ExplicitFileFlag, pathOpts.LoadingRules.ExplicitPath, "use a particular kubeconfig file")
|
||||
command.Flags().BoolVar(&inCluster, "in-cluster", false, "Indicates Argo CD resides inside this cluster and should connect using the internal k8s hostname (kubernetes.default.svc)")
|
||||
command.Flags().BoolVar(&upsert, "upsert", false, "Override an existing cluster with the same name even if the spec differs")
|
||||
command.Flags().StringVar(&serviceAccount, "service-account", "", fmt.Sprintf("System namespace service account to use for kubernetes resource management. If not set then default \"%s\" SA will be created", clusterauth.ArgoCDManagerServiceAccount))
|
||||
command.Flags().StringVar(&awsClusterName, "aws-cluster-name", "", "AWS Cluster name if set then aws cli eks token command will be used to access cluster")
|
||||
command.Flags().StringVar(&awsRoleArn, "aws-role-arn", "", "Optional AWS role arn. If set then AWS IAM Authenticator assume a role to perform cluster operations instead of the default AWS credential provider chain.")
|
||||
command.Flags().StringVar(&systemNamespace, "system-namespace", common.DefaultSystemNamespace, "Use different system namespace")
|
||||
command.Flags().StringArrayVar(&namespaces, "namespace", nil, "List of namespaces which are allowed to manage")
|
||||
command.Flags().StringVar(&name, "name", "", "Overwrite the cluster name")
|
||||
command.Flags().BoolVar(&clusterOpts.Upsert, "upsert", false, "Override an existing cluster with the same name even if the spec differs")
|
||||
command.Flags().StringVar(&clusterOpts.ServiceAccount, "service-account", "", fmt.Sprintf("System namespace service account to use for kubernetes resource management. If not set then default \"%s\" SA will be created", clusterauth.ArgoCDManagerServiceAccount))
|
||||
command.Flags().StringVar(&clusterOpts.SystemNamespace, "system-namespace", common.DefaultSystemNamespace, "Use different system namespace")
|
||||
cmdutil.AddClusterFlags(command, &clusterOpts)
|
||||
return command
|
||||
}
|
||||
|
||||
func printKubeContexts(ca clientcmd.ConfigAccess) {
|
||||
config, err := ca.GetStartingConfig()
|
||||
errors.CheckError(err)
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
defer func() { _ = w.Flush() }()
|
||||
columnNames := []string{"CURRENT", "NAME", "CLUSTER", "SERVER"}
|
||||
_, err = fmt.Fprintf(w, "%s\n", strings.Join(columnNames, "\t"))
|
||||
errors.CheckError(err)
|
||||
|
||||
// sort names so output is deterministic
|
||||
contextNames := make([]string, 0)
|
||||
for name := range config.Contexts {
|
||||
contextNames = append(contextNames, name)
|
||||
}
|
||||
sort.Strings(contextNames)
|
||||
|
||||
if config.Clusters == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, name := range contextNames {
|
||||
// ignore malformed kube config entries
|
||||
context := config.Contexts[name]
|
||||
if context == nil {
|
||||
continue
|
||||
}
|
||||
cluster := config.Clusters[context.Cluster]
|
||||
if cluster == nil {
|
||||
continue
|
||||
}
|
||||
prefix := " "
|
||||
if config.CurrentContext == name {
|
||||
prefix = "*"
|
||||
}
|
||||
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", prefix, name, context.Cluster, cluster.Server)
|
||||
errors.CheckError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func newCluster(name string, namespaces []string, conf *rest.Config, managerBearerToken string, awsAuthConf *argoappv1.AWSAuthConfig) *argoappv1.Cluster {
|
||||
tlsClientConfig := argoappv1.TLSClientConfig{
|
||||
Insecure: conf.TLSClientConfig.Insecure,
|
||||
ServerName: conf.TLSClientConfig.ServerName,
|
||||
CAData: conf.TLSClientConfig.CAData,
|
||||
CertData: conf.TLSClientConfig.CertData,
|
||||
KeyData: conf.TLSClientConfig.KeyData,
|
||||
}
|
||||
if len(conf.TLSClientConfig.CAData) == 0 && conf.TLSClientConfig.CAFile != "" {
|
||||
data, err := ioutil.ReadFile(conf.TLSClientConfig.CAFile)
|
||||
errors.CheckError(err)
|
||||
tlsClientConfig.CAData = data
|
||||
}
|
||||
if len(conf.TLSClientConfig.CertData) == 0 && conf.TLSClientConfig.CertFile != "" {
|
||||
data, err := ioutil.ReadFile(conf.TLSClientConfig.CertFile)
|
||||
errors.CheckError(err)
|
||||
tlsClientConfig.CertData = data
|
||||
}
|
||||
if len(conf.TLSClientConfig.KeyData) == 0 && conf.TLSClientConfig.KeyFile != "" {
|
||||
data, err := ioutil.ReadFile(conf.TLSClientConfig.KeyFile)
|
||||
errors.CheckError(err)
|
||||
tlsClientConfig.KeyData = data
|
||||
}
|
||||
|
||||
clst := argoappv1.Cluster{
|
||||
Server: conf.Host,
|
||||
Name: name,
|
||||
Namespaces: namespaces,
|
||||
Config: argoappv1.ClusterConfig{
|
||||
TLSClientConfig: tlsClientConfig,
|
||||
AWSAuthConfig: awsAuthConf,
|
||||
},
|
||||
}
|
||||
|
||||
// Bearer token will preferentially be used for auth if present,
|
||||
// Even in presence of key/cert credentials
|
||||
// So set bearer token only if the key/cert data is absent
|
||||
if len(tlsClientConfig.CertData) == 0 || len(tlsClientConfig.KeyData) == 0 {
|
||||
clst.Config.BearerToken = managerBearerToken
|
||||
}
|
||||
|
||||
return &clst
|
||||
}
|
||||
|
||||
// NewClusterGetCommand returns a new instance of an `argocd cluster get` command
|
||||
func NewClusterGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
@@ -318,6 +234,7 @@ func NewClusterRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm
|
||||
// errors.CheckError(err)
|
||||
_, err := clusterIf.Delete(context.Background(), &clusterpkg.ClusterQuery{Server: clusterName})
|
||||
errors.CheckError(err)
|
||||
fmt.Printf("Cluster '%s' removed\n", clusterName)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
@@ -32,52 +29,3 @@ func Test_printClusterTable(t *testing.T) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func Test_newCluster(t *testing.T) {
|
||||
clusterWithData := newCluster("test-cluster", []string{"test-namespace"}, &rest.Config{
|
||||
TLSClientConfig: rest.TLSClientConfig{
|
||||
Insecure: false,
|
||||
ServerName: "test-endpoint.example.com",
|
||||
CAData: []byte("test-ca-data"),
|
||||
CertData: []byte("test-cert-data"),
|
||||
KeyData: []byte("test-key-data"),
|
||||
},
|
||||
Host: "test-endpoint.example.com",
|
||||
},
|
||||
"test-bearer-token",
|
||||
&v1alpha1.AWSAuthConfig{})
|
||||
|
||||
assert.Equal(t, "test-cert-data", string(clusterWithData.Config.CertData))
|
||||
assert.Equal(t, "test-key-data", string(clusterWithData.Config.KeyData))
|
||||
assert.Equal(t, "", clusterWithData.Config.BearerToken)
|
||||
|
||||
clusterWithFiles := newCluster("test-cluster", []string{"test-namespace"}, &rest.Config{
|
||||
TLSClientConfig: rest.TLSClientConfig{
|
||||
Insecure: false,
|
||||
ServerName: "test-endpoint.example.com",
|
||||
CAData: []byte("test-ca-data"),
|
||||
CertFile: "./testdata/test.cert.pem",
|
||||
KeyFile: "./testdata/test.key.pem",
|
||||
},
|
||||
Host: "test-endpoint.example.com",
|
||||
},
|
||||
"test-bearer-token",
|
||||
&v1alpha1.AWSAuthConfig{})
|
||||
|
||||
assert.True(t, strings.Contains(string(clusterWithFiles.Config.CertData), "test-cert-data"))
|
||||
assert.True(t, strings.Contains(string(clusterWithFiles.Config.KeyData), "test-key-data"))
|
||||
assert.Equal(t, "", clusterWithFiles.Config.BearerToken)
|
||||
|
||||
clusterWithBearerToken := newCluster("test-cluster", []string{"test-namespace"}, &rest.Config{
|
||||
TLSClientConfig: rest.TLSClientConfig{
|
||||
Insecure: false,
|
||||
ServerName: "test-endpoint.example.com",
|
||||
CAData: []byte("test-ca-data"),
|
||||
},
|
||||
Host: "test-endpoint.example.com",
|
||||
},
|
||||
"test-bearer-token",
|
||||
&v1alpha1.AWSAuthConfig{})
|
||||
|
||||
assert.Equal(t, "test-bearer-token", clusterWithBearerToken.Config.BearerToken)
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ import (
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/localconfig"
|
||||
)
|
||||
|
||||
|
||||
@@ -8,13 +8,13 @@ import (
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
argoio "github.com/argoproj/gitops-engine/pkg/utils/io"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
|
||||
gpgkeypkg "github.com/argoproj/argo-cd/pkg/apiclient/gpgkey"
|
||||
appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
argoio "github.com/argoproj/argo-cd/util/io"
|
||||
)
|
||||
|
||||
// NewGPGCommand returns a new instance of an `argocd repo` command
|
||||
|
||||
@@ -2,17 +2,18 @@ package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"html"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/io"
|
||||
"github.com/coreos/go-oidc"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/dgrijalva/jwt-go/v4"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -22,7 +23,10 @@ import (
|
||||
sessionpkg "github.com/argoproj/argo-cd/pkg/apiclient/session"
|
||||
settingspkg "github.com/argoproj/argo-cd/pkg/apiclient/settings"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
grpc_util "github.com/argoproj/argo-cd/util/grpc"
|
||||
"github.com/argoproj/argo-cd/util/io"
|
||||
jwtutil "github.com/argoproj/argo-cd/util/jwt"
|
||||
"github.com/argoproj/argo-cd/util/localconfig"
|
||||
oidcutil "github.com/argoproj/argo-cd/util/oidc"
|
||||
"github.com/argoproj/argo-cd/util/rand"
|
||||
@@ -80,6 +84,7 @@ func NewLoginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comman
|
||||
GRPCWebRootPath: globalClientOpts.GRPCWebRootPath,
|
||||
PortForward: globalClientOpts.PortForward,
|
||||
PortForwardNamespace: globalClientOpts.PortForwardNamespace,
|
||||
Headers: globalClientOpts.Headers,
|
||||
}
|
||||
acdClient := argocdclient.NewClientOrDie(&clientOpts)
|
||||
setConn, setIf := acdClient.NewSettingsClientOrDie()
|
||||
@@ -111,7 +116,7 @@ func NewLoginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comman
|
||||
}
|
||||
|
||||
parser := &jwt.Parser{
|
||||
SkipClaimsValidation: true,
|
||||
ValidationHelper: jwt.NewValidationHelper(jwt.WithoutClaimsValidation(), jwt.WithoutAudienceValidation()),
|
||||
}
|
||||
claims := jwt.MapClaims{}
|
||||
_, _, err := parser.ParseUnverified(tokenString, &claims)
|
||||
@@ -159,13 +164,13 @@ func NewLoginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comman
|
||||
}
|
||||
|
||||
func userDisplayName(claims jwt.MapClaims) string {
|
||||
if email, ok := claims["email"]; ok && email != nil {
|
||||
return email.(string)
|
||||
if email := jwtutil.StringField(claims, "email"); email != "" {
|
||||
return email
|
||||
}
|
||||
if name, ok := claims["name"]; ok && name != nil {
|
||||
return name.(string)
|
||||
if name := jwtutil.StringField(claims, "name"); name != "" {
|
||||
return name
|
||||
}
|
||||
return claims["sub"].(string)
|
||||
return jwtutil.StringField(claims, "sub")
|
||||
}
|
||||
|
||||
// oauth2Login opens a browser, runs a temporary HTTP server to delegate OAuth2 login flow and
|
||||
@@ -188,17 +193,22 @@ func oauth2Login(ctx context.Context, port int, oidcSettings *settingspkg.OIDCCo
|
||||
var refreshToken string
|
||||
|
||||
handleErr := func(w http.ResponseWriter, errMsg string) {
|
||||
http.Error(w, errMsg, http.StatusBadRequest)
|
||||
http.Error(w, html.EscapeString(errMsg), http.StatusBadRequest)
|
||||
completionChan <- errMsg
|
||||
}
|
||||
|
||||
// PKCE implementation of https://tools.ietf.org/html/rfc7636
|
||||
codeVerifier := rand.RandStringCharset(43, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~")
|
||||
codeChallengeHash := sha256.Sum256([]byte(codeVerifier))
|
||||
codeChallenge := base64.RawURLEncoding.EncodeToString(codeChallengeHash[:])
|
||||
|
||||
// Authorization redirect callback from OAuth2 auth flow.
|
||||
// Handles both implicit and authorization code flow
|
||||
callbackHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Debugf("Callback: %s", r.URL)
|
||||
|
||||
if formErr := r.FormValue("error"); formErr != "" {
|
||||
handleErr(w, formErr+": "+r.FormValue("error_description"))
|
||||
handleErr(w, fmt.Sprintf("%s: %s", formErr, r.FormValue("error_description")))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -231,7 +241,8 @@ func oauth2Login(ctx context.Context, port int, oidcSettings *settingspkg.OIDCCo
|
||||
handleErr(w, fmt.Sprintf("no code in request: %q", r.Form))
|
||||
return
|
||||
}
|
||||
tok, err := oauth2conf.Exchange(ctx, code)
|
||||
opts := []oauth2.AuthCodeOption{oauth2.SetAuthURLParam("code_verifier", codeVerifier)}
|
||||
tok, err := oauth2conf.Exchange(ctx, code, opts...)
|
||||
if err != nil {
|
||||
handleErr(w, err.Error())
|
||||
return
|
||||
@@ -267,6 +278,8 @@ func oauth2Login(ctx context.Context, port int, oidcSettings *settingspkg.OIDCCo
|
||||
|
||||
switch grantType {
|
||||
case oidcutil.GrantTypeAuthorizationCode:
|
||||
opts = append(opts, oauth2.SetAuthURLParam("code_challenge", codeChallenge))
|
||||
opts = append(opts, oauth2.SetAuthURLParam("code_challenge_method", "S256"))
|
||||
url = oauth2conf.AuthCodeURL(stateNonce, opts...)
|
||||
case oidcutil.GrantTypeImplicit:
|
||||
url = oidcutil.ImplicitFlowURL(oauth2conf, stateNonce, opts...)
|
||||
|
||||
31
cmd/argocd/commands/login_test.go
Normal file
31
cmd/argocd/commands/login_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/dgrijalva/jwt-go/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
//
|
||||
|
||||
func Test_userDisplayName_email(t *testing.T) {
|
||||
claims := jwt.MapClaims{"iss": "qux", "sub": "foo", "email": "firstname.lastname@example.com", "groups": []string{"baz"}}
|
||||
actualName := userDisplayName(claims)
|
||||
expectedName := "firstname.lastname@example.com"
|
||||
assert.Equal(t, expectedName, actualName)
|
||||
}
|
||||
|
||||
func Test_userDisplayName_name(t *testing.T) {
|
||||
claims := jwt.MapClaims{"iss": "qux", "sub": "foo", "name": "Firstname Lastname", "groups": []string{"baz"}}
|
||||
actualName := userDisplayName(claims)
|
||||
expectedName := "Firstname Lastname"
|
||||
assert.Equal(t, expectedName, actualName)
|
||||
}
|
||||
|
||||
func Test_userDisplayName_sub(t *testing.T) {
|
||||
claims := jwt.MapClaims{"iss": "qux", "sub": "foo", "groups": []string{"baz"}}
|
||||
actualName := userDisplayName(claims)
|
||||
expectedName := "foo"
|
||||
assert.Equal(t, expectedName, actualName)
|
||||
}
|
||||
@@ -4,11 +4,11 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/localconfig"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
argoio "github.com/argoproj/gitops-engine/pkg/utils/io"
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/ghodss/yaml"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -21,60 +17,24 @@ import (
|
||||
"github.com/spf13/pflag"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
cmdutil "github.com/argoproj/argo-cd/cmd/util"
|
||||
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
|
||||
projectpkg "github.com/argoproj/argo-cd/pkg/apiclient/project"
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/config"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/git"
|
||||
"github.com/argoproj/argo-cd/util/gpg"
|
||||
argoio "github.com/argoproj/argo-cd/util/io"
|
||||
)
|
||||
|
||||
type projectOpts struct {
|
||||
description string
|
||||
destinations []string
|
||||
sources []string
|
||||
signatureKeys []string
|
||||
orphanedResourcesEnabled bool
|
||||
orphanedResourcesWarn bool
|
||||
}
|
||||
|
||||
type policyOpts struct {
|
||||
action string
|
||||
permission string
|
||||
object string
|
||||
}
|
||||
|
||||
func (opts *projectOpts) GetDestinations() []v1alpha1.ApplicationDestination {
|
||||
destinations := make([]v1alpha1.ApplicationDestination, 0)
|
||||
for _, destStr := range opts.destinations {
|
||||
parts := strings.Split(destStr, ",")
|
||||
if len(parts) != 2 {
|
||||
log.Fatalf("Expected destination of the form: server,namespace. Received: %s", destStr)
|
||||
} else {
|
||||
destinations = append(destinations, v1alpha1.ApplicationDestination{
|
||||
Server: parts[0],
|
||||
Namespace: parts[1],
|
||||
})
|
||||
}
|
||||
}
|
||||
return destinations
|
||||
}
|
||||
|
||||
// TODO: Get configured keys and emit warning when a key is specified that is not configured
|
||||
func (opts *projectOpts) GetSignatureKeys() []v1alpha1.SignatureKey {
|
||||
signatureKeys := make([]v1alpha1.SignatureKey, 0)
|
||||
for _, keyStr := range opts.signatureKeys {
|
||||
if !gpg.IsShortKeyID(keyStr) && !gpg.IsLongKeyID(keyStr) {
|
||||
log.Fatalf("'%s' is not a valid GnuPG key ID", keyStr)
|
||||
}
|
||||
signatureKeys = append(signatureKeys, v1alpha1.SignatureKey{KeyID: gpg.KeyID(keyStr)})
|
||||
}
|
||||
return signatureKeys
|
||||
}
|
||||
|
||||
// NewProjectCommand returns a new instance of an `argocd proj` command
|
||||
func NewProjectCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
@@ -108,28 +68,6 @@ func NewProjectCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func addProjFlags(command *cobra.Command, opts *projectOpts) {
|
||||
command.Flags().StringVarP(&opts.description, "description", "", "", "Project description")
|
||||
command.Flags().StringArrayVarP(&opts.destinations, "dest", "d", []string{},
|
||||
"Permitted destination server and namespace (e.g. https://192.168.99.100:8443,default)")
|
||||
command.Flags().StringArrayVarP(&opts.sources, "src", "s", []string{}, "Permitted source repository URL")
|
||||
command.Flags().StringSliceVar(&opts.signatureKeys, "signature-keys", []string{}, "GnuPG public key IDs for commit signature verification")
|
||||
command.Flags().BoolVar(&opts.orphanedResourcesEnabled, "orphaned-resources", false, "Enables orphaned resources monitoring")
|
||||
command.Flags().BoolVar(&opts.orphanedResourcesWarn, "orphaned-resources-warn", false, "Specifies if applications should be a warning condition when orphaned resources detected")
|
||||
}
|
||||
|
||||
func getOrphanedResourcesSettings(c *cobra.Command, opts projectOpts) *v1alpha1.OrphanedResourcesMonitorSettings {
|
||||
warnChanged := c.Flag("orphaned-resources-warn").Changed
|
||||
if opts.orphanedResourcesEnabled || warnChanged {
|
||||
settings := v1alpha1.OrphanedResourcesMonitorSettings{}
|
||||
if warnChanged {
|
||||
settings.Warn = pointer.BoolPtr(opts.orphanedResourcesWarn)
|
||||
}
|
||||
return &settings
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addPolicyFlags(command *cobra.Command, opts *policyOpts) {
|
||||
command.Flags().StringVarP(&opts.action, "action", "a", "", "Action to grant/deny permission on (e.g. get, create, list, update, delete)")
|
||||
command.Flags().StringVarP(&opts.permission, "permission", "p", "allow", "Whether to allow or deny access to object with the action. This can only be 'allow' or 'deny'")
|
||||
@@ -144,7 +82,7 @@ func humanizeTimestamp(epoch int64) string {
|
||||
// NewProjectCreateCommand returns a new instance of an `argocd proj create` command
|
||||
func NewProjectCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
opts projectOpts
|
||||
opts cmdutil.ProjectOpts
|
||||
fileURL string
|
||||
upsert bool
|
||||
)
|
||||
@@ -152,48 +90,12 @@ func NewProjectCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm
|
||||
Use: "create PROJECT",
|
||||
Short: "Create a project",
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
var proj v1alpha1.AppProject
|
||||
fmt.Printf("EE: %d/%v\n", len(opts.signatureKeys), opts.signatureKeys)
|
||||
if fileURL == "-" {
|
||||
// read stdin
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
err := config.UnmarshalReader(reader, &proj)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to read manifest from stdin: %v", err)
|
||||
}
|
||||
} else if fileURL != "" {
|
||||
// read uri
|
||||
parsedURL, err := url.ParseRequestURI(fileURL)
|
||||
if err != nil || !(parsedURL.Scheme == "http" || parsedURL.Scheme == "https") {
|
||||
err = config.UnmarshalLocalFile(fileURL, &proj)
|
||||
} else {
|
||||
err = config.UnmarshalRemoteFile(fileURL, &proj)
|
||||
}
|
||||
errors.CheckError(err)
|
||||
if len(args) == 1 && args[0] != proj.Name {
|
||||
log.Fatalf("project name '%s' does not match project spec metadata.name '%s'", args[0], proj.Name)
|
||||
}
|
||||
} else {
|
||||
// read arguments
|
||||
if len(args) == 0 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
projName := args[0]
|
||||
proj = v1alpha1.AppProject{
|
||||
ObjectMeta: v1.ObjectMeta{Name: projName},
|
||||
Spec: v1alpha1.AppProjectSpec{
|
||||
Description: opts.description,
|
||||
Destinations: opts.GetDestinations(),
|
||||
SourceRepos: opts.sources,
|
||||
SignatureKeys: opts.GetSignatureKeys(),
|
||||
OrphanedResources: getOrphanedResourcesSettings(c, opts),
|
||||
},
|
||||
}
|
||||
}
|
||||
proj, err := cmdutil.ConstructAppProj(fileURL, args, opts, c)
|
||||
errors.CheckError(err)
|
||||
|
||||
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
|
||||
defer argoio.Close(conn)
|
||||
_, err := projIf.Create(context.Background(), &projectpkg.ProjectCreateRequest{Project: &proj, Upsert: upsert})
|
||||
_, err = projIf.Create(context.Background(), &projectpkg.ProjectCreateRequest{Project: proj, Upsert: upsert})
|
||||
errors.CheckError(err)
|
||||
},
|
||||
}
|
||||
@@ -203,14 +105,14 @@ func NewProjectCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
addProjFlags(command, &opts)
|
||||
cmdutil.AddProjFlags(command, &opts)
|
||||
return command
|
||||
}
|
||||
|
||||
// NewProjectSetCommand returns a new instance of an `argocd proj set` command
|
||||
func NewProjectSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
opts projectOpts
|
||||
opts cmdutil.ProjectOpts
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "set PROJECT",
|
||||
@@ -232,15 +134,15 @@ func NewProjectSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
|
||||
visited++
|
||||
switch f.Name {
|
||||
case "description":
|
||||
proj.Spec.Description = opts.description
|
||||
proj.Spec.Description = opts.Description
|
||||
case "dest":
|
||||
proj.Spec.Destinations = opts.GetDestinations()
|
||||
case "src":
|
||||
proj.Spec.SourceRepos = opts.sources
|
||||
proj.Spec.SourceRepos = opts.Sources
|
||||
case "signature-keys":
|
||||
proj.Spec.SignatureKeys = opts.GetSignatureKeys()
|
||||
case "orphaned-resources", "orphaned-resources-warn":
|
||||
proj.Spec.OrphanedResources = getOrphanedResourcesSettings(c, opts)
|
||||
proj.Spec.OrphanedResources = cmdutil.GetOrphanedResourcesSettings(c, opts)
|
||||
}
|
||||
})
|
||||
if visited == 0 {
|
||||
@@ -253,7 +155,7 @@ func NewProjectSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
|
||||
errors.CheckError(err)
|
||||
},
|
||||
}
|
||||
addProjFlags(command, &opts)
|
||||
cmdutil.AddProjFlags(command, &opts)
|
||||
return command
|
||||
}
|
||||
|
||||
@@ -564,9 +466,9 @@ func modifyResourceListCmd(cmdUse, cmdDesc string, clientOpts *argocdclient.Clie
|
||||
defaultList string
|
||||
)
|
||||
if namespacedList {
|
||||
defaultList = "black"
|
||||
defaultList = "deny"
|
||||
} else {
|
||||
defaultList = "white"
|
||||
defaultList = "allow"
|
||||
}
|
||||
var command = &cobra.Command{
|
||||
Use: cmdUse,
|
||||
@@ -582,24 +484,24 @@ func modifyResourceListCmd(cmdUse, cmdDesc string, clientOpts *argocdclient.Clie
|
||||
|
||||
proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
|
||||
errors.CheckError(err)
|
||||
var list, white, black *[]metav1.GroupKind
|
||||
var list, allowList, denyList *[]metav1.GroupKind
|
||||
var listAction, listDesc string
|
||||
var add bool
|
||||
if namespacedList {
|
||||
white, black = &proj.Spec.NamespaceResourceWhitelist, &proj.Spec.NamespaceResourceBlacklist
|
||||
allowList, denyList = &proj.Spec.NamespaceResourceWhitelist, &proj.Spec.NamespaceResourceBlacklist
|
||||
listDesc = "namespaced"
|
||||
} else {
|
||||
white, black = &proj.Spec.ClusterResourceWhitelist, &proj.Spec.ClusterResourceBlacklist
|
||||
allowList, denyList = &proj.Spec.ClusterResourceWhitelist, &proj.Spec.ClusterResourceBlacklist
|
||||
listDesc = "cluster"
|
||||
}
|
||||
|
||||
if listType == "white" {
|
||||
list = white
|
||||
listAction = "whitelisted"
|
||||
if (listType == "allow") || (listType == "white") {
|
||||
list = allowList
|
||||
listAction = "allowed"
|
||||
add = allow
|
||||
} else {
|
||||
list = black
|
||||
listAction = "blacklisted"
|
||||
list = denyList
|
||||
listAction = "denied"
|
||||
add = !allow
|
||||
}
|
||||
|
||||
@@ -609,35 +511,35 @@ func modifyResourceListCmd(cmdUse, cmdDesc string, clientOpts *argocdclient.Clie
|
||||
}
|
||||
},
|
||||
}
|
||||
command.Flags().StringVarP(&listType, "list", "l", defaultList, "Use blacklist or whitelist. This can only be 'white' or 'black'")
|
||||
command.Flags().StringVarP(&listType, "list", "l", defaultList, "Use deny list or allow list. This can only be 'allow' or 'deny'")
|
||||
return command
|
||||
}
|
||||
|
||||
// NewProjectAllowNamespaceResourceCommand returns a new instance of an `deny-cluster-resources` command
|
||||
func NewProjectAllowNamespaceResourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
use := "allow-namespace-resource PROJECT GROUP KIND"
|
||||
desc := "Removes a namespaced API resource from the blacklist or add a namespaced API resource to the whitelist"
|
||||
desc := "Removes a namespaced API resource from the deny list or add a namespaced API resource to the allow list"
|
||||
return modifyResourceListCmd(use, desc, clientOpts, true, true)
|
||||
}
|
||||
|
||||
// NewProjectDenyNamespaceResourceCommand returns a new instance of an `argocd proj deny-namespace-resource` command
|
||||
func NewProjectDenyNamespaceResourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
use := "deny-namespace-resource PROJECT GROUP KIND"
|
||||
desc := "Adds a namespaced API resource to the blacklist or removes a namespaced API resource from the whitelist"
|
||||
desc := "Adds a namespaced API resource to the deny list or removes a namespaced API resource from the allow list"
|
||||
return modifyResourceListCmd(use, desc, clientOpts, false, true)
|
||||
}
|
||||
|
||||
// NewProjectDenyClusterResourceCommand returns a new instance of an `deny-cluster-resource` command
|
||||
func NewProjectDenyClusterResourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
use := "deny-cluster-resource PROJECT GROUP KIND"
|
||||
desc := "Removes a cluster-scoped API resource from the whitelist and adds it to blacklist"
|
||||
desc := "Removes a cluster-scoped API resource from the allow list and adds it to deny list"
|
||||
return modifyResourceListCmd(use, desc, clientOpts, false, false)
|
||||
}
|
||||
|
||||
// NewProjectAllowClusterResourceCommand returns a new instance of an `argocd proj allow-cluster-resource` command
|
||||
func NewProjectAllowClusterResourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
use := "allow-cluster-resource PROJECT GROUP KIND"
|
||||
desc := "Adds a cluster-scoped API resource to the whitelist and removes it from blacklist"
|
||||
desc := "Adds a cluster-scoped API resource to the allow list and removes it from deny list"
|
||||
return modifyResourceListCmd(use, desc, clientOpts, true, false)
|
||||
}
|
||||
|
||||
@@ -800,7 +702,7 @@ func printProjectLine(w io.Writer, p *v1alpha1.AppProject) {
|
||||
}
|
||||
|
||||
func printProject(p *v1alpha1.AppProject) {
|
||||
const printProjFmtStr = "%-34s%s\n"
|
||||
const printProjFmtStr = "%-29s%s\n"
|
||||
|
||||
fmt.Printf(printProjFmtStr, "Name:", p.Name)
|
||||
fmt.Printf(printProjFmtStr, "Description:", p.Spec.Description)
|
||||
@@ -825,22 +727,22 @@ func printProject(p *v1alpha1.AppProject) {
|
||||
fmt.Printf(printProjFmtStr, "", p.Spec.SourceRepos[i])
|
||||
}
|
||||
|
||||
// Print whitelisted cluster resources
|
||||
// Print allowed cluster resources
|
||||
cwl0 := "<none>"
|
||||
if len(p.Spec.ClusterResourceWhitelist) > 0 {
|
||||
cwl0 = fmt.Sprintf("%s/%s", p.Spec.ClusterResourceWhitelist[0].Group, p.Spec.ClusterResourceWhitelist[0].Kind)
|
||||
}
|
||||
fmt.Printf(printProjFmtStr, "Whitelisted Cluster Resources:", cwl0)
|
||||
fmt.Printf(printProjFmtStr, "Allowed Cluster Resources:", cwl0)
|
||||
for i := 1; i < len(p.Spec.ClusterResourceWhitelist); i++ {
|
||||
fmt.Printf(printProjFmtStr, "", fmt.Sprintf("%s/%s", p.Spec.ClusterResourceWhitelist[i].Group, p.Spec.ClusterResourceWhitelist[i].Kind))
|
||||
}
|
||||
|
||||
// Print blacklisted namespaced resources
|
||||
// Print denied namespaced resources
|
||||
rbl0 := "<none>"
|
||||
if len(p.Spec.NamespaceResourceBlacklist) > 0 {
|
||||
rbl0 = fmt.Sprintf("%s/%s", p.Spec.NamespaceResourceBlacklist[0].Group, p.Spec.NamespaceResourceBlacklist[0].Kind)
|
||||
}
|
||||
fmt.Printf(printProjFmtStr, "Blacklisted Namespaced Resources:", rbl0)
|
||||
fmt.Printf(printProjFmtStr, "Denied Namespaced Resources:", rbl0)
|
||||
for i := 1; i < len(p.Spec.NamespaceResourceBlacklist); i++ {
|
||||
fmt.Printf(printProjFmtStr, "", fmt.Sprintf("%s/%s", p.Spec.NamespaceResourceBlacklist[i].Group, p.Spec.NamespaceResourceBlacklist[i].Kind))
|
||||
}
|
||||
|
||||
@@ -6,15 +6,18 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/io"
|
||||
timeutil "github.com/argoproj/pkg/time"
|
||||
jwtgo "github.com/dgrijalva/jwt-go/v4"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
|
||||
projectpkg "github.com/argoproj/argo-cd/pkg/apiclient/project"
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/io"
|
||||
"github.com/argoproj/argo-cd/util/jwt"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -36,6 +39,7 @@ func NewProjectRoleCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
|
||||
roleCommand.AddCommand(NewProjectRoleCreateCommand(clientOpts))
|
||||
roleCommand.AddCommand(NewProjectRoleDeleteCommand(clientOpts))
|
||||
roleCommand.AddCommand(NewProjectRoleCreateTokenCommand(clientOpts))
|
||||
roleCommand.AddCommand(NewProjectRoleListTokensCommand(clientOpts))
|
||||
roleCommand.AddCommand(NewProjectRoleDeleteTokenCommand(clientOpts))
|
||||
roleCommand.AddCommand(NewProjectRoleAddPolicyCommand(clientOpts))
|
||||
roleCommand.AddCommand(NewProjectRoleRemovePolicyCommand(clientOpts))
|
||||
@@ -195,14 +199,25 @@ func NewProjectRoleDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
|
||||
return command
|
||||
}
|
||||
|
||||
func tokenTimeToString(t int64) string {
|
||||
tokenTimeToString := "Never"
|
||||
if t > 0 {
|
||||
tokenTimeToString = time.Unix(t, 0).Format(time.RFC3339)
|
||||
}
|
||||
return tokenTimeToString
|
||||
}
|
||||
|
||||
// NewProjectRoleCreateTokenCommand returns a new instance of an `argocd proj role create-token` command
|
||||
func NewProjectRoleCreateTokenCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
expiresIn string
|
||||
expiresIn string
|
||||
outputTokenOnly bool
|
||||
tokenID string
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "create-token PROJECT ROLE-NAME",
|
||||
Short: "Create a project token",
|
||||
Use: "create-token PROJECT ROLE-NAME",
|
||||
Short: "Create a project token",
|
||||
Aliases: []string{"token-create"},
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
c.HelpFunc()(c, args)
|
||||
@@ -212,23 +227,109 @@ func NewProjectRoleCreateTokenCommand(clientOpts *argocdclient.ClientOptions) *c
|
||||
roleName := args[1]
|
||||
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
|
||||
defer io.Close(conn)
|
||||
if expiresIn == "" {
|
||||
expiresIn = "0s"
|
||||
}
|
||||
duration, err := timeutil.ParseDuration(expiresIn)
|
||||
errors.CheckError(err)
|
||||
token, err := projIf.CreateToken(context.Background(), &projectpkg.ProjectTokenCreateRequest{Project: projName, Role: roleName, ExpiresIn: int64(duration.Seconds())})
|
||||
tokenResponse, err := projIf.CreateToken(context.Background(), &projectpkg.ProjectTokenCreateRequest{
|
||||
Project: projName,
|
||||
Role: roleName,
|
||||
ExpiresIn: int64(duration.Seconds()),
|
||||
Id: tokenID,
|
||||
})
|
||||
errors.CheckError(err)
|
||||
fmt.Println(token.Token)
|
||||
|
||||
token, err := jwtgo.Parse(tokenResponse.Token, nil)
|
||||
if token == nil {
|
||||
err = fmt.Errorf("received malformed token %v", err)
|
||||
errors.CheckError(err)
|
||||
return
|
||||
}
|
||||
|
||||
claims := token.Claims.(jwtgo.MapClaims)
|
||||
issuedAt, _ := jwt.IssuedAt(claims)
|
||||
expiresAt := int64(jwt.Float64Field(claims, "exp"))
|
||||
id := jwt.StringField(claims, "jti")
|
||||
subject := jwt.StringField(claims, "sub")
|
||||
|
||||
if !outputTokenOnly {
|
||||
fmt.Printf("Create token succeeded for %s.\n", subject)
|
||||
fmt.Printf(" ID: %s\n Issued At: %s\n Expires At: %s\n",
|
||||
id, tokenTimeToString(issuedAt), tokenTimeToString(expiresAt),
|
||||
)
|
||||
fmt.Println(" Token: " + tokenResponse.Token)
|
||||
} else {
|
||||
fmt.Println(tokenResponse.Token)
|
||||
}
|
||||
},
|
||||
}
|
||||
command.Flags().StringVarP(&expiresIn, "expires-in", "e", "0s", "Duration before the token will expire. (Default: No expiration)")
|
||||
command.Flags().StringVarP(&expiresIn, "expires-in", "e", "",
|
||||
"Duration before the token will expire, eg \"12h\", \"7d\". (Default: No expiration)",
|
||||
)
|
||||
command.Flags().StringVarP(&tokenID, "id", "i", "", "Token unique identifier. (Default: Random UUID)")
|
||||
command.Flags().BoolVarP(&outputTokenOnly, "token-only", "t", false, "Output token only - for use in scripts.")
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
func NewProjectRoleListTokensCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
useUnixTime bool
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "list-tokens PROJECT ROLE-NAME",
|
||||
Short: "List tokens for a given role.",
|
||||
Aliases: []string{"list-token", "token-list"},
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
projName := args[0]
|
||||
roleName := args[1]
|
||||
|
||||
conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie()
|
||||
defer io.Close(conn)
|
||||
|
||||
proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
|
||||
errors.CheckError(err)
|
||||
role, _, err := proj.GetRoleByName(roleName)
|
||||
errors.CheckError(err)
|
||||
|
||||
if len(role.JWTTokens) == 0 {
|
||||
fmt.Printf("No tokens for %s.%s\n", projName, roleName)
|
||||
return
|
||||
}
|
||||
|
||||
writer := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
|
||||
_, err = fmt.Fprintf(writer, "ID\tISSUED AT\tEXPIRES AT\n")
|
||||
errors.CheckError(err)
|
||||
|
||||
tokenRowFormat := "%s\t%v\t%v\n"
|
||||
for _, token := range role.JWTTokens {
|
||||
if useUnixTime {
|
||||
_, _ = fmt.Fprintf(writer, tokenRowFormat, token.ID, token.IssuedAt, token.ExpiresAt)
|
||||
} else {
|
||||
_, _ = fmt.Fprintf(writer, tokenRowFormat, token.ID, tokenTimeToString(token.IssuedAt), tokenTimeToString(token.ExpiresAt))
|
||||
}
|
||||
}
|
||||
err = writer.Flush()
|
||||
errors.CheckError(err)
|
||||
},
|
||||
}
|
||||
command.Flags().BoolVarP(&useUnixTime, "unixtime", "u", false,
|
||||
"Print timestamps as Unix time instead of converting. Useful for piping into delete-token.",
|
||||
)
|
||||
return command
|
||||
}
|
||||
|
||||
// NewProjectRoleDeleteTokenCommand returns a new instance of an `argocd proj role delete-token` command
|
||||
func NewProjectRoleDeleteTokenCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var command = &cobra.Command{
|
||||
Use: "delete-token PROJECT ROLE-NAME ISSUED-AT",
|
||||
Short: "Delete a project token",
|
||||
Use: "delete-token PROJECT ROLE-NAME ISSUED-AT",
|
||||
Short: "Delete a project token",
|
||||
Aliases: []string{"token-delete", "remove-token"},
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
c.HelpFunc()(c, args)
|
||||
|
||||
@@ -8,13 +8,13 @@ import (
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/io"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
|
||||
projectpkg "github.com/argoproj/argo-cd/pkg/apiclient/project"
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/io"
|
||||
)
|
||||
|
||||
// NewProjectWindowsCommand returns a new instance of the `argocd proj windows` command
|
||||
|
||||
@@ -5,14 +5,14 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
argoio "github.com/argoproj/gitops-engine/pkg/utils/io"
|
||||
"github.com/coreos/go-oidc"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
|
||||
settingspkg "github.com/argoproj/argo-cd/pkg/apiclient/settings"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
argoio "github.com/argoproj/argo-cd/util/io"
|
||||
"github.com/argoproj/argo-cd/util/localconfig"
|
||||
"github.com/argoproj/argo-cd/util/session"
|
||||
)
|
||||
@@ -49,6 +49,7 @@ func NewReloginCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Comm
|
||||
GRPCWeb: globalClientOpts.GRPCWeb,
|
||||
GRPCWebRootPath: globalClientOpts.GRPCWebRootPath,
|
||||
PlainText: configCtx.Server.PlainText,
|
||||
Headers: globalClientOpts.Headers,
|
||||
}
|
||||
acdClient := argocdclient.NewClientOrDie(&clientOpts)
|
||||
claims, err := configCtx.User.Claims()
|
||||
|
||||
@@ -7,17 +7,17 @@ import (
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/io"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
cmdutil "github.com/argoproj/argo-cd/cmd/util"
|
||||
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
|
||||
repositorypkg "github.com/argoproj/argo-cd/pkg/apiclient/repository"
|
||||
appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/git"
|
||||
"github.com/argoproj/argo-cd/util/io"
|
||||
)
|
||||
|
||||
// NewRepoCommand returns a new instance of an `argocd repo` command
|
||||
@@ -41,22 +41,15 @@ func NewRepoCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
// NewRepoAddCommand returns a new instance of an `argocd repo add` command
|
||||
func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
repo appsv1.Repository
|
||||
upsert bool
|
||||
sshPrivateKeyPath string
|
||||
insecureIgnoreHostKey bool
|
||||
insecureSkipServerVerification bool
|
||||
tlsClientCertPath string
|
||||
tlsClientCertKeyPath string
|
||||
enableLfs bool
|
||||
repoOpts cmdutil.RepoOptions
|
||||
)
|
||||
|
||||
// For better readability and easier formatting
|
||||
var repoAddExamples = ` # Add a Git repository via SSH using a private key for authentication, ignoring the server's host key:
|
||||
argocd repo add git@git.example.com:repos/repo --insecure-ignore-host-key --ssh-private-key-path ~/id_rsa
|
||||
argocd repo add git@git.example.com:repos/repo --insecure-ignore-host-key --ssh-private-key-path ~/id_rsa
|
||||
|
||||
# Add a Git repository via SSH on a non-default port - need to use ssh:// style URLs here
|
||||
argocd repo add ssh://git@git.example.com:2222/repos/repo --ssh-private-key-path ~/id_rsa
|
||||
# Add a Git repository via SSH on a non-default port - need to use ssh:// style URLs here
|
||||
argocd repo add ssh://git@git.example.com:2222/repos/repo --ssh-private-key-path ~/id_rsa
|
||||
|
||||
# Add a private Git repository via HTTPS using username/password and TLS client certificates:
|
||||
argocd repo add https://git.example.com/repos/repo --username git --password secret --tls-client-cert-path ~/mycert.crt --tls-client-cert-key-path ~/mycert.key
|
||||
@@ -69,6 +62,15 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
|
||||
# Add a private Helm repository named 'stable' via HTTPS
|
||||
argocd repo add https://kubernetes-charts.storage.googleapis.com --type helm --name stable --username test --password test
|
||||
|
||||
# Add a private Helm OCI-based repository named 'stable' via HTTPS
|
||||
argocd repo add helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type helm --name stable --enable-oci --username test --password test
|
||||
|
||||
# Add a private Git repository on GitHub.com via GitHub App
|
||||
argocd repo add https://git.example.com/repos/repo --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem
|
||||
|
||||
# Add a private Git repository on GitHub Enterprise via GitHub App
|
||||
argocd repo add https://ghe.example.com/repos/repo --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem --github-app-enterprise-base-url https://ghe.example.com/api/v3
|
||||
`
|
||||
|
||||
var command = &cobra.Command{
|
||||
@@ -82,16 +84,16 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
}
|
||||
|
||||
// Repository URL
|
||||
repo.Repo = args[0]
|
||||
repoOpts.Repo.Repo = args[0]
|
||||
|
||||
// Specifying ssh-private-key-path is only valid for SSH repositories
|
||||
if sshPrivateKeyPath != "" {
|
||||
if ok, _ := git.IsSSHURL(repo.Repo); ok {
|
||||
keyData, err := ioutil.ReadFile(sshPrivateKeyPath)
|
||||
if repoOpts.SshPrivateKeyPath != "" {
|
||||
if ok, _ := git.IsSSHURL(repoOpts.Repo.Repo); ok {
|
||||
keyData, err := ioutil.ReadFile(repoOpts.SshPrivateKeyPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
repo.SSHPrivateKey = string(keyData)
|
||||
repoOpts.Repo.SSHPrivateKey = string(keyData)
|
||||
} else {
|
||||
err := fmt.Errorf("--ssh-private-key-path is only supported for SSH repositories.")
|
||||
errors.CheckError(err)
|
||||
@@ -100,34 +102,50 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
|
||||
// tls-client-cert-path and tls-client-cert-key-key-path must always be
|
||||
// specified together
|
||||
if (tlsClientCertPath != "" && tlsClientCertKeyPath == "") || (tlsClientCertPath == "" && tlsClientCertKeyPath != "") {
|
||||
if (repoOpts.TlsClientCertPath != "" && repoOpts.TlsClientCertKeyPath == "") || (repoOpts.TlsClientCertPath == "" && repoOpts.TlsClientCertKeyPath != "") {
|
||||
err := fmt.Errorf("--tls-client-cert-path and --tls-client-cert-key-path must be specified together")
|
||||
errors.CheckError(err)
|
||||
}
|
||||
|
||||
// Specifying tls-client-cert-path is only valid for HTTPS repositories
|
||||
if tlsClientCertPath != "" {
|
||||
if git.IsHTTPSURL(repo.Repo) {
|
||||
tlsCertData, err := ioutil.ReadFile(tlsClientCertPath)
|
||||
if repoOpts.TlsClientCertPath != "" {
|
||||
if git.IsHTTPSURL(repoOpts.Repo.Repo) {
|
||||
tlsCertData, err := ioutil.ReadFile(repoOpts.TlsClientCertPath)
|
||||
errors.CheckError(err)
|
||||
tlsCertKey, err := ioutil.ReadFile(tlsClientCertKeyPath)
|
||||
tlsCertKey, err := ioutil.ReadFile(repoOpts.TlsClientCertKeyPath)
|
||||
errors.CheckError(err)
|
||||
repo.TLSClientCertData = string(tlsCertData)
|
||||
repo.TLSClientCertKey = string(tlsCertKey)
|
||||
repoOpts.Repo.TLSClientCertData = string(tlsCertData)
|
||||
repoOpts.Repo.TLSClientCertKey = string(tlsCertKey)
|
||||
} else {
|
||||
err := fmt.Errorf("--tls-client-cert-path is only supported for HTTPS repositories")
|
||||
errors.CheckError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Specifying github-app-private-key-path is only valid for HTTPS repositories
|
||||
if repoOpts.GithubAppPrivateKeyPath != "" {
|
||||
if git.IsHTTPSURL(repoOpts.Repo.Repo) {
|
||||
githubAppPrivateKey, err := ioutil.ReadFile(repoOpts.GithubAppPrivateKeyPath)
|
||||
errors.CheckError(err)
|
||||
repoOpts.Repo.GithubAppPrivateKey = string(githubAppPrivateKey)
|
||||
} else {
|
||||
err := fmt.Errorf("--github-app-private-key-path is only supported for HTTPS repositories")
|
||||
errors.CheckError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Set repository connection properties only when creating repository, not
|
||||
// when creating repository credentials.
|
||||
// InsecureIgnoreHostKey is deprecated and only here for backwards compat
|
||||
repo.InsecureIgnoreHostKey = insecureIgnoreHostKey
|
||||
repo.Insecure = insecureSkipServerVerification
|
||||
repo.EnableLFS = enableLfs
|
||||
repoOpts.Repo.InsecureIgnoreHostKey = repoOpts.InsecureIgnoreHostKey
|
||||
repoOpts.Repo.Insecure = repoOpts.InsecureSkipServerVerification
|
||||
repoOpts.Repo.EnableLFS = repoOpts.EnableLfs
|
||||
repoOpts.Repo.EnableOCI = repoOpts.EnableOci
|
||||
repoOpts.Repo.GithubAppId = repoOpts.GithubAppId
|
||||
repoOpts.Repo.GithubAppInstallationId = repoOpts.GithubAppInstallationId
|
||||
repoOpts.Repo.GitHubAppEnterpriseBaseURL = repoOpts.GitHubAppEnterpriseBaseURL
|
||||
|
||||
if repo.Type == "helm" && repo.Name == "" {
|
||||
if repoOpts.Repo.Type == "helm" && repoOpts.Repo.Name == "" {
|
||||
errors.CheckError(fmt.Errorf("Must specify --name for repos of type 'helm'"))
|
||||
}
|
||||
|
||||
@@ -136,8 +154,8 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
|
||||
// If the user set a username, but didn't supply password via --password,
|
||||
// then we prompt for it
|
||||
if repo.Username != "" && repo.Password == "" {
|
||||
repo.Password = cli.PromptPassword(repo.Password)
|
||||
if repoOpts.Repo.Username != "" && repoOpts.Repo.Password == "" {
|
||||
repoOpts.Repo.Password = cli.PromptPassword(repoOpts.Repo.Password)
|
||||
}
|
||||
|
||||
// We let the server check access to the repository before adding it. If
|
||||
@@ -148,40 +166,36 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
// are high that we do not have the given URL pointing to a valid Git
|
||||
// repo anyway.
|
||||
repoAccessReq := repositorypkg.RepoAccessQuery{
|
||||
Repo: repo.Repo,
|
||||
Type: repo.Type,
|
||||
Name: repo.Name,
|
||||
Username: repo.Username,
|
||||
Password: repo.Password,
|
||||
SshPrivateKey: repo.SSHPrivateKey,
|
||||
TlsClientCertData: repo.TLSClientCertData,
|
||||
TlsClientCertKey: repo.TLSClientCertKey,
|
||||
Insecure: repo.IsInsecure(),
|
||||
Repo: repoOpts.Repo.Repo,
|
||||
Type: repoOpts.Repo.Type,
|
||||
Name: repoOpts.Repo.Name,
|
||||
Username: repoOpts.Repo.Username,
|
||||
Password: repoOpts.Repo.Password,
|
||||
SshPrivateKey: repoOpts.Repo.SSHPrivateKey,
|
||||
TlsClientCertData: repoOpts.Repo.TLSClientCertData,
|
||||
TlsClientCertKey: repoOpts.Repo.TLSClientCertKey,
|
||||
Insecure: repoOpts.Repo.IsInsecure(),
|
||||
EnableOci: repoOpts.Repo.EnableOCI,
|
||||
GithubAppPrivateKey: repoOpts.Repo.GithubAppPrivateKey,
|
||||
GithubAppID: repoOpts.Repo.GithubAppId,
|
||||
GithubAppInstallationID: repoOpts.Repo.GithubAppInstallationId,
|
||||
GithubAppEnterpriseBaseUrl: repoOpts.Repo.GitHubAppEnterpriseBaseURL,
|
||||
}
|
||||
_, err := repoIf.ValidateAccess(context.Background(), &repoAccessReq)
|
||||
errors.CheckError(err)
|
||||
|
||||
repoCreateReq := repositorypkg.RepoCreateRequest{
|
||||
Repo: &repo,
|
||||
Upsert: upsert,
|
||||
Repo: &repoOpts.Repo,
|
||||
Upsert: repoOpts.Upsert,
|
||||
}
|
||||
|
||||
createdRepo, err := repoIf.Create(context.Background(), &repoCreateReq)
|
||||
errors.CheckError(err)
|
||||
fmt.Printf("repository '%s' added\n", createdRepo.Repo)
|
||||
fmt.Printf("Repository '%s' added\n", createdRepo.Repo)
|
||||
},
|
||||
}
|
||||
command.Flags().StringVar(&repo.Type, "type", common.DefaultRepoType, "type of the repository, \"git\" or \"helm\"")
|
||||
command.Flags().StringVar(&repo.Name, "name", "", "name of the repository, mandatory for repositories of type helm")
|
||||
command.Flags().StringVar(&repo.Username, "username", "", "username to the repository")
|
||||
command.Flags().StringVar(&repo.Password, "password", "", "password to the repository")
|
||||
command.Flags().StringVar(&sshPrivateKeyPath, "ssh-private-key-path", "", "path to the private ssh key (e.g. ~/.ssh/id_rsa)")
|
||||
command.Flags().StringVar(&tlsClientCertPath, "tls-client-cert-path", "", "path to the TLS client cert (must be PEM format)")
|
||||
command.Flags().StringVar(&tlsClientCertKeyPath, "tls-client-cert-key-path", "", "path to the TLS client cert's key path (must be PEM format)")
|
||||
command.Flags().BoolVar(&insecureIgnoreHostKey, "insecure-ignore-host-key", false, "disables SSH strict host key checking (deprecated, use --insecure-skip-server-verification instead)")
|
||||
command.Flags().BoolVar(&insecureSkipServerVerification, "insecure-skip-server-verification", false, "disables server certificate and host key checks")
|
||||
command.Flags().BoolVar(&enableLfs, "enable-lfs", false, "enable git-lfs (Large File Support) on this repository")
|
||||
command.Flags().BoolVar(&upsert, "upsert", false, "Override an existing repository with the same name even if the spec differs")
|
||||
command.Flags().BoolVar(&repoOpts.Upsert, "upsert", false, "Override an existing repository with the same name even if the spec differs")
|
||||
cmdutil.AddRepoFlags(command, &repoOpts)
|
||||
return command
|
||||
}
|
||||
|
||||
@@ -200,6 +214,7 @@ func NewRepoRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
|
||||
for _, repoURL := range args {
|
||||
_, err := repoIf.Delete(context.Background(), &repositorypkg.RepoQuery{Repo: repoURL})
|
||||
errors.CheckError(err)
|
||||
fmt.Printf("Repository '%s' removed\n", repoURL)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -209,7 +224,7 @@ func NewRepoRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
|
||||
// Print table of repo info
|
||||
func printRepoTable(repos appsv1.Repositories) {
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
_, _ = fmt.Fprintf(w, "TYPE\tNAME\tREPO\tINSECURE\tLFS\tCREDS\tSTATUS\tMESSAGE\n")
|
||||
_, _ = fmt.Fprintf(w, "TYPE\tNAME\tREPO\tINSECURE\tOCI\tLFS\tCREDS\tSTATUS\tMESSAGE\n")
|
||||
for _, r := range repos {
|
||||
var hasCreds string
|
||||
if !r.HasCredentials() {
|
||||
@@ -221,7 +236,7 @@ func printRepoTable(repos appsv1.Repositories) {
|
||||
hasCreds = "true"
|
||||
}
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%v\t%s\t%s\t%s\n", r.Type, r.Name, r.Repo, r.IsInsecure(), r.EnableLFS, hasCreds, r.ConnectionState.Status, r.ConnectionState.Message)
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%v\t%v\t%s\t%s\t%s\n", r.Type, r.Name, r.Repo, r.IsInsecure(), r.EnableOCI, r.EnableLFS, hasCreds, r.ConnectionState.Status, r.ConnectionState.Message)
|
||||
}
|
||||
_ = w.Flush()
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ import (
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/io"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@@ -16,7 +14,9 @@ import (
|
||||
repocredspkg "github.com/argoproj/argo-cd/pkg/apiclient/repocreds"
|
||||
appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/git"
|
||||
"github.com/argoproj/argo-cd/util/io"
|
||||
)
|
||||
|
||||
// NewRepoCredsCommand returns a new instance of an `argocd repocreds` command
|
||||
@@ -39,11 +39,12 @@ func NewRepoCredsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
|
||||
// NewRepoCredsAddCommand returns a new instance of an `argocd repocreds add` command
|
||||
func NewRepoCredsAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
repo appsv1.RepoCreds
|
||||
upsert bool
|
||||
sshPrivateKeyPath string
|
||||
tlsClientCertPath string
|
||||
tlsClientCertKeyPath string
|
||||
repo appsv1.RepoCreds
|
||||
upsert bool
|
||||
sshPrivateKeyPath string
|
||||
tlsClientCertPath string
|
||||
tlsClientCertKeyPath string
|
||||
githubAppPrivateKeyPath string
|
||||
)
|
||||
|
||||
// For better readability and easier formatting
|
||||
@@ -52,6 +53,12 @@ func NewRepoCredsAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comma
|
||||
|
||||
# Add credentials with SSH private key authentication to use for all repositories under ssh://git@git.example.com/repos
|
||||
argocd repocreds add ssh://git@git.example.com/repos/ --ssh-private-key-path ~/.ssh/id_rsa
|
||||
|
||||
# Add credentials with GitHub App authentication to use for all repositories under https://github.com/repos
|
||||
argocd repocreds add https://github.com/repos/ --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem
|
||||
|
||||
# Add credentials with GitHub App authentication to use for all repositories under https://ghe.example.com/repos
|
||||
argocd repocreds add https://ghe.example.com/repos/ --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem --github-app-enterprise-base-url https://ghe.example.com/api/v3
|
||||
`
|
||||
|
||||
var command = &cobra.Command{
|
||||
@@ -103,6 +110,18 @@ func NewRepoCredsAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comma
|
||||
}
|
||||
}
|
||||
|
||||
// Specifying github-app-private-key-path is only valid for HTTPS repositories
|
||||
if githubAppPrivateKeyPath != "" {
|
||||
if git.IsHTTPSURL(repo.URL) {
|
||||
githubAppPrivateKey, err := ioutil.ReadFile(githubAppPrivateKeyPath)
|
||||
errors.CheckError(err)
|
||||
repo.GithubAppPrivateKey = string(githubAppPrivateKey)
|
||||
} else {
|
||||
err := fmt.Errorf("--github-app-private-key-path is only supported for HTTPS repositories")
|
||||
errors.CheckError(err)
|
||||
}
|
||||
}
|
||||
|
||||
conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoCredsClientOrDie()
|
||||
defer io.Close(conn)
|
||||
|
||||
@@ -119,7 +138,7 @@ func NewRepoCredsAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comma
|
||||
|
||||
createdRepo, err := repoIf.CreateRepositoryCredentials(context.Background(), &repoCreateReq)
|
||||
errors.CheckError(err)
|
||||
fmt.Printf("repository credentials for '%s' added\n", createdRepo.URL)
|
||||
fmt.Printf("Repository credentials for '%s' added\n", createdRepo.URL)
|
||||
},
|
||||
}
|
||||
command.Flags().StringVar(&repo.Username, "username", "", "username to the repository")
|
||||
@@ -127,6 +146,10 @@ func NewRepoCredsAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comma
|
||||
command.Flags().StringVar(&sshPrivateKeyPath, "ssh-private-key-path", "", "path to the private ssh key (e.g. ~/.ssh/id_rsa)")
|
||||
command.Flags().StringVar(&tlsClientCertPath, "tls-client-cert-path", "", "path to the TLS client cert (must be PEM format)")
|
||||
command.Flags().StringVar(&tlsClientCertKeyPath, "tls-client-cert-key-path", "", "path to the TLS client cert's key path (must be PEM format)")
|
||||
command.Flags().Int64Var(&repo.GithubAppId, "github-app-id", 0, "id of the GitHub Application")
|
||||
command.Flags().Int64Var(&repo.GithubAppInstallationId, "github-app-installation-id", 0, "installation id of the GitHub Application")
|
||||
command.Flags().StringVar(&githubAppPrivateKeyPath, "github-app-private-key-path", "", "private key of the GitHub Application")
|
||||
command.Flags().StringVar(&repo.GitHubAppEnterpriseBaseURL, "github-app-enterprise-base-url", "", "base url to use when using GitHub Enterprise (e.g. https://ghe.example.com/api/v3")
|
||||
command.Flags().BoolVar(&upsert, "upsert", false, "Override an existing repository with the same name even if the spec differs")
|
||||
return command
|
||||
}
|
||||
@@ -146,6 +169,7 @@ func NewRepoCredsRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
for _, repoURL := range args {
|
||||
_, err := repoIf.DeleteRepositoryCredentials(context.Background(), &repocredspkg.RepoCredsDeleteRequest{Url: repoURL})
|
||||
errors.CheckError(err)
|
||||
fmt.Printf("Repository credentials for '%s' removed\n", repoURL)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
cmdutil "github.com/argoproj/argo-cd/cmd/util"
|
||||
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
|
||||
"github.com/argoproj/argo-cd/util/cli"
|
||||
"github.com/argoproj/argo-cd/util/config"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/localconfig"
|
||||
)
|
||||
|
||||
@@ -15,14 +16,9 @@ func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
}
|
||||
|
||||
var (
|
||||
logFormat string
|
||||
logLevel string
|
||||
)
|
||||
|
||||
func initConfig() {
|
||||
cli.SetLogFormat(logFormat)
|
||||
cli.SetLogLevel(logLevel)
|
||||
cli.SetLogFormat(cmdutil.LogFormat)
|
||||
cli.SetLogLevel(cmdutil.LogLevel)
|
||||
}
|
||||
|
||||
// NewCommand returns a new instance of an argocd command
|
||||
@@ -38,6 +34,7 @@ func NewCommand() *cobra.Command {
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
c.HelpFunc()(c, args)
|
||||
},
|
||||
DisableAutoGenTag: true,
|
||||
}
|
||||
|
||||
command.AddCommand(NewCompletionCommand())
|
||||
@@ -67,8 +64,8 @@ func NewCommand() *cobra.Command {
|
||||
command.PersistentFlags().StringVar(&clientOpts.AuthToken, "auth-token", config.GetFlag("auth-token", ""), "Authentication token")
|
||||
command.PersistentFlags().BoolVar(&clientOpts.GRPCWeb, "grpc-web", config.GetBoolFlag("grpc-web"), "Enables gRPC-web protocol. Useful if Argo CD server is behind proxy which does not support HTTP2.")
|
||||
command.PersistentFlags().StringVar(&clientOpts.GRPCWebRootPath, "grpc-web-root-path", config.GetFlag("grpc-web-root-path", ""), "Enables gRPC-web protocol. Useful if Argo CD server is behind proxy which does not support HTTP2. Set web root.")
|
||||
command.PersistentFlags().StringVar(&logFormat, "logformat", config.GetFlag("logformat", "text"), "Set the logging format. One of: text|json")
|
||||
command.PersistentFlags().StringVar(&logLevel, "loglevel", config.GetFlag("loglevel", "info"), "Set the logging level. One of: debug|info|warn|error")
|
||||
command.PersistentFlags().StringVar(&cmdutil.LogFormat, "logformat", config.GetFlag("logformat", "text"), "Set the logging format. One of: text|json")
|
||||
command.PersistentFlags().StringVar(&cmdutil.LogLevel, "loglevel", config.GetFlag("loglevel", "info"), "Set the logging level. One of: debug|info|warn|error")
|
||||
command.PersistentFlags().StringSliceVarP(&clientOpts.Headers, "header", "H", []string{}, "Sets additional header to all requests made by Argo CD CLI. (Can be repeated multiple times to add multiple headers, also supports comma separated headers)")
|
||||
command.PersistentFlags().BoolVar(&clientOpts.PortForward, "port-forward", config.GetBoolFlag("port-forward"), "Connect to a random argocd-server port using port forwarding")
|
||||
command.PersistentFlags().StringVar(&clientOpts.PortForwardNamespace, "port-forward-namespace", config.GetFlag("port-forward-namespace", ""), "Namespace name which should be used for port forwarding")
|
||||
|
||||
@@ -8,12 +8,11 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
argoio "github.com/argoproj/gitops-engine/pkg/utils/io"
|
||||
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
|
||||
"github.com/argoproj/argo-cd/pkg/apiclient/version"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
argoio "github.com/argoproj/argo-cd/util/io"
|
||||
)
|
||||
|
||||
// NewVersionCmd returns a new `version` command to be used as a sub-command to root
|
||||
@@ -117,17 +116,40 @@ func printServerVersion(version *version.VersionMessage, short bool) {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf(" BuildDate: %s\n", version.BuildDate)
|
||||
fmt.Printf(" GitCommit: %s\n", version.GitCommit)
|
||||
fmt.Printf(" GitTreeState: %s\n", version.GitTreeState)
|
||||
if version.BuildDate != "" {
|
||||
fmt.Printf(" BuildDate: %s\n", version.BuildDate)
|
||||
}
|
||||
if version.GitCommit != "" {
|
||||
fmt.Printf(" GitCommit: %s\n", version.GitCommit)
|
||||
}
|
||||
if version.GitTreeState != "" {
|
||||
fmt.Printf(" GitTreeState: %s\n", version.GitTreeState)
|
||||
}
|
||||
if version.GitTag != "" {
|
||||
fmt.Printf(" GitTag: %s\n", version.GitTag)
|
||||
}
|
||||
fmt.Printf(" GoVersion: %s\n", version.GoVersion)
|
||||
fmt.Printf(" Compiler: %s\n", version.Compiler)
|
||||
fmt.Printf(" Platform: %s\n", version.Platform)
|
||||
fmt.Printf(" Ksonnet Version: %s\n", version.KsonnetVersion)
|
||||
fmt.Printf(" Kustomize Version: %s\n", version.KustomizeVersion)
|
||||
fmt.Printf(" Helm Version: %s\n", version.HelmVersion)
|
||||
fmt.Printf(" Kubectl Version: %s\n", version.KubectlVersion)
|
||||
if version.GoVersion != "" {
|
||||
fmt.Printf(" GoVersion: %s\n", version.GoVersion)
|
||||
}
|
||||
if version.Compiler != "" {
|
||||
fmt.Printf(" Compiler: %s\n", version.Compiler)
|
||||
}
|
||||
if version.Platform != "" {
|
||||
fmt.Printf(" Platform: %s\n", version.Platform)
|
||||
}
|
||||
if version.KsonnetVersion != "" {
|
||||
fmt.Printf(" Ksonnet Version: %s\n", version.KsonnetVersion)
|
||||
}
|
||||
if version.KustomizeVersion != "" {
|
||||
fmt.Printf(" Kustomize Version: %s\n", version.KustomizeVersion)
|
||||
}
|
||||
if version.HelmVersion != "" {
|
||||
fmt.Printf(" Helm Version: %s\n", version.HelmVersion)
|
||||
}
|
||||
if version.KubectlVersion != "" {
|
||||
fmt.Printf(" Kubectl Version: %s\n", version.KubectlVersion)
|
||||
}
|
||||
if version.JsonnetVersion != "" {
|
||||
fmt.Printf(" Jsonnet Version: %s\n", version.JsonnetVersion)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
|
||||
commands "github.com/argoproj/argo-cd/cmd/argocd/commands"
|
||||
|
||||
// load the gcp plugin (required to authenticate against GKE clusters).
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
// load the oidc plugin (required to authenticate with OpenID Connect).
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := commands.NewCommand().Execute()
|
||||
errors.CheckError(err)
|
||||
}
|
||||
64
cmd/main.go
Normal file
64
cmd/main.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
appcontroller "github.com/argoproj/argo-cd/cmd/argocd-application-controller/commands"
|
||||
dex "github.com/argoproj/argo-cd/cmd/argocd-dex/commands"
|
||||
reposerver "github.com/argoproj/argo-cd/cmd/argocd-repo-server/commands"
|
||||
apiserver "github.com/argoproj/argo-cd/cmd/argocd-server/commands"
|
||||
util "github.com/argoproj/argo-cd/cmd/argocd-util/commands"
|
||||
cli "github.com/argoproj/argo-cd/cmd/argocd/commands"
|
||||
)
|
||||
|
||||
const (
|
||||
binaryNameEnv = "ARGOCD_BINARY_NAME"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var command *cobra.Command
|
||||
|
||||
binaryName := filepath.Base(os.Args[0])
|
||||
if val := os.Getenv(binaryNameEnv); val != "" {
|
||||
binaryName = val
|
||||
}
|
||||
switch binaryName {
|
||||
case "argocd", "argocd-linux-amd64", "argocd-darwin-amd64", "argocd-windows-amd64.exe":
|
||||
command = cli.NewCommand()
|
||||
case "argocd-util", "argocd-util-linux-amd64", "argocd-util-darwin-amd64", "argocd-util-windows-amd64.exe":
|
||||
command = util.NewCommand()
|
||||
case "argocd-server":
|
||||
command = apiserver.NewCommand()
|
||||
case "argocd-application-controller":
|
||||
command = appcontroller.NewCommand()
|
||||
case "argocd-repo-server":
|
||||
command = reposerver.NewCommand()
|
||||
case "argocd-dex":
|
||||
command = dex.NewCommand()
|
||||
default:
|
||||
if len(os.Args[1:]) > 0 {
|
||||
// trying to guess between argocd and argocd-util by matching sub command
|
||||
for _, cmd := range []*cobra.Command{cli.NewCommand(), util.NewCommand()} {
|
||||
if _, _, err := cmd.Find(os.Args[1:]); err == nil {
|
||||
command = cmd
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if command == nil {
|
||||
fmt.Printf("Unknown binary name '%s'.Use '%s' environment variable to specify required binary name "+
|
||||
"(possible values 'argocd' or 'argocd-util').\n", binaryName, binaryNameEnv)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if err := command.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
558
cmd/util/app.go
Normal file
558
cmd/util/app.go
Normal file
@@ -0,0 +1,558 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application"
|
||||
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/util/config"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/text/label"
|
||||
)
|
||||
|
||||
type AppOptions struct {
|
||||
repoURL string
|
||||
appPath string
|
||||
chart string
|
||||
env string
|
||||
revision string
|
||||
revisionHistoryLimit int
|
||||
destName string
|
||||
destServer string
|
||||
destNamespace string
|
||||
Parameters []string
|
||||
valuesFiles []string
|
||||
values string
|
||||
releaseName string
|
||||
helmSets []string
|
||||
helmSetStrings []string
|
||||
helmSetFiles []string
|
||||
helmVersion string
|
||||
project string
|
||||
syncPolicy string
|
||||
syncOptions []string
|
||||
autoPrune bool
|
||||
selfHeal bool
|
||||
allowEmpty bool
|
||||
namePrefix string
|
||||
nameSuffix string
|
||||
directoryRecurse bool
|
||||
configManagementPlugin string
|
||||
jsonnetTlaStr []string
|
||||
jsonnetTlaCode []string
|
||||
jsonnetExtVarStr []string
|
||||
jsonnetExtVarCode []string
|
||||
jsonnetLibs []string
|
||||
kustomizeImages []string
|
||||
kustomizeVersion string
|
||||
kustomizeCommonLabels []string
|
||||
kustomizeCommonAnnotations []string
|
||||
pluginEnvs []string
|
||||
Validate bool
|
||||
directoryExclude string
|
||||
directoryInclude string
|
||||
}
|
||||
|
||||
func AddAppFlags(command *cobra.Command, opts *AppOptions) {
|
||||
command.Flags().StringVar(&opts.repoURL, "repo", "", "Repository URL, ignored if a file is set")
|
||||
command.Flags().StringVar(&opts.appPath, "path", "", "Path in repository to the app directory, ignored if a file is set")
|
||||
command.Flags().StringVar(&opts.chart, "helm-chart", "", "Helm Chart name")
|
||||
command.Flags().StringVar(&opts.env, "env", "", "Application environment to monitor")
|
||||
command.Flags().StringVar(&opts.revision, "revision", "", "The tracking source branch, tag, commit or Helm chart version the application will sync to")
|
||||
command.Flags().IntVar(&opts.revisionHistoryLimit, "revision-history-limit", common.RevisionHistoryLimit, "How many items to keep in revision history")
|
||||
command.Flags().StringVar(&opts.destServer, "dest-server", "", "K8s cluster URL (e.g. https://kubernetes.default.svc)")
|
||||
command.Flags().StringVar(&opts.destName, "dest-name", "", "K8s cluster Name (e.g. minikube)")
|
||||
command.Flags().StringVar(&opts.destNamespace, "dest-namespace", "", "K8s target namespace (overrides the namespace specified in the ksonnet app.yaml)")
|
||||
command.Flags().StringArrayVarP(&opts.Parameters, "parameter", "p", []string{}, "set a parameter override (e.g. -p guestbook=image=example/guestbook:latest)")
|
||||
command.Flags().StringArrayVar(&opts.valuesFiles, "values", []string{}, "Helm values file(s) to use")
|
||||
command.Flags().StringVar(&opts.values, "values-literal-file", "", "Filename or URL to import as a literal Helm values block")
|
||||
command.Flags().StringVar(&opts.releaseName, "release-name", "", "Helm release-name")
|
||||
command.Flags().StringVar(&opts.helmVersion, "helm-version", "", "Helm version")
|
||||
command.Flags().StringArrayVar(&opts.helmSets, "helm-set", []string{}, "Helm set values on the command line (can be repeated to set several values: --helm-set key1=val1 --helm-set key2=val2)")
|
||||
command.Flags().StringArrayVar(&opts.helmSetStrings, "helm-set-string", []string{}, "Helm set STRING values on the command line (can be repeated to set several values: --helm-set-string key1=val1 --helm-set-string key2=val2)")
|
||||
command.Flags().StringArrayVar(&opts.helmSetFiles, "helm-set-file", []string{}, "Helm set values from respective files specified via the command line (can be repeated to set several values: --helm-set-file key1=path1 --helm-set-file key2=path2)")
|
||||
command.Flags().StringVar(&opts.project, "project", "", "Application project name")
|
||||
command.Flags().StringVar(&opts.syncPolicy, "sync-policy", "", "Set the sync policy (one of: none, automated (aliases of automated: auto, automatic))")
|
||||
command.Flags().StringArrayVar(&opts.syncOptions, "sync-option", []string{}, "Add or remove a sync option, e.g add `Prune=false`. Remove using `!` prefix, e.g. `!Prune=false`")
|
||||
command.Flags().BoolVar(&opts.autoPrune, "auto-prune", false, "Set automatic pruning when sync is automated")
|
||||
command.Flags().BoolVar(&opts.selfHeal, "self-heal", false, "Set self healing when sync is automated")
|
||||
command.Flags().BoolVar(&opts.allowEmpty, "allow-empty", false, "Set allow zero live resources when sync is automated")
|
||||
command.Flags().StringVar(&opts.namePrefix, "nameprefix", "", "Kustomize nameprefix")
|
||||
command.Flags().StringVar(&opts.nameSuffix, "namesuffix", "", "Kustomize namesuffix")
|
||||
command.Flags().StringVar(&opts.kustomizeVersion, "kustomize-version", "", "Kustomize version")
|
||||
command.Flags().BoolVar(&opts.directoryRecurse, "directory-recurse", false, "Recurse directory")
|
||||
command.Flags().StringVar(&opts.configManagementPlugin, "config-management-plugin", "", "Config management plugin name")
|
||||
command.Flags().StringArrayVar(&opts.jsonnetTlaStr, "jsonnet-tla-str", []string{}, "Jsonnet top level string arguments")
|
||||
command.Flags().StringArrayVar(&opts.jsonnetTlaCode, "jsonnet-tla-code", []string{}, "Jsonnet top level code arguments")
|
||||
command.Flags().StringArrayVar(&opts.jsonnetExtVarStr, "jsonnet-ext-var-str", []string{}, "Jsonnet string ext var")
|
||||
command.Flags().StringArrayVar(&opts.jsonnetExtVarCode, "jsonnet-ext-var-code", []string{}, "Jsonnet ext var")
|
||||
command.Flags().StringArrayVar(&opts.jsonnetLibs, "jsonnet-libs", []string{}, "Additional jsonnet libs (prefixed by repoRoot)")
|
||||
command.Flags().StringArrayVar(&opts.kustomizeImages, "kustomize-image", []string{}, "Kustomize images (e.g. --kustomize-image node:8.15.0 --kustomize-image mysql=mariadb,alpine@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d)")
|
||||
command.Flags().StringArrayVar(&opts.pluginEnvs, "plugin-env", []string{}, "Additional plugin envs")
|
||||
command.Flags().BoolVar(&opts.Validate, "validate", true, "Validation of repo and cluster")
|
||||
command.Flags().StringArrayVar(&opts.kustomizeCommonLabels, "kustomize-common-label", []string{}, "Set common labels in Kustomize")
|
||||
command.Flags().StringArrayVar(&opts.kustomizeCommonAnnotations, "kustomize-common-annotation", []string{}, "Set common labels in Kustomize")
|
||||
command.Flags().StringVar(&opts.directoryExclude, "directory-exclude", "", "Set glob expression used to exclude files from application source path")
|
||||
command.Flags().StringVar(&opts.directoryInclude, "directory-include", "", "Set glob expression used to include files from application source path")
|
||||
}
|
||||
|
||||
func SetAppSpecOptions(flags *pflag.FlagSet, spec *argoappv1.ApplicationSpec, appOpts *AppOptions) int {
|
||||
visited := 0
|
||||
flags.Visit(func(f *pflag.Flag) {
|
||||
visited++
|
||||
switch f.Name {
|
||||
case "repo":
|
||||
spec.Source.RepoURL = appOpts.repoURL
|
||||
case "path":
|
||||
spec.Source.Path = appOpts.appPath
|
||||
case "helm-chart":
|
||||
spec.Source.Chart = appOpts.chart
|
||||
case "env":
|
||||
setKsonnetOpt(&spec.Source, &appOpts.env)
|
||||
case "revision":
|
||||
spec.Source.TargetRevision = appOpts.revision
|
||||
case "revision-history-limit":
|
||||
i := int64(appOpts.revisionHistoryLimit)
|
||||
spec.RevisionHistoryLimit = &i
|
||||
case "values":
|
||||
setHelmOpt(&spec.Source, helmOpts{valueFiles: appOpts.valuesFiles})
|
||||
case "values-literal-file":
|
||||
var data []byte
|
||||
|
||||
// read uri
|
||||
parsedURL, err := url.ParseRequestURI(appOpts.values)
|
||||
if err != nil || !(parsedURL.Scheme == "http" || parsedURL.Scheme == "https") {
|
||||
data, err = ioutil.ReadFile(appOpts.values)
|
||||
} else {
|
||||
data, err = config.ReadRemoteFile(appOpts.values)
|
||||
}
|
||||
errors.CheckError(err)
|
||||
setHelmOpt(&spec.Source, helmOpts{values: string(data)})
|
||||
case "release-name":
|
||||
setHelmOpt(&spec.Source, helmOpts{releaseName: appOpts.releaseName})
|
||||
case "helm-version":
|
||||
setHelmOpt(&spec.Source, helmOpts{version: appOpts.helmVersion})
|
||||
case "helm-set":
|
||||
setHelmOpt(&spec.Source, helmOpts{helmSets: appOpts.helmSets})
|
||||
case "helm-set-string":
|
||||
setHelmOpt(&spec.Source, helmOpts{helmSetStrings: appOpts.helmSetStrings})
|
||||
case "helm-set-file":
|
||||
setHelmOpt(&spec.Source, helmOpts{helmSetFiles: appOpts.helmSetFiles})
|
||||
case "directory-recurse":
|
||||
if spec.Source.Directory != nil {
|
||||
spec.Source.Directory.Recurse = appOpts.directoryRecurse
|
||||
} else {
|
||||
spec.Source.Directory = &argoappv1.ApplicationSourceDirectory{Recurse: appOpts.directoryRecurse}
|
||||
}
|
||||
case "directory-exclude":
|
||||
if spec.Source.Directory != nil {
|
||||
spec.Source.Directory.Exclude = appOpts.directoryExclude
|
||||
} else {
|
||||
spec.Source.Directory = &argoappv1.ApplicationSourceDirectory{Exclude: appOpts.directoryExclude}
|
||||
}
|
||||
case "directory-include":
|
||||
if spec.Source.Directory != nil {
|
||||
spec.Source.Directory.Include = appOpts.directoryInclude
|
||||
} else {
|
||||
spec.Source.Directory = &argoappv1.ApplicationSourceDirectory{Include: appOpts.directoryInclude}
|
||||
}
|
||||
case "config-management-plugin":
|
||||
spec.Source.Plugin = &argoappv1.ApplicationSourcePlugin{Name: appOpts.configManagementPlugin}
|
||||
case "dest-name":
|
||||
spec.Destination.Name = appOpts.destName
|
||||
case "dest-server":
|
||||
spec.Destination.Server = appOpts.destServer
|
||||
case "dest-namespace":
|
||||
spec.Destination.Namespace = appOpts.destNamespace
|
||||
case "project":
|
||||
spec.Project = appOpts.project
|
||||
case "nameprefix":
|
||||
setKustomizeOpt(&spec.Source, kustomizeOpts{namePrefix: appOpts.namePrefix})
|
||||
case "namesuffix":
|
||||
setKustomizeOpt(&spec.Source, kustomizeOpts{nameSuffix: appOpts.nameSuffix})
|
||||
case "kustomize-image":
|
||||
setKustomizeOpt(&spec.Source, kustomizeOpts{images: appOpts.kustomizeImages})
|
||||
case "kustomize-version":
|
||||
setKustomizeOpt(&spec.Source, kustomizeOpts{version: appOpts.kustomizeVersion})
|
||||
case "kustomize-common-label":
|
||||
parsedLabels, err := label.Parse(appOpts.kustomizeCommonLabels)
|
||||
errors.CheckError(err)
|
||||
setKustomizeOpt(&spec.Source, kustomizeOpts{commonLabels: parsedLabels})
|
||||
case "kustomize-common-annotation":
|
||||
parsedAnnotations, err := label.Parse(appOpts.kustomizeCommonAnnotations)
|
||||
errors.CheckError(err)
|
||||
setKustomizeOpt(&spec.Source, kustomizeOpts{commonAnnotations: parsedAnnotations})
|
||||
case "jsonnet-tla-str":
|
||||
setJsonnetOpt(&spec.Source, appOpts.jsonnetTlaStr, false)
|
||||
case "jsonnet-tla-code":
|
||||
setJsonnetOpt(&spec.Source, appOpts.jsonnetTlaCode, true)
|
||||
case "jsonnet-ext-var-str":
|
||||
setJsonnetOptExtVar(&spec.Source, appOpts.jsonnetExtVarStr, false)
|
||||
case "jsonnet-ext-var-code":
|
||||
setJsonnetOptExtVar(&spec.Source, appOpts.jsonnetExtVarCode, true)
|
||||
case "jsonnet-libs":
|
||||
setJsonnetOptLibs(&spec.Source, appOpts.jsonnetLibs)
|
||||
case "plugin-env":
|
||||
setPluginOptEnvs(&spec.Source, appOpts.pluginEnvs)
|
||||
case "sync-policy":
|
||||
switch appOpts.syncPolicy {
|
||||
case "none":
|
||||
if spec.SyncPolicy != nil {
|
||||
spec.SyncPolicy.Automated = nil
|
||||
}
|
||||
if spec.SyncPolicy.IsZero() {
|
||||
spec.SyncPolicy = nil
|
||||
}
|
||||
case "automated", "automatic", "auto":
|
||||
if spec.SyncPolicy == nil {
|
||||
spec.SyncPolicy = &argoappv1.SyncPolicy{}
|
||||
}
|
||||
spec.SyncPolicy.Automated = &argoappv1.SyncPolicyAutomated{}
|
||||
default:
|
||||
log.Fatalf("Invalid sync-policy: %s", appOpts.syncPolicy)
|
||||
}
|
||||
case "sync-option":
|
||||
if spec.SyncPolicy == nil {
|
||||
spec.SyncPolicy = &argoappv1.SyncPolicy{}
|
||||
}
|
||||
for _, option := range appOpts.syncOptions {
|
||||
// `!` means remove the option
|
||||
if strings.HasPrefix(option, "!") {
|
||||
option = strings.TrimPrefix(option, "!")
|
||||
spec.SyncPolicy.SyncOptions = spec.SyncPolicy.SyncOptions.RemoveOption(option)
|
||||
} else {
|
||||
spec.SyncPolicy.SyncOptions = spec.SyncPolicy.SyncOptions.AddOption(option)
|
||||
}
|
||||
}
|
||||
if spec.SyncPolicy.IsZero() {
|
||||
spec.SyncPolicy = nil
|
||||
}
|
||||
}
|
||||
})
|
||||
if flags.Changed("auto-prune") {
|
||||
if spec.SyncPolicy == nil || spec.SyncPolicy.Automated == nil {
|
||||
log.Fatal("Cannot set --auto-prune: application not configured with automatic sync")
|
||||
}
|
||||
spec.SyncPolicy.Automated.Prune = appOpts.autoPrune
|
||||
}
|
||||
if flags.Changed("self-heal") {
|
||||
if spec.SyncPolicy == nil || spec.SyncPolicy.Automated == nil {
|
||||
log.Fatal("Cannot set --self-heal: application not configured with automatic sync")
|
||||
}
|
||||
spec.SyncPolicy.Automated.SelfHeal = appOpts.selfHeal
|
||||
}
|
||||
if flags.Changed("allow-empty") {
|
||||
if spec.SyncPolicy == nil || spec.SyncPolicy.Automated == nil {
|
||||
log.Fatal("Cannot set --allow-empty: application not configured with automatic sync")
|
||||
}
|
||||
spec.SyncPolicy.Automated.AllowEmpty = appOpts.allowEmpty
|
||||
}
|
||||
|
||||
return visited
|
||||
}
|
||||
|
||||
func setKsonnetOpt(src *argoappv1.ApplicationSource, env *string) {
|
||||
if src.Ksonnet == nil {
|
||||
src.Ksonnet = &argoappv1.ApplicationSourceKsonnet{}
|
||||
}
|
||||
if env != nil {
|
||||
src.Ksonnet.Environment = *env
|
||||
}
|
||||
if src.Ksonnet.IsZero() {
|
||||
src.Ksonnet = nil
|
||||
}
|
||||
}
|
||||
|
||||
type kustomizeOpts struct {
|
||||
namePrefix string
|
||||
nameSuffix string
|
||||
images []string
|
||||
version string
|
||||
commonLabels map[string]string
|
||||
commonAnnotations map[string]string
|
||||
}
|
||||
|
||||
func setKustomizeOpt(src *argoappv1.ApplicationSource, opts kustomizeOpts) {
|
||||
if src.Kustomize == nil {
|
||||
src.Kustomize = &argoappv1.ApplicationSourceKustomize{}
|
||||
}
|
||||
if opts.version != "" {
|
||||
src.Kustomize.Version = opts.version
|
||||
}
|
||||
if opts.namePrefix != "" {
|
||||
src.Kustomize.NamePrefix = opts.namePrefix
|
||||
}
|
||||
if opts.nameSuffix != "" {
|
||||
src.Kustomize.NameSuffix = opts.nameSuffix
|
||||
}
|
||||
if opts.commonLabels != nil {
|
||||
src.Kustomize.CommonLabels = opts.commonLabels
|
||||
}
|
||||
if opts.commonAnnotations != nil {
|
||||
src.Kustomize.CommonAnnotations = opts.commonAnnotations
|
||||
}
|
||||
for _, image := range opts.images {
|
||||
src.Kustomize.MergeImage(argoappv1.KustomizeImage(image))
|
||||
}
|
||||
if src.Kustomize.IsZero() {
|
||||
src.Kustomize = nil
|
||||
}
|
||||
}
|
||||
|
||||
func setPluginOptEnvs(src *argoappv1.ApplicationSource, envs []string) {
|
||||
if src.Plugin == nil {
|
||||
src.Plugin = &argoappv1.ApplicationSourcePlugin{}
|
||||
}
|
||||
|
||||
for _, text := range envs {
|
||||
e, err := argoappv1.NewEnvEntry(text)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
src.Plugin.AddEnvEntry(e)
|
||||
}
|
||||
}
|
||||
|
||||
type helmOpts struct {
|
||||
valueFiles []string
|
||||
values string
|
||||
releaseName string
|
||||
version string
|
||||
helmSets []string
|
||||
helmSetStrings []string
|
||||
helmSetFiles []string
|
||||
}
|
||||
|
||||
func setHelmOpt(src *argoappv1.ApplicationSource, opts helmOpts) {
|
||||
if src.Helm == nil {
|
||||
src.Helm = &argoappv1.ApplicationSourceHelm{}
|
||||
}
|
||||
if len(opts.valueFiles) > 0 {
|
||||
src.Helm.ValueFiles = opts.valueFiles
|
||||
}
|
||||
if len(opts.values) > 0 {
|
||||
src.Helm.Values = opts.values
|
||||
}
|
||||
if opts.releaseName != "" {
|
||||
src.Helm.ReleaseName = opts.releaseName
|
||||
}
|
||||
if opts.version != "" {
|
||||
src.Helm.Version = opts.version
|
||||
}
|
||||
for _, text := range opts.helmSets {
|
||||
p, err := argoappv1.NewHelmParameter(text, false)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
src.Helm.AddParameter(*p)
|
||||
}
|
||||
for _, text := range opts.helmSetStrings {
|
||||
p, err := argoappv1.NewHelmParameter(text, true)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
src.Helm.AddParameter(*p)
|
||||
}
|
||||
for _, text := range opts.helmSetFiles {
|
||||
p, err := argoappv1.NewHelmFileParameter(text)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
src.Helm.AddFileParameter(*p)
|
||||
}
|
||||
if src.Helm.IsZero() {
|
||||
src.Helm = nil
|
||||
}
|
||||
}
|
||||
|
||||
func setJsonnetOpt(src *argoappv1.ApplicationSource, tlaParameters []string, code bool) {
|
||||
if src.Directory == nil {
|
||||
src.Directory = &argoappv1.ApplicationSourceDirectory{}
|
||||
}
|
||||
for _, j := range tlaParameters {
|
||||
src.Directory.Jsonnet.TLAs = append(src.Directory.Jsonnet.TLAs, argoappv1.NewJsonnetVar(j, code))
|
||||
}
|
||||
}
|
||||
|
||||
func setJsonnetOptExtVar(src *argoappv1.ApplicationSource, jsonnetExtVar []string, code bool) {
|
||||
if src.Directory == nil {
|
||||
src.Directory = &argoappv1.ApplicationSourceDirectory{}
|
||||
}
|
||||
for _, j := range jsonnetExtVar {
|
||||
src.Directory.Jsonnet.ExtVars = append(src.Directory.Jsonnet.ExtVars, argoappv1.NewJsonnetVar(j, code))
|
||||
}
|
||||
}
|
||||
|
||||
func setJsonnetOptLibs(src *argoappv1.ApplicationSource, libs []string) {
|
||||
if src.Directory == nil {
|
||||
src.Directory = &argoappv1.ApplicationSourceDirectory{}
|
||||
}
|
||||
src.Directory.Jsonnet.Libs = append(src.Directory.Jsonnet.Libs, libs...)
|
||||
}
|
||||
|
||||
// SetParameterOverrides updates an existing or appends a new parameter override in the application
|
||||
// If the app is a ksonnet app, then parameters are expected to be in the form: component=param=value
|
||||
// Otherwise, the app is assumed to be a helm app and is expected to be in the form:
|
||||
// param=value
|
||||
func SetParameterOverrides(app *argoappv1.Application, parameters []string) {
|
||||
if len(parameters) == 0 {
|
||||
return
|
||||
}
|
||||
var sourceType argoappv1.ApplicationSourceType
|
||||
if st, _ := app.Spec.Source.ExplicitType(); st != nil {
|
||||
sourceType = *st
|
||||
} else if app.Status.SourceType != "" {
|
||||
sourceType = app.Status.SourceType
|
||||
} else {
|
||||
// HACK: we don't know the source type, so make an educated guess based on the supplied
|
||||
// parameter string. This code handles the corner case where app doesn't exist yet, and the
|
||||
// command is something like: `argocd app create MYAPP -p foo=bar`
|
||||
// This logic is not foolproof, but when ksonnet is deprecated, this will no longer matter
|
||||
// since helm will remain as the only source type which has parameters.
|
||||
if len(strings.SplitN(parameters[0], "=", 3)) == 3 {
|
||||
sourceType = argoappv1.ApplicationSourceTypeKsonnet
|
||||
} else if len(strings.SplitN(parameters[0], "=", 2)) == 2 {
|
||||
sourceType = argoappv1.ApplicationSourceTypeHelm
|
||||
}
|
||||
}
|
||||
|
||||
switch sourceType {
|
||||
case argoappv1.ApplicationSourceTypeKsonnet:
|
||||
if app.Spec.Source.Ksonnet == nil {
|
||||
app.Spec.Source.Ksonnet = &argoappv1.ApplicationSourceKsonnet{}
|
||||
}
|
||||
for _, paramStr := range parameters {
|
||||
parts := strings.SplitN(paramStr, "=", 3)
|
||||
if len(parts) != 3 {
|
||||
log.Fatalf("Expected ksonnet parameter of the form: component=param=value. Received: %s", paramStr)
|
||||
}
|
||||
newParam := argoappv1.KsonnetParameter{
|
||||
Component: parts[0],
|
||||
Name: parts[1],
|
||||
Value: parts[2],
|
||||
}
|
||||
found := false
|
||||
for i, cp := range app.Spec.Source.Ksonnet.Parameters {
|
||||
if cp.Component == newParam.Component && cp.Name == newParam.Name {
|
||||
found = true
|
||||
app.Spec.Source.Ksonnet.Parameters[i] = newParam
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
app.Spec.Source.Ksonnet.Parameters = append(app.Spec.Source.Ksonnet.Parameters, newParam)
|
||||
}
|
||||
}
|
||||
case argoappv1.ApplicationSourceTypeHelm:
|
||||
if app.Spec.Source.Helm == nil {
|
||||
app.Spec.Source.Helm = &argoappv1.ApplicationSourceHelm{}
|
||||
}
|
||||
for _, p := range parameters {
|
||||
newParam, err := argoappv1.NewHelmParameter(p, false)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
continue
|
||||
}
|
||||
app.Spec.Source.Helm.AddParameter(*newParam)
|
||||
}
|
||||
default:
|
||||
log.Fatalf("Parameters can only be set against Ksonnet or Helm applications")
|
||||
}
|
||||
}
|
||||
|
||||
func readAppFromStdin(app *argoappv1.Application) error {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
err := config.UnmarshalReader(reader, &app)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read manifest from stdin: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readAppFromURI(fileURL string, app *argoappv1.Application) error {
|
||||
parsedURL, err := url.ParseRequestURI(fileURL)
|
||||
if err != nil || !(parsedURL.Scheme == "http" || parsedURL.Scheme == "https") {
|
||||
err = config.UnmarshalLocalFile(fileURL, &app)
|
||||
} else {
|
||||
err = config.UnmarshalRemoteFile(fileURL, &app)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func ConstructApp(fileURL, appName string, labels, args []string, appOpts AppOptions, flags *pflag.FlagSet) (*argoappv1.Application, error) {
|
||||
var app argoappv1.Application
|
||||
if fileURL == "-" {
|
||||
// read stdin
|
||||
err := readAppFromStdin(&app)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if fileURL != "" {
|
||||
// read uri
|
||||
err := readAppFromURI(fileURL, &app)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(args) == 1 && args[0] != app.Name {
|
||||
return nil, fmt.Errorf("app name '%s' does not match app spec metadata.name '%s'", args[0], app.Name)
|
||||
}
|
||||
if appName != "" && appName != app.Name {
|
||||
app.Name = appName
|
||||
}
|
||||
if app.Name == "" {
|
||||
return nil, fmt.Errorf("app.Name is empty. --name argument can be used to provide app.Name")
|
||||
}
|
||||
SetAppSpecOptions(flags, &app.Spec, &appOpts)
|
||||
SetParameterOverrides(&app, appOpts.Parameters)
|
||||
mergeLabels(&app, labels)
|
||||
} else {
|
||||
// read arguments
|
||||
if len(args) == 1 {
|
||||
if appName != "" && appName != args[0] {
|
||||
return nil, fmt.Errorf("--name argument '%s' does not match app name %s", appName, args[0])
|
||||
}
|
||||
appName = args[0]
|
||||
}
|
||||
app = argoappv1.Application{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: application.ApplicationKind,
|
||||
APIVersion: application.Group + "/v1alpha1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: appName,
|
||||
},
|
||||
}
|
||||
SetAppSpecOptions(flags, &app.Spec, &appOpts)
|
||||
SetParameterOverrides(&app, appOpts.Parameters)
|
||||
mergeLabels(&app, labels)
|
||||
}
|
||||
return &app, nil
|
||||
}
|
||||
|
||||
func mergeLabels(app *argoappv1.Application, labels []string) {
|
||||
mapLabels, err := label.Parse(labels)
|
||||
errors.CheckError(err)
|
||||
|
||||
mergedLabels := make(map[string]string)
|
||||
|
||||
for name, value := range app.GetLabels() {
|
||||
mergedLabels[name] = value
|
||||
}
|
||||
|
||||
for name, value := range mapLabels {
|
||||
mergedLabels[name] = value
|
||||
}
|
||||
|
||||
app.SetLabels(mergedLabels)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package commands
|
||||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -40,6 +40,49 @@ func Test_setHelmOpt(t *testing.T) {
|
||||
setHelmOpt(&src, helmOpts{helmSetFiles: []string{"foo=bar"}})
|
||||
assert.Equal(t, []v1alpha1.HelmFileParameter{{Name: "foo", Path: "bar"}}, src.Helm.FileParameters)
|
||||
})
|
||||
t.Run("Version", func(t *testing.T) {
|
||||
src := v1alpha1.ApplicationSource{}
|
||||
setHelmOpt(&src, helmOpts{version: "v3"})
|
||||
assert.Equal(t, "v3", src.Helm.Version)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_setKustomizeOpt(t *testing.T) {
|
||||
t.Run("No kustomize", func(t *testing.T) {
|
||||
src := v1alpha1.ApplicationSource{}
|
||||
setKustomizeOpt(&src, kustomizeOpts{})
|
||||
assert.Nil(t, src.Kustomize)
|
||||
})
|
||||
t.Run("Name prefix", func(t *testing.T) {
|
||||
src := v1alpha1.ApplicationSource{}
|
||||
setKustomizeOpt(&src, kustomizeOpts{namePrefix: "test-"})
|
||||
assert.Equal(t, &v1alpha1.ApplicationSourceKustomize{NamePrefix: "test-"}, src.Kustomize)
|
||||
})
|
||||
t.Run("Name suffix", func(t *testing.T) {
|
||||
src := v1alpha1.ApplicationSource{}
|
||||
setKustomizeOpt(&src, kustomizeOpts{nameSuffix: "-test"})
|
||||
assert.Equal(t, &v1alpha1.ApplicationSourceKustomize{NameSuffix: "-test"}, src.Kustomize)
|
||||
})
|
||||
t.Run("Images", func(t *testing.T) {
|
||||
src := v1alpha1.ApplicationSource{}
|
||||
setKustomizeOpt(&src, kustomizeOpts{images: []string{"org/image:v1", "org/image:v2"}})
|
||||
assert.Equal(t, &v1alpha1.ApplicationSourceKustomize{Images: v1alpha1.KustomizeImages{v1alpha1.KustomizeImage("org/image:v2")}}, src.Kustomize)
|
||||
})
|
||||
t.Run("Version", func(t *testing.T) {
|
||||
src := v1alpha1.ApplicationSource{}
|
||||
setKustomizeOpt(&src, kustomizeOpts{version: "v0.1"})
|
||||
assert.Equal(t, &v1alpha1.ApplicationSourceKustomize{Version: "v0.1"}, src.Kustomize)
|
||||
})
|
||||
t.Run("Common labels", func(t *testing.T) {
|
||||
src := v1alpha1.ApplicationSource{}
|
||||
setKustomizeOpt(&src, kustomizeOpts{commonLabels: map[string]string{"foo1": "bar1", "foo2": "bar2"}})
|
||||
assert.Equal(t, &v1alpha1.ApplicationSourceKustomize{CommonLabels: map[string]string{"foo1": "bar1", "foo2": "bar2"}}, src.Kustomize)
|
||||
})
|
||||
t.Run("Common annotations", func(t *testing.T) {
|
||||
src := v1alpha1.ApplicationSource{}
|
||||
setKustomizeOpt(&src, kustomizeOpts{commonAnnotations: map[string]string{"foo1": "bar1", "foo2": "bar2"}})
|
||||
assert.Equal(t, &v1alpha1.ApplicationSourceKustomize{CommonAnnotations: map[string]string{"foo1": "bar1", "foo2": "bar2"}}, src.Kustomize)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_setJsonnetOpt(t *testing.T) {
|
||||
@@ -59,10 +102,22 @@ func Test_setJsonnetOpt(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_setPluginOptEnvs(t *testing.T) {
|
||||
t.Run("PluginEnvs", func(t *testing.T) {
|
||||
src := v1alpha1.ApplicationSource{}
|
||||
setPluginOptEnvs(&src, []string{"FOO=bar"})
|
||||
assert.Equal(t, v1alpha1.EnvEntry{Name: "FOO", Value: "bar"}, *src.Plugin.Env[0])
|
||||
setPluginOptEnvs(&src, []string{"BAR=baz"})
|
||||
assert.Equal(t, v1alpha1.EnvEntry{Name: "BAR", Value: "baz"}, *src.Plugin.Env[1])
|
||||
setPluginOptEnvs(&src, []string{"FOO=baz"})
|
||||
assert.Equal(t, v1alpha1.EnvEntry{Name: "FOO", Value: "baz"}, *src.Plugin.Env[0])
|
||||
})
|
||||
}
|
||||
|
||||
type appOptionsFixture struct {
|
||||
spec *v1alpha1.ApplicationSpec
|
||||
command *cobra.Command
|
||||
options *appOptions
|
||||
options *AppOptions
|
||||
}
|
||||
|
||||
func (f *appOptionsFixture) SetFlag(key, value string) error {
|
||||
@@ -70,7 +125,7 @@ func (f *appOptionsFixture) SetFlag(key, value string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = setAppSpecOptions(f.command.Flags(), f.spec, f.options)
|
||||
_ = SetAppSpecOptions(f.command.Flags(), f.spec, f.options)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -78,9 +133,9 @@ func newAppOptionsFixture() *appOptionsFixture {
|
||||
fixture := &appOptionsFixture{
|
||||
spec: &v1alpha1.ApplicationSpec{},
|
||||
command: &cobra.Command{},
|
||||
options: &appOptions{},
|
||||
options: &AppOptions{},
|
||||
}
|
||||
addAppFlags(fixture.command, fixture.options)
|
||||
AddAppFlags(fixture.command, fixture.options)
|
||||
return fixture
|
||||
}
|
||||
|
||||
132
cmd/util/cluster.go
Normal file
132
cmd/util/cluster.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
)
|
||||
|
||||
func PrintKubeContexts(ca clientcmd.ConfigAccess) {
|
||||
config, err := ca.GetStartingConfig()
|
||||
errors.CheckError(err)
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
defer func() { _ = w.Flush() }()
|
||||
columnNames := []string{"CURRENT", "NAME", "CLUSTER", "SERVER"}
|
||||
_, err = fmt.Fprintf(w, "%s\n", strings.Join(columnNames, "\t"))
|
||||
errors.CheckError(err)
|
||||
|
||||
// sort names so output is deterministic
|
||||
contextNames := make([]string, 0)
|
||||
for name := range config.Contexts {
|
||||
contextNames = append(contextNames, name)
|
||||
}
|
||||
sort.Strings(contextNames)
|
||||
|
||||
if config.Clusters == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, name := range contextNames {
|
||||
// ignore malformed kube config entries
|
||||
context := config.Contexts[name]
|
||||
if context == nil {
|
||||
continue
|
||||
}
|
||||
cluster := config.Clusters[context.Cluster]
|
||||
if cluster == nil {
|
||||
continue
|
||||
}
|
||||
prefix := " "
|
||||
if config.CurrentContext == name {
|
||||
prefix = "*"
|
||||
}
|
||||
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", prefix, name, context.Cluster, cluster.Server)
|
||||
errors.CheckError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func NewCluster(name string, namespaces []string, conf *rest.Config, managerBearerToken string, awsAuthConf *argoappv1.AWSAuthConfig, execProviderConf *argoappv1.ExecProviderConfig) *argoappv1.Cluster {
|
||||
tlsClientConfig := argoappv1.TLSClientConfig{
|
||||
Insecure: conf.TLSClientConfig.Insecure,
|
||||
ServerName: conf.TLSClientConfig.ServerName,
|
||||
CAData: conf.TLSClientConfig.CAData,
|
||||
CertData: conf.TLSClientConfig.CertData,
|
||||
KeyData: conf.TLSClientConfig.KeyData,
|
||||
}
|
||||
if len(conf.TLSClientConfig.CAData) == 0 && conf.TLSClientConfig.CAFile != "" {
|
||||
data, err := ioutil.ReadFile(conf.TLSClientConfig.CAFile)
|
||||
errors.CheckError(err)
|
||||
tlsClientConfig.CAData = data
|
||||
}
|
||||
if len(conf.TLSClientConfig.CertData) == 0 && conf.TLSClientConfig.CertFile != "" {
|
||||
data, err := ioutil.ReadFile(conf.TLSClientConfig.CertFile)
|
||||
errors.CheckError(err)
|
||||
tlsClientConfig.CertData = data
|
||||
}
|
||||
if len(conf.TLSClientConfig.KeyData) == 0 && conf.TLSClientConfig.KeyFile != "" {
|
||||
data, err := ioutil.ReadFile(conf.TLSClientConfig.KeyFile)
|
||||
errors.CheckError(err)
|
||||
tlsClientConfig.KeyData = data
|
||||
}
|
||||
|
||||
clst := argoappv1.Cluster{
|
||||
Server: conf.Host,
|
||||
Name: name,
|
||||
Namespaces: namespaces,
|
||||
Config: argoappv1.ClusterConfig{
|
||||
TLSClientConfig: tlsClientConfig,
|
||||
AWSAuthConfig: awsAuthConf,
|
||||
ExecProviderConfig: execProviderConf,
|
||||
},
|
||||
}
|
||||
|
||||
// Bearer token will preferentially be used for auth if present,
|
||||
// Even in presence of key/cert credentials
|
||||
// So set bearer token only if the key/cert data is absent
|
||||
if len(tlsClientConfig.CertData) == 0 || len(tlsClientConfig.KeyData) == 0 {
|
||||
clst.Config.BearerToken = managerBearerToken
|
||||
}
|
||||
|
||||
return &clst
|
||||
}
|
||||
|
||||
type ClusterOptions struct {
|
||||
InCluster bool
|
||||
Upsert bool
|
||||
ServiceAccount string
|
||||
AwsRoleArn string
|
||||
AwsClusterName string
|
||||
SystemNamespace string
|
||||
Namespaces []string
|
||||
Name string
|
||||
Shard int64
|
||||
ExecProviderCommand string
|
||||
ExecProviderArgs []string
|
||||
ExecProviderEnv map[string]string
|
||||
ExecProviderAPIVersion string
|
||||
ExecProviderInstallHint string
|
||||
}
|
||||
|
||||
func AddClusterFlags(command *cobra.Command, opts *ClusterOptions) {
|
||||
command.Flags().BoolVar(&opts.InCluster, "in-cluster", false, "Indicates Argo CD resides inside this cluster and should connect using the internal k8s hostname (kubernetes.default.svc)")
|
||||
command.Flags().StringVar(&opts.AwsClusterName, "aws-cluster-name", "", "AWS Cluster name if set then aws cli eks token command will be used to access cluster")
|
||||
command.Flags().StringVar(&opts.AwsRoleArn, "aws-role-arn", "", "Optional AWS role arn. If set then AWS IAM Authenticator assumes a role to perform cluster operations instead of the default AWS credential provider chain.")
|
||||
command.Flags().StringArrayVar(&opts.Namespaces, "namespace", nil, "List of namespaces which are allowed to manage")
|
||||
command.Flags().StringVar(&opts.Name, "name", "", "Overwrite the cluster name")
|
||||
command.Flags().Int64Var(&opts.Shard, "shard", -1, "Cluster shard number; inferred from hostname if not set")
|
||||
command.Flags().StringVar(&opts.ExecProviderCommand, "exec-command", "", "Command to run to provide client credentials to the cluster. You may need to build a custom ArgoCD image to ensure the command is available at runtime.")
|
||||
command.Flags().StringArrayVar(&opts.ExecProviderArgs, "exec-command-args", nil, "Arguments to supply to the --exec-command executable")
|
||||
command.Flags().StringToStringVar(&opts.ExecProviderEnv, "exec-command-env", nil, "Environment vars to set when running the --exec-command executable")
|
||||
command.Flags().StringVar(&opts.ExecProviderAPIVersion, "exec-command-api-version", "", "Preferred input version of the ExecInfo for the --exec-command executable")
|
||||
command.Flags().StringVar(&opts.ExecProviderInstallHint, "exec-command-install-hint", "", "Text shown to the user when the --exec-command executable doesn't seem to be present")
|
||||
}
|
||||
63
cmd/util/cluster_test.go
Normal file
63
cmd/util/cluster_test.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
|
||||
func Test_newCluster(t *testing.T) {
|
||||
clusterWithData := NewCluster("test-cluster", []string{"test-namespace"}, &rest.Config{
|
||||
TLSClientConfig: rest.TLSClientConfig{
|
||||
Insecure: false,
|
||||
ServerName: "test-endpoint.example.com",
|
||||
CAData: []byte("test-ca-data"),
|
||||
CertData: []byte("test-cert-data"),
|
||||
KeyData: []byte("test-key-data"),
|
||||
},
|
||||
Host: "test-endpoint.example.com",
|
||||
},
|
||||
"test-bearer-token",
|
||||
&v1alpha1.AWSAuthConfig{},
|
||||
&v1alpha1.ExecProviderConfig{})
|
||||
|
||||
assert.Equal(t, "test-cert-data", string(clusterWithData.Config.CertData))
|
||||
assert.Equal(t, "test-key-data", string(clusterWithData.Config.KeyData))
|
||||
assert.Equal(t, "", clusterWithData.Config.BearerToken)
|
||||
|
||||
clusterWithFiles := NewCluster("test-cluster", []string{"test-namespace"}, &rest.Config{
|
||||
TLSClientConfig: rest.TLSClientConfig{
|
||||
Insecure: false,
|
||||
ServerName: "test-endpoint.example.com",
|
||||
CAData: []byte("test-ca-data"),
|
||||
CertFile: "./testdata/test.cert.pem",
|
||||
KeyFile: "./testdata/test.key.pem",
|
||||
},
|
||||
Host: "test-endpoint.example.com",
|
||||
},
|
||||
"test-bearer-token",
|
||||
&v1alpha1.AWSAuthConfig{},
|
||||
&v1alpha1.ExecProviderConfig{})
|
||||
|
||||
assert.True(t, strings.Contains(string(clusterWithFiles.Config.CertData), "test-cert-data"))
|
||||
assert.True(t, strings.Contains(string(clusterWithFiles.Config.KeyData), "test-key-data"))
|
||||
assert.Equal(t, "", clusterWithFiles.Config.BearerToken)
|
||||
|
||||
clusterWithBearerToken := NewCluster("test-cluster", []string{"test-namespace"}, &rest.Config{
|
||||
TLSClientConfig: rest.TLSClientConfig{
|
||||
Insecure: false,
|
||||
ServerName: "test-endpoint.example.com",
|
||||
CAData: []byte("test-ca-data"),
|
||||
},
|
||||
Host: "test-endpoint.example.com",
|
||||
},
|
||||
"test-bearer-token",
|
||||
&v1alpha1.AWSAuthConfig{},
|
||||
&v1alpha1.ExecProviderConfig{})
|
||||
|
||||
assert.Equal(t, "test-bearer-token", clusterWithBearerToken.Config.BearerToken)
|
||||
}
|
||||
80
cmd/util/common.go
Normal file
80
cmd/util/common.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
)
|
||||
|
||||
var (
|
||||
LogFormat string
|
||||
LogLevel string
|
||||
)
|
||||
|
||||
// PrintResource prints a single resource in YAML or JSON format to stdout according to the output format
|
||||
func PrintResources(resources []interface{}, output string) error {
|
||||
for i, resource := range resources {
|
||||
filteredResource, err := omitFields(resource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resources[i] = filteredResource
|
||||
}
|
||||
|
||||
switch output {
|
||||
case "json":
|
||||
jsonBytes, err := json.MarshalIndent(resources, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(string(jsonBytes))
|
||||
case "yaml":
|
||||
yamlBytes, err := yaml.Marshal(resources)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(yamlBytes))
|
||||
default:
|
||||
return fmt.Errorf("unknown output format: %s", output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// omit fields such as status, creationTimestamp and metadata.namespace in k8s objects
|
||||
func omitFields(resource interface{}) (interface{}, error) {
|
||||
jsonBytes, err := json.Marshal(resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
toMap := make(map[string]interface{})
|
||||
err = json.Unmarshal([]byte(string(jsonBytes)), &toMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
delete(toMap, "status")
|
||||
if v, ok := toMap["metadata"]; ok {
|
||||
if metadata, ok := v.(map[string]interface{}); ok {
|
||||
delete(metadata, "creationTimestamp")
|
||||
delete(metadata, "namespace")
|
||||
}
|
||||
}
|
||||
return toMap, nil
|
||||
}
|
||||
|
||||
// ConvertSecretData converts kubernetes secret's data to stringData
|
||||
func ConvertSecretData(secret *v1.Secret) {
|
||||
secret.Kind = kube.SecretKind
|
||||
secret.APIVersion = "v1"
|
||||
secret.StringData = map[string]string{}
|
||||
for k, v := range secret.Data {
|
||||
secret.StringData[k] = string(v)
|
||||
}
|
||||
secret.Data = map[string][]byte{}
|
||||
}
|
||||
141
cmd/util/project.go
Normal file
141
cmd/util/project.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application"
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/util/config"
|
||||
"github.com/argoproj/argo-cd/util/gpg"
|
||||
)
|
||||
|
||||
type ProjectOpts struct {
|
||||
Description string
|
||||
destinations []string
|
||||
Sources []string
|
||||
SignatureKeys []string
|
||||
orphanedResourcesEnabled bool
|
||||
orphanedResourcesWarn bool
|
||||
}
|
||||
|
||||
func AddProjFlags(command *cobra.Command, opts *ProjectOpts) {
|
||||
command.Flags().StringVarP(&opts.Description, "description", "", "", "Project description")
|
||||
command.Flags().StringArrayVarP(&opts.destinations, "dest", "d", []string{},
|
||||
"Permitted destination server and namespace (e.g. https://192.168.99.100:8443,default)")
|
||||
command.Flags().StringArrayVarP(&opts.Sources, "src", "s", []string{}, "Permitted source repository URL")
|
||||
command.Flags().StringSliceVar(&opts.SignatureKeys, "signature-keys", []string{}, "GnuPG public key IDs for commit signature verification")
|
||||
command.Flags().BoolVar(&opts.orphanedResourcesEnabled, "orphaned-resources", false, "Enables orphaned resources monitoring")
|
||||
command.Flags().BoolVar(&opts.orphanedResourcesWarn, "orphaned-resources-warn", false, "Specifies if applications should have a warning condition when orphaned resources detected")
|
||||
}
|
||||
|
||||
func (opts *ProjectOpts) GetDestinations() []v1alpha1.ApplicationDestination {
|
||||
destinations := make([]v1alpha1.ApplicationDestination, 0)
|
||||
for _, destStr := range opts.destinations {
|
||||
parts := strings.Split(destStr, ",")
|
||||
if len(parts) != 2 {
|
||||
log.Fatalf("Expected destination of the form: server,namespace. Received: %s", destStr)
|
||||
} else {
|
||||
destinations = append(destinations, v1alpha1.ApplicationDestination{
|
||||
Server: parts[0],
|
||||
Namespace: parts[1],
|
||||
})
|
||||
}
|
||||
}
|
||||
return destinations
|
||||
}
|
||||
|
||||
// TODO: Get configured keys and emit warning when a key is specified that is not configured
|
||||
func (opts *ProjectOpts) GetSignatureKeys() []v1alpha1.SignatureKey {
|
||||
signatureKeys := make([]v1alpha1.SignatureKey, 0)
|
||||
for _, keyStr := range opts.SignatureKeys {
|
||||
if !gpg.IsShortKeyID(keyStr) && !gpg.IsLongKeyID(keyStr) {
|
||||
log.Fatalf("'%s' is not a valid GnuPG key ID", keyStr)
|
||||
}
|
||||
signatureKeys = append(signatureKeys, v1alpha1.SignatureKey{KeyID: gpg.KeyID(keyStr)})
|
||||
}
|
||||
return signatureKeys
|
||||
}
|
||||
|
||||
func GetOrphanedResourcesSettings(c *cobra.Command, opts ProjectOpts) *v1alpha1.OrphanedResourcesMonitorSettings {
|
||||
warnChanged := c.Flag("orphaned-resources-warn").Changed
|
||||
if opts.orphanedResourcesEnabled || warnChanged {
|
||||
settings := v1alpha1.OrphanedResourcesMonitorSettings{}
|
||||
if warnChanged {
|
||||
settings.Warn = pointer.BoolPtr(opts.orphanedResourcesWarn)
|
||||
}
|
||||
return &settings
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readProjFromStdin(proj *v1alpha1.AppProject) error {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
err := config.UnmarshalReader(reader, &proj)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read manifest from stdin: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readProjFromURI(fileURL string, proj *v1alpha1.AppProject) error {
|
||||
parsedURL, err := url.ParseRequestURI(fileURL)
|
||||
if err != nil || !(parsedURL.Scheme == "http" || parsedURL.Scheme == "https") {
|
||||
err = config.UnmarshalLocalFile(fileURL, &proj)
|
||||
} else {
|
||||
err = config.UnmarshalRemoteFile(fileURL, &proj)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func ConstructAppProj(fileURL string, args []string, opts ProjectOpts, c *cobra.Command) (*v1alpha1.AppProject, error) {
|
||||
var proj v1alpha1.AppProject
|
||||
if fileURL == "-" {
|
||||
// read stdin
|
||||
err := readProjFromStdin(&proj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if fileURL != "" {
|
||||
// read uri
|
||||
err := readProjFromURI(fileURL, &proj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(args) == 1 && args[0] != proj.Name {
|
||||
return nil, fmt.Errorf("project name '%s' does not match project spec metadata.name '%s'", args[0], proj.Name)
|
||||
}
|
||||
} else {
|
||||
// read arguments
|
||||
if len(args) == 0 {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
projName := args[0]
|
||||
proj = v1alpha1.AppProject{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: application.AppProjectKind,
|
||||
APIVersion: application.Group + "/v1alpha1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{Name: projName},
|
||||
Spec: v1alpha1.AppProjectSpec{
|
||||
Description: opts.Description,
|
||||
Destinations: opts.GetDestinations(),
|
||||
SourceRepos: opts.Sources,
|
||||
SignatureKeys: opts.GetSignatureKeys(),
|
||||
OrphanedResources: GetOrphanedResourcesSettings(c, opts),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &proj, nil
|
||||
}
|
||||
42
cmd/util/repo.go
Normal file
42
cmd/util/repo.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
|
||||
type RepoOptions struct {
|
||||
Repo appsv1.Repository
|
||||
Upsert bool
|
||||
SshPrivateKeyPath string
|
||||
InsecureIgnoreHostKey bool
|
||||
InsecureSkipServerVerification bool
|
||||
TlsClientCertPath string
|
||||
TlsClientCertKeyPath string
|
||||
EnableLfs bool
|
||||
EnableOci bool
|
||||
GithubAppId int64
|
||||
GithubAppInstallationId int64
|
||||
GithubAppPrivateKeyPath string
|
||||
GitHubAppEnterpriseBaseURL string
|
||||
}
|
||||
|
||||
func AddRepoFlags(command *cobra.Command, opts *RepoOptions) {
|
||||
command.Flags().StringVar(&opts.Repo.Type, "type", common.DefaultRepoType, "type of the repository, \"git\" or \"helm\"")
|
||||
command.Flags().StringVar(&opts.Repo.Name, "name", "", "name of the repository, mandatory for repositories of type helm")
|
||||
command.Flags().StringVar(&opts.Repo.Username, "username", "", "username to the repository")
|
||||
command.Flags().StringVar(&opts.Repo.Password, "password", "", "password to the repository")
|
||||
command.Flags().StringVar(&opts.SshPrivateKeyPath, "ssh-private-key-path", "", "path to the private ssh key (e.g. ~/.ssh/id_rsa)")
|
||||
command.Flags().StringVar(&opts.TlsClientCertPath, "tls-client-cert-path", "", "path to the TLS client cert (must be PEM format)")
|
||||
command.Flags().StringVar(&opts.TlsClientCertKeyPath, "tls-client-cert-key-path", "", "path to the TLS client cert's key path (must be PEM format)")
|
||||
command.Flags().BoolVar(&opts.InsecureIgnoreHostKey, "insecure-ignore-host-key", false, "disables SSH strict host key checking (deprecated, use --insecure-skip-server-verification instead)")
|
||||
command.Flags().BoolVar(&opts.InsecureSkipServerVerification, "insecure-skip-server-verification", false, "disables server certificate and host key checks")
|
||||
command.Flags().BoolVar(&opts.EnableLfs, "enable-lfs", false, "enable git-lfs (Large File Support) on this repository")
|
||||
command.Flags().BoolVar(&opts.EnableOci, "enable-oci", false, "enable helm-oci (Helm OCI-Based Repository)")
|
||||
command.Flags().Int64Var(&opts.GithubAppId, "github-app-id", 0, "id of the GitHub Application")
|
||||
command.Flags().Int64Var(&opts.GithubAppInstallationId, "github-app-installation-id", 0, "installation id of the GitHub Application")
|
||||
command.Flags().StringVar(&opts.GithubAppPrivateKeyPath, "github-app-private-key-path", "", "private key of the GitHub Application")
|
||||
command.Flags().StringVar(&opts.GitHubAppEnterpriseBaseURL, "github-app-enterprise-base-url", "", "base url to use when using GitHub Enterprise (e.g. https://ghe.example.com/api/v3")
|
||||
}
|
||||
@@ -77,6 +77,8 @@ const (
|
||||
RevisionHistoryLimit = 10
|
||||
// ChangePasswordSSOTokenMaxAge is the max token age for password change operation
|
||||
ChangePasswordSSOTokenMaxAge = time.Minute * 5
|
||||
// GithubAppCredsExpirationDuration is the default time used to cache the GitHub app credentials
|
||||
GithubAppCredsExpirationDuration = time.Minute * 60
|
||||
)
|
||||
|
||||
// Dex related constants
|
||||
@@ -85,6 +87,8 @@ const (
|
||||
DexAPIEndpoint = "/api/dex"
|
||||
// LoginEndpoint is Argo CD's shorthand login endpoint which redirects to dex's OAuth 2.0 provider's consent page
|
||||
LoginEndpoint = "/auth/login"
|
||||
// LogoutEndpoint is Argo CD's shorthand logout endpoint which invalidates OIDC session after logout
|
||||
LogoutEndpoint = "/auth/logout"
|
||||
// CallbackEndpoint is Argo CD's final callback endpoint we reach after OAuth 2.0 login flow has been completed
|
||||
CallbackEndpoint = "/auth/callback"
|
||||
// DexCallbackEndpoint is Argo CD's final callback endpoint when Dex is configured
|
||||
@@ -123,6 +127,22 @@ const (
|
||||
AnnotationValueManagedByArgoCD = "argocd.argoproj.io"
|
||||
// ResourcesFinalizerName the finalizer value which we inject to finalize deletion of an application
|
||||
ResourcesFinalizerName = "resources-finalizer.argocd.argoproj.io"
|
||||
|
||||
// AnnotationKeyManifestGeneratePaths is an annotation that contains a list of semicolon-separated paths in the
|
||||
// manifests repository that affects the manifest generation. Paths might be either relative or absolute. The
|
||||
// absolute path means an absolute path within the repository and the relative path is relative to the application
|
||||
// source path within the repository.
|
||||
AnnotationKeyManifestGeneratePaths = "argocd.argoproj.io/manifest-generate-paths"
|
||||
|
||||
// AnnotationKeyLinkPrefix tells the UI to add an external link icon to the application node
|
||||
// that links to the value given in the annotation.
|
||||
// The annotation key must be followed by a unique identifier. Ex: link.argocd.argoproj.io/dashboard
|
||||
// It's valid to have multiple annotations that match the prefix.
|
||||
// Values can simply be a url or they can have
|
||||
// an optional link title separated by a "|"
|
||||
// Ex: "http://grafana.example.com/d/yu5UH4MMz/deployments"
|
||||
// Ex: "Go to Dashboard|http://grafana.example.com/d/yu5UH4MMz/deployments"
|
||||
AnnotationKeyLinkPrefix = "link.argocd.argoproj.io/"
|
||||
)
|
||||
|
||||
// Environment variables for tuning and debugging Argo CD
|
||||
@@ -152,6 +172,22 @@ const (
|
||||
EnvK8sClientMaxIdleConnections = "ARGOCD_K8S_CLIENT_MAX_IDLE_CONNECTIONS"
|
||||
// EnvGnuPGHome is the path to ArgoCD's GnuPG keyring for signature verification
|
||||
EnvGnuPGHome = "ARGOCD_GNUPGHOME"
|
||||
// EnvWatchAPIBufferSize is the buffer size used to transfer K8S watch events to watch API consumer
|
||||
EnvWatchAPIBufferSize = "ARGOCD_WATCH_API_BUFFER_SIZE"
|
||||
// EnvPauseGenerationAfterFailedAttempts will pause manifest generation after the specified number of failed generation attempts
|
||||
EnvPauseGenerationAfterFailedAttempts = "ARGOCD_PAUSE_GEN_AFTER_FAILED_ATTEMPTS"
|
||||
// EnvPauseGenerationMinutes pauses manifest generation for the specified number of minutes, after sufficient manifest generation failures
|
||||
EnvPauseGenerationMinutes = "ARGOCD_PAUSE_GEN_MINUTES"
|
||||
// EnvPauseGenerationRequests pauses manifest generation for the specified number of requests, after sufficient manifest generation failures
|
||||
EnvPauseGenerationRequests = "ARGOCD_PAUSE_GEN_REQUESTS"
|
||||
// EnvControllerReplicas is the number of controller replicas
|
||||
EnvControllerReplicas = "ARGOCD_CONTROLLER_REPLICAS"
|
||||
// EnvControllerShard is the shard number that should be handled by controller
|
||||
EnvControllerShard = "ARGOCD_CONTROLLER_SHARD"
|
||||
// EnvEnableGRPCTimeHistogramEnv enables gRPC metrics collection
|
||||
EnvEnableGRPCTimeHistogramEnv = "ARGOCD_ENABLE_GRPC_TIME_HISTOGRAM"
|
||||
// EnvGithubAppCredsExpirationDuration controls the caching of Github app credentials. This value is in minutes (default: 60)
|
||||
EnvGithubAppCredsExpirationDuration = "ARGOCD_GITHUB_APP_CREDS_EXPIRATION_DURATION"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -161,7 +197,7 @@ const (
|
||||
MinClientVersion = "1.4.0"
|
||||
// CacheVersion is a objects version cached using util/cache/cache.go.
|
||||
// Number should be bumped in case of backward incompatible change to make sure cache is invalidated after upgrade.
|
||||
CacheVersion = "1.0.0"
|
||||
CacheVersion = "1.8.3"
|
||||
)
|
||||
|
||||
// GetGnuPGHomePath retrieves the path to use for GnuPG home directory, which is either taken from GNUPGHOME environment or a default value
|
||||
|
||||
@@ -8,23 +8,25 @@ import (
|
||||
// Version information set by link flags during build. We fall back to these sane
|
||||
// default values when we build outside the Makefile context (e.g. go run, go build, or go test).
|
||||
var (
|
||||
version = "99.99.99" // value from VERSION file
|
||||
buildDate = "1970-01-01T00:00:00Z" // output from `date -u +'%Y-%m-%dT%H:%M:%SZ'`
|
||||
gitCommit = "" // output from `git rev-parse HEAD`
|
||||
gitTag = "" // output from `git describe --exact-match --tags HEAD` (if clean tree state)
|
||||
gitTreeState = "" // determined from `git status --porcelain`. either 'clean' or 'dirty'
|
||||
version = "99.99.99" // value from VERSION file
|
||||
buildDate = "1970-01-01T00:00:00Z" // output from `date -u +'%Y-%m-%dT%H:%M:%SZ'`
|
||||
gitCommit = "" // output from `git rev-parse HEAD`
|
||||
gitTag = "" // output from `git describe --exact-match --tags HEAD` (if clean tree state)
|
||||
gitTreeState = "" // determined from `git status --porcelain`. either 'clean' or 'dirty'
|
||||
kubectlVersion = "" // determined from go.mod file
|
||||
)
|
||||
|
||||
// Version contains Argo version information
|
||||
type Version struct {
|
||||
Version string
|
||||
BuildDate string
|
||||
GitCommit string
|
||||
GitTag string
|
||||
GitTreeState string
|
||||
GoVersion string
|
||||
Compiler string
|
||||
Platform string
|
||||
Version string
|
||||
BuildDate string
|
||||
GitCommit string
|
||||
GitTag string
|
||||
GitTreeState string
|
||||
GoVersion string
|
||||
Compiler string
|
||||
Platform string
|
||||
KubectlVersion string
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
@@ -53,13 +55,14 @@ func GetVersion() Version {
|
||||
}
|
||||
}
|
||||
return Version{
|
||||
Version: versionStr,
|
||||
BuildDate: buildDate,
|
||||
GitCommit: gitCommit,
|
||||
GitTag: gitTag,
|
||||
GitTreeState: gitTreeState,
|
||||
GoVersion: runtime.Version(),
|
||||
Compiler: runtime.Compiler,
|
||||
Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
|
||||
Version: versionStr,
|
||||
BuildDate: buildDate,
|
||||
GitCommit: gitCommit,
|
||||
GitTag: gitTag,
|
||||
GitTreeState: gitTreeState,
|
||||
GoVersion: runtime.Version(),
|
||||
Compiler: runtime.Compiler,
|
||||
Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
|
||||
KubectlVersion: kubectlVersion,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
@@ -13,12 +14,12 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
clustercache "github.com/argoproj/gitops-engine/pkg/cache"
|
||||
"github.com/argoproj/gitops-engine/pkg/diff"
|
||||
"github.com/argoproj/gitops-engine/pkg/health"
|
||||
synccommon "github.com/argoproj/gitops-engine/pkg/sync/common"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/errors"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/io"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/sync/semaphore"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
@@ -26,9 +27,11 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
apiruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
@@ -42,14 +45,15 @@ import (
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application"
|
||||
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
|
||||
appinformers "github.com/argoproj/argo-cd/pkg/client/informers/externalversions"
|
||||
"github.com/argoproj/argo-cd/pkg/client/informers/externalversions/application/v1alpha1"
|
||||
applisters "github.com/argoproj/argo-cd/pkg/client/listers/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/util/argo"
|
||||
appstatecache "github.com/argoproj/argo-cd/util/cache/appstate"
|
||||
"github.com/argoproj/argo-cd/util/db"
|
||||
"github.com/argoproj/argo-cd/util/errors"
|
||||
"github.com/argoproj/argo-cd/util/glob"
|
||||
logutils "github.com/argoproj/argo-cd/util/log"
|
||||
settings_util "github.com/argoproj/argo-cd/util/settings"
|
||||
)
|
||||
|
||||
@@ -106,11 +110,7 @@ type ApplicationController struct {
|
||||
refreshRequestedAppsMutex *sync.Mutex
|
||||
metricsServer *metrics.MetricsServer
|
||||
kubectlSemaphore *semaphore.Weighted
|
||||
}
|
||||
|
||||
type ApplicationControllerConfig struct {
|
||||
InstanceID string
|
||||
Namespace string
|
||||
clusterFilter func(cluster *appv1.Cluster) bool
|
||||
}
|
||||
|
||||
// NewApplicationController creates new instance of ApplicationController.
|
||||
@@ -125,7 +125,9 @@ func NewApplicationController(
|
||||
appResyncPeriod time.Duration,
|
||||
selfHealTimeout time.Duration,
|
||||
metricsPort int,
|
||||
metricsCacheExpiration time.Duration,
|
||||
kubectlParallelismLimit int64,
|
||||
clusterFilter func(cluster *appv1.Cluster) bool,
|
||||
) (*ApplicationController, error) {
|
||||
log.Infof("appResyncPeriod=%v", appResyncPeriod)
|
||||
db := db.NewDB(namespace, settingsMgr, kubeClientset)
|
||||
@@ -147,15 +149,13 @@ func NewApplicationController(
|
||||
auditLogger: argo.NewAuditLogger(namespace, kubeClientset, "argocd-application-controller"),
|
||||
settingsMgr: settingsMgr,
|
||||
selfHealTimeout: selfHealTimeout,
|
||||
clusterFilter: clusterFilter,
|
||||
}
|
||||
if kubectlParallelismLimit > 0 {
|
||||
ctrl.kubectlSemaphore = semaphore.NewWeighted(kubectlParallelismLimit)
|
||||
}
|
||||
kubectl.SetOnKubectlRun(ctrl.onKubectlRun)
|
||||
appInformer, appLister, err := ctrl.newApplicationInformerAndLister()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appInformer, appLister := ctrl.newApplicationInformerAndLister()
|
||||
indexers := cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}
|
||||
projInformer := v1alpha1.NewAppProjectInformer(applicationClientset, namespace, appResyncPeriod, indexers)
|
||||
projInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
@@ -176,12 +176,21 @@ func NewApplicationController(
|
||||
},
|
||||
})
|
||||
metricsAddr := fmt.Sprintf("0.0.0.0:%d", metricsPort)
|
||||
ctrl.metricsServer = metrics.NewMetricsServer(metricsAddr, appLister, func() error {
|
||||
_, err := kubeClientset.Discovery().ServerVersion()
|
||||
return err
|
||||
var err error
|
||||
ctrl.metricsServer, err = metrics.NewMetricsServer(metricsAddr, appLister, ctrl.canProcessApp, func(r *http.Request) error {
|
||||
return nil
|
||||
})
|
||||
stateCache := statecache.NewLiveStateCache(db, appInformer, ctrl.settingsMgr, kubectl, ctrl.metricsServer, ctrl.handleObjectUpdated)
|
||||
appStateManager := NewAppStateManager(db, applicationClientset, repoClientset, namespace, kubectl, ctrl.settingsMgr, stateCache, projInformer, ctrl.metricsServer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if metricsCacheExpiration.Seconds() != 0 {
|
||||
err = ctrl.metricsServer.SetExpiration(metricsCacheExpiration)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
stateCache := statecache.NewLiveStateCache(db, appInformer, ctrl.settingsMgr, kubectl, ctrl.metricsServer, ctrl.handleObjectUpdated, clusterFilter)
|
||||
appStateManager := NewAppStateManager(db, applicationClientset, repoClientset, namespace, kubectl, ctrl.settingsMgr, stateCache, projInformer, ctrl.metricsServer, argoCache, ctrl.statusRefreshTimeout)
|
||||
ctrl.appInformer = appInformer
|
||||
ctrl.appLister = appLister
|
||||
ctrl.projInformer = projInformer
|
||||
@@ -195,7 +204,7 @@ func (ctrl *ApplicationController) GetMetricsServer() *metrics.MetricsServer {
|
||||
return ctrl.metricsServer
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) onKubectlRun(command string) (io.Closer, error) {
|
||||
func (ctrl *ApplicationController) onKubectlRun(command string) (kube.CleanupFunc, error) {
|
||||
ctrl.metricsServer.IncKubectlExec(command)
|
||||
if ctrl.kubectlSemaphore != nil {
|
||||
if err := ctrl.kubectlSemaphore.Acquire(context.Background(), 1); err != nil {
|
||||
@@ -203,13 +212,12 @@ func (ctrl *ApplicationController) onKubectlRun(command string) (io.Closer, erro
|
||||
}
|
||||
ctrl.metricsServer.IncKubectlExecPending(command)
|
||||
}
|
||||
return io.NewCloser(func() error {
|
||||
return func() {
|
||||
if ctrl.kubectlSemaphore != nil {
|
||||
ctrl.kubectlSemaphore.Release(1)
|
||||
ctrl.metricsServer.DecKubectlExecPending(command)
|
||||
}
|
||||
return nil
|
||||
}), nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func isSelfReferencedApp(app *appv1.Application, ref v1.ObjectReference) bool {
|
||||
@@ -222,13 +230,13 @@ func isSelfReferencedApp(app *appv1.Application, ref v1.ObjectReference) bool {
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) getAppProj(app *appv1.Application) (*appv1.AppProject, error) {
|
||||
return argo.GetAppProject(&app.Spec, applisters.NewAppProjectLister(ctrl.projInformer.GetIndexer()), ctrl.namespace)
|
||||
return argo.GetAppProject(&app.Spec, applisters.NewAppProjectLister(ctrl.projInformer.GetIndexer()), ctrl.namespace, ctrl.settingsMgr)
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) handleObjectUpdated(managedByApp map[string]bool, ref v1.ObjectReference) {
|
||||
// if namespaced resource is not managed by any app it might be orphaned resource of some other apps
|
||||
if len(managedByApp) == 0 && ref.Namespace != "" {
|
||||
// retrieve applications which monitor orphaned resources in the same namespace and refresh them unless resource is blacklisted in app project
|
||||
// retrieve applications which monitor orphaned resources in the same namespace and refresh them unless resource is denied in app project
|
||||
if objs, err := ctrl.appInformer.GetIndexer().ByIndex(orphanedIndex, ref.Namespace); err == nil {
|
||||
for i := range objs {
|
||||
app, ok := objs[i].(*appv1.Application)
|
||||
@@ -251,6 +259,11 @@ func (ctrl *ApplicationController) handleObjectUpdated(managedByApp map[string]b
|
||||
continue
|
||||
}
|
||||
|
||||
if !ctrl.canProcessApp(obj) {
|
||||
// Don't force refresh app if app belongs to a different controller shard
|
||||
continue
|
||||
}
|
||||
|
||||
level := ComparisonWithNothing
|
||||
if isManagedResource {
|
||||
level = CompareWithRecent
|
||||
@@ -283,6 +296,9 @@ func isKnownOrphanedResourceExclusion(key kube.ResourceKey, proj *appv1.AppProje
|
||||
if key.Group == "" && key.Kind == kube.ServiceAccountKind && key.Name == "default" {
|
||||
return true
|
||||
}
|
||||
if key.Group == "" && key.Kind == "ConfigMap" && key.Name == "kube-root-ca.crt" {
|
||||
return true
|
||||
}
|
||||
list := proj.Spec.OrphanedResources.Ignore
|
||||
for _, item := range list {
|
||||
if item.Kind == "" || glob.Match(item.Kind, key.Kind) {
|
||||
@@ -299,7 +315,7 @@ func isKnownOrphanedResourceExclusion(key kube.ResourceKey, proj *appv1.AppProje
|
||||
func (ctrl *ApplicationController) getResourceTree(a *appv1.Application, managedResources []*appv1.ResourceDiff) (*appv1.ApplicationTree, error) {
|
||||
nodes := make([]appv1.ResourceNode, 0)
|
||||
|
||||
proj, err := argo.GetAppProject(&a.Spec, applisters.NewAppProjectLister(ctrl.projInformer.GetIndexer()), ctrl.namespace)
|
||||
proj, err := argo.GetAppProject(&a.Spec, applisters.NewAppProjectLister(ctrl.projInformer.GetIndexer()), ctrl.namespace, ctrl.settingsMgr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -376,7 +392,99 @@ func (ctrl *ApplicationController) getResourceTree(a *appv1.Application, managed
|
||||
sort.Slice(orphanedNodes, func(i, j int) bool {
|
||||
return orphanedNodes[i].ResourceRef.String() < orphanedNodes[j].ResourceRef.String()
|
||||
})
|
||||
return &appv1.ApplicationTree{Nodes: nodes, OrphanedNodes: orphanedNodes}, nil
|
||||
|
||||
hosts, err := ctrl.getAppHosts(a, nodes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &appv1.ApplicationTree{Nodes: nodes, OrphanedNodes: orphanedNodes, Hosts: hosts}, nil
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) getAppHosts(a *appv1.Application, appNodes []appv1.ResourceNode) ([]appv1.HostInfo, error) {
|
||||
supportedResourceNames := map[v1.ResourceName]bool{
|
||||
v1.ResourceCPU: true,
|
||||
v1.ResourceStorage: true,
|
||||
v1.ResourceMemory: true,
|
||||
}
|
||||
appPods := map[kube.ResourceKey]bool{}
|
||||
for _, node := range appNodes {
|
||||
if node.Group == "" && node.Kind == kube.PodKind {
|
||||
appPods[kube.NewResourceKey(node.Group, node.Kind, node.Namespace, node.Name)] = true
|
||||
}
|
||||
}
|
||||
allNodesInfo := map[string]statecache.NodeInfo{}
|
||||
allPodsByNode := map[string][]statecache.PodInfo{}
|
||||
appPodsByNode := map[string][]statecache.PodInfo{}
|
||||
err := ctrl.stateCache.IterateResources(a.Spec.Destination.Server, func(res *clustercache.Resource, info *statecache.ResourceInfo) {
|
||||
key := res.ResourceKey()
|
||||
switch {
|
||||
case info.NodeInfo != nil && key.Group == "" && key.Kind == "Node":
|
||||
allNodesInfo[key.Name] = *info.NodeInfo
|
||||
case info.PodInfo != nil && key.Group == "" && key.Kind == kube.PodKind:
|
||||
if appPods[key] {
|
||||
appPodsByNode[info.PodInfo.NodeName] = append(appPodsByNode[info.PodInfo.NodeName], *info.PodInfo)
|
||||
} else {
|
||||
allPodsByNode[info.PodInfo.NodeName] = append(allPodsByNode[info.PodInfo.NodeName], *info.PodInfo)
|
||||
}
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var hosts []appv1.HostInfo
|
||||
for nodeName, appPods := range appPodsByNode {
|
||||
node, ok := allNodesInfo[nodeName]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
neighbors := allPodsByNode[nodeName]
|
||||
|
||||
resources := map[v1.ResourceName]appv1.HostResourceInfo{}
|
||||
for name, resource := range node.Capacity {
|
||||
info := resources[name]
|
||||
info.ResourceName = name
|
||||
info.Capacity += resource.MilliValue()
|
||||
resources[name] = info
|
||||
}
|
||||
|
||||
for _, pod := range appPods {
|
||||
for name, resource := range pod.ResourceRequests {
|
||||
if !supportedResourceNames[name] {
|
||||
continue
|
||||
}
|
||||
|
||||
info := resources[name]
|
||||
info.RequestedByApp += resource.MilliValue()
|
||||
resources[name] = info
|
||||
}
|
||||
}
|
||||
|
||||
for _, pod := range neighbors {
|
||||
for name, resource := range pod.ResourceRequests {
|
||||
if !supportedResourceNames[name] {
|
||||
continue
|
||||
}
|
||||
info := resources[name]
|
||||
info.RequestedByNeighbors += resource.MilliValue()
|
||||
resources[name] = info
|
||||
}
|
||||
}
|
||||
|
||||
var resourcesInfo []appv1.HostResourceInfo
|
||||
for _, info := range resources {
|
||||
if supportedResourceNames[info.ResourceName] && info.Capacity > 0 {
|
||||
resourcesInfo = append(resourcesInfo, info)
|
||||
}
|
||||
}
|
||||
sort.Slice(resourcesInfo, func(i, j int) bool {
|
||||
return resourcesInfo[i].ResourceName < resourcesInfo[j].ResourceName
|
||||
})
|
||||
hosts = append(hosts, appv1.HostInfo{Name: nodeName, SystemInfo: node.SystemInfo, ResourcesInfo: resourcesInfo})
|
||||
}
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) managedResources(comparisonResult *comparisonResult) ([]*appv1.ResourceDiff, error) {
|
||||
@@ -384,11 +492,12 @@ func (ctrl *ApplicationController) managedResources(comparisonResult *comparison
|
||||
for i := range comparisonResult.managedResources {
|
||||
res := comparisonResult.managedResources[i]
|
||||
item := appv1.ResourceDiff{
|
||||
Namespace: res.Namespace,
|
||||
Name: res.Name,
|
||||
Group: res.Group,
|
||||
Kind: res.Kind,
|
||||
Hook: res.Hook,
|
||||
Namespace: res.Namespace,
|
||||
Name: res.Name,
|
||||
Group: res.Group,
|
||||
Kind: res.Kind,
|
||||
Hook: res.Hook,
|
||||
ResourceVersion: res.ResourceVersion,
|
||||
}
|
||||
|
||||
target := res.Target
|
||||
@@ -404,7 +513,10 @@ func (ctrl *ApplicationController) managedResources(comparisonResult *comparison
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resDiffPtr, err := diff.Diff(target, live, comparisonResult.diffNormalizer, compareOptions)
|
||||
resDiffPtr, err := diff.Diff(target, live,
|
||||
diff.WithNormalizer(comparisonResult.diffNormalizer),
|
||||
diff.WithLogr(logutils.NewLogrusLogger(log.New())),
|
||||
diff.IgnoreAggregatedRoles(compareOptions.IgnoreAggregatedRoles))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -432,6 +544,7 @@ func (ctrl *ApplicationController) managedResources(comparisonResult *comparison
|
||||
}
|
||||
item.PredictedLiveState = string(resDiff.PredictedLive)
|
||||
item.NormalizedLiveState = string(resDiff.NormalizedLive)
|
||||
item.Modified = resDiff.Modified
|
||||
|
||||
items[i] = &item
|
||||
}
|
||||
@@ -501,8 +614,10 @@ func (ctrl *ApplicationController) requestAppRefresh(appName string, compareWith
|
||||
}
|
||||
if after != nil {
|
||||
ctrl.appRefreshQueue.AddAfter(key, *after)
|
||||
ctrl.appOperationQueue.AddAfter(key, *after)
|
||||
} else {
|
||||
ctrl.appRefreshQueue.Add(key)
|
||||
ctrl.appOperationQueue.Add(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -540,11 +655,13 @@ func (ctrl *ApplicationController) processAppOperationQueueItem() (processNext b
|
||||
// This happens after app was deleted, but the work queue still had an entry for it.
|
||||
return
|
||||
}
|
||||
app, ok := obj.(*appv1.Application)
|
||||
origApp, ok := obj.(*appv1.Application)
|
||||
if !ok {
|
||||
log.Warnf("Key '%s' in index is not an application", appKey)
|
||||
return
|
||||
}
|
||||
app := origApp.DeepCopy()
|
||||
|
||||
if app.Operation != nil {
|
||||
ctrl.processRequestedAppOperation(app)
|
||||
} else if app.DeletionTimestamp != nil && app.CascadedDeletion() {
|
||||
@@ -770,6 +887,13 @@ func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Applic
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) setAppCondition(app *appv1.Application, condition appv1.ApplicationCondition) {
|
||||
// do nothing if app already has same condition
|
||||
for _, c := range app.Status.Conditions {
|
||||
if c.Message == condition.Message && c.Type == condition.Type {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
app.Status.SetConditions([]appv1.ApplicationCondition{condition}, map[appv1.ApplicationConditionType]bool{condition.Type: true})
|
||||
|
||||
var patch []byte
|
||||
@@ -835,6 +959,11 @@ func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Appli
|
||||
ctrl.requestAppRefresh(app.Name, CompareWithLatest.Pointer(), &retryAfter)
|
||||
return
|
||||
} else {
|
||||
// retrying operation. remove previous failure time in app since it is used as a trigger
|
||||
// that previous failed and operation should be retried
|
||||
state.FinishedAt = nil
|
||||
ctrl.setOperationState(app, state)
|
||||
// Get rid of sync results and null out previous operation completion time
|
||||
state.SyncResult = nil
|
||||
}
|
||||
} else {
|
||||
@@ -898,7 +1027,7 @@ func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Appli
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) setOperationState(app *appv1.Application, state *appv1.OperationState) {
|
||||
kube.RetryUntilSucceed(context.Background(), updateOperationStateTimeout, "Update application operation state", func() error {
|
||||
kube.RetryUntilSucceed(context.Background(), updateOperationStateTimeout, "Update application operation state", logutils.NewLogrusLogger(log.New()), func() error {
|
||||
if state.Phase == "" {
|
||||
// expose any bugs where we neglect to set phase
|
||||
panic("no phase was set")
|
||||
@@ -925,6 +1054,13 @@ func (ctrl *ApplicationController) setOperationState(app *appv1.Application, sta
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if app.Status.OperationState != nil && app.Status.OperationState.FinishedAt != nil && state.FinishedAt == nil {
|
||||
patchJSON, err = jsonpatch.MergeMergePatches(patchJSON, []byte(`{"status": {"operationState": {"finishedAt": null}}}`))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
appClient := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(ctrl.namespace)
|
||||
_, err = appClient.Patch(context.Background(), app.Name, types.MergePatchType, patchJSON, metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
@@ -1016,23 +1152,14 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
|
||||
logCtx.Warnf("Failed to get cached managed resources for tree reconciliation, fallback to full reconciliation")
|
||||
} else {
|
||||
var tree *appv1.ApplicationTree
|
||||
if err = argo.ValidateDestination(context.Background(), &app.Spec.Destination, ctrl.db); err == nil {
|
||||
if tree, err = ctrl.getResourceTree(app, managedResources); err == nil {
|
||||
app.Status.Summary = tree.GetSummary()
|
||||
if err := ctrl.cache.SetAppResourcesTree(app.Name, tree); err != nil {
|
||||
logCtx.Errorf("Failed to cache resources tree: %v", err)
|
||||
return
|
||||
}
|
||||
if tree, err = ctrl.getResourceTree(app, managedResources); err == nil {
|
||||
app.Status.Summary = tree.GetSummary()
|
||||
if err := ctrl.cache.SetAppResourcesTree(app.Name, tree); err != nil {
|
||||
logCtx.Errorf("Failed to cache resources tree: %v", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
app.Status.SetConditions([]appv1.ApplicationCondition{{
|
||||
Type: appv1.ApplicationConditionComparisonError, Message: err.Error(),
|
||||
}}, map[appv1.ApplicationConditionType]bool{
|
||||
appv1.ApplicationConditionComparisonError: true,
|
||||
})
|
||||
}
|
||||
now := metav1.Now()
|
||||
app.Status.ObservedAt = &now
|
||||
|
||||
ctrl.persistAppStatus(origApp, &app.Status)
|
||||
return
|
||||
}
|
||||
@@ -1056,7 +1183,7 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
|
||||
revision = app.Status.Sync.Revision
|
||||
}
|
||||
|
||||
observedAt := metav1.Now()
|
||||
now := metav1.Now()
|
||||
compareResult := ctrl.appStateManager.CompareAppState(app, project, revision, app.Spec.Source, refreshType == appv1.RefreshTypeHard, localManifests)
|
||||
for k, v := range compareResult.timings {
|
||||
logCtx = logCtx.WithField(k, v.Milliseconds())
|
||||
@@ -1089,9 +1216,8 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
|
||||
}
|
||||
|
||||
if app.Status.ReconciledAt == nil || comparisonLevel == CompareWithLatest {
|
||||
app.Status.ReconciledAt = &observedAt
|
||||
app.Status.ReconciledAt = &now
|
||||
}
|
||||
app.Status.ObservedAt = &observedAt
|
||||
app.Status.Sync = *compareResult.syncStatus
|
||||
app.Status.Health = *compareResult.healthStatus
|
||||
app.Status.Resources = compareResult.resources
|
||||
@@ -1163,13 +1289,6 @@ func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if err := argo.ValidateDestination(context.Background(), &app.Spec.Destination, ctrl.db); err != nil {
|
||||
errorConditions = append(errorConditions, appv1.ApplicationCondition{
|
||||
Message: err.Error(),
|
||||
Type: appv1.ApplicationConditionInvalidSpecError,
|
||||
})
|
||||
}
|
||||
|
||||
specConditions, err := argo.ValidatePermissions(context.Background(), &app.Spec, proj, ctrl.db)
|
||||
if err != nil {
|
||||
errorConditions = append(errorConditions, appv1.ApplicationCondition{
|
||||
@@ -1327,7 +1446,7 @@ func (ctrl *ApplicationController) autoSync(app *appv1.Application, syncStatus *
|
||||
|
||||
}
|
||||
|
||||
if app.Spec.SyncPolicy.Automated.Prune {
|
||||
if app.Spec.SyncPolicy.Automated.Prune && !app.Spec.SyncPolicy.Automated.AllowEmpty {
|
||||
bAllNeedPrune := true
|
||||
for _, r := range resources {
|
||||
if !r.RequiresPruning {
|
||||
@@ -1335,7 +1454,7 @@ func (ctrl *ApplicationController) autoSync(app *appv1.Application, syncStatus *
|
||||
}
|
||||
}
|
||||
if bAllNeedPrune {
|
||||
message := fmt.Sprintf("Skipping sync attempt to %s: auto-sync will wipe out all resourses", desiredCommitSHA)
|
||||
message := fmt.Sprintf("Skipping sync attempt to %s: auto-sync will wipe out all resources", desiredCommitSHA)
|
||||
logCtx.Warnf(message)
|
||||
return &appv1.ApplicationCondition{Type: appv1.ApplicationConditionSyncError, Message: message}
|
||||
}
|
||||
@@ -1385,18 +1504,69 @@ func (ctrl *ApplicationController) shouldSelfHeal(app *appv1.Application) (bool,
|
||||
return retryAfter <= 0, retryAfter
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.SharedIndexInformer, applisters.ApplicationLister, error) {
|
||||
appInformerFactory := appinformers.NewFilteredSharedInformerFactory(
|
||||
ctrl.applicationClientset,
|
||||
func (ctrl *ApplicationController) canProcessApp(obj interface{}) bool {
|
||||
app, ok := obj.(*appv1.Application)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if ctrl.clusterFilter != nil {
|
||||
cluster, err := ctrl.db.GetCluster(context.Background(), app.Spec.Destination.Server)
|
||||
if err != nil {
|
||||
return ctrl.clusterFilter(nil)
|
||||
}
|
||||
return ctrl.clusterFilter(cluster)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.SharedIndexInformer, applisters.ApplicationLister) {
|
||||
informer := cache.NewSharedIndexInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (apiruntime.Object, error) {
|
||||
return ctrl.applicationClientset.ArgoprojV1alpha1().Applications(ctrl.namespace).List(context.TODO(), options)
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
return ctrl.applicationClientset.ArgoprojV1alpha1().Applications(ctrl.namespace).Watch(context.TODO(), options)
|
||||
},
|
||||
},
|
||||
&appv1.Application{},
|
||||
ctrl.statusRefreshTimeout,
|
||||
ctrl.namespace,
|
||||
func(options *metav1.ListOptions) {},
|
||||
cache.Indexers{
|
||||
cache.NamespaceIndex: func(obj interface{}) ([]string, error) {
|
||||
app, ok := obj.(*appv1.Application)
|
||||
if ok {
|
||||
if err := argo.ValidateDestination(context.Background(), &app.Spec.Destination, ctrl.db); err != nil {
|
||||
ctrl.setAppCondition(app, appv1.ApplicationCondition{Type: appv1.ApplicationConditionInvalidSpecError, Message: err.Error()})
|
||||
}
|
||||
}
|
||||
|
||||
return cache.MetaNamespaceIndexFunc(obj)
|
||||
},
|
||||
orphanedIndex: func(obj interface{}) (i []string, e error) {
|
||||
app, ok := obj.(*appv1.Application)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
proj, err := ctrl.getAppProj(app)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
if proj.Spec.OrphanedResources != nil {
|
||||
return []string{app.Spec.Destination.Namespace}, nil
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
)
|
||||
informer := appInformerFactory.Argoproj().V1alpha1().Applications().Informer()
|
||||
lister := appInformerFactory.Argoproj().V1alpha1().Applications().Lister()
|
||||
lister := applisters.NewApplicationLister(informer.GetIndexer())
|
||||
informer.AddEventHandler(
|
||||
cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
if !ctrl.canProcessApp(obj) {
|
||||
return
|
||||
}
|
||||
key, err := cache.MetaNamespaceKeyFunc(obj)
|
||||
if err == nil {
|
||||
ctrl.appRefreshQueue.Add(key)
|
||||
@@ -1404,6 +1574,10 @@ func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.Shar
|
||||
}
|
||||
},
|
||||
UpdateFunc: func(old, new interface{}) {
|
||||
if !ctrl.canProcessApp(new) {
|
||||
return
|
||||
}
|
||||
|
||||
key, err := cache.MetaNamespaceKeyFunc(new)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -1419,6 +1593,9 @@ func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.Shar
|
||||
ctrl.appOperationQueue.Add(key)
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
if !ctrl.canProcessApp(obj) {
|
||||
return
|
||||
}
|
||||
// IndexerInformer uses a delta queue, therefore for deletes we have to use this
|
||||
// key function.
|
||||
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
|
||||
@@ -1428,28 +1605,11 @@ func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.Shar
|
||||
},
|
||||
},
|
||||
)
|
||||
err := informer.AddIndexers(cache.Indexers{
|
||||
orphanedIndex: func(obj interface{}) (i []string, e error) {
|
||||
app, ok := obj.(*appv1.Application)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
proj, err := ctrl.getAppProj(app)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
if proj.Spec.OrphanedResources != nil {
|
||||
return []string{app.Spec.Destination.Namespace}, nil
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
})
|
||||
return informer, lister, err
|
||||
return informer, lister
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) RegisterClusterSecretUpdater(ctx context.Context) {
|
||||
updater := NewClusterInfoUpdater(ctrl.stateCache, ctrl.db, ctrl.appLister.Applications(ctrl.namespace), ctrl.cache)
|
||||
updater := NewClusterInfoUpdater(ctrl.stateCache, ctrl.db, ctrl.appLister.Applications(ctrl.namespace), ctrl.cache, ctrl.clusterFilter)
|
||||
go updater.Run(ctx)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,12 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
|
||||
clustercache "github.com/argoproj/gitops-engine/pkg/cache"
|
||||
|
||||
statecache "github.com/argoproj/argo-cd/controller/cache"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/cache/mocks"
|
||||
synccommon "github.com/argoproj/gitops-engine/pkg/sync/common"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
@@ -41,11 +47,12 @@ type namespacedResource struct {
|
||||
}
|
||||
|
||||
type fakeData struct {
|
||||
apps []runtime.Object
|
||||
manifestResponse *apiclient.ManifestResponse
|
||||
managedLiveObjs map[kube.ResourceKey]*unstructured.Unstructured
|
||||
namespacedResources map[kube.ResourceKey]namespacedResource
|
||||
configMapData map[string]string
|
||||
apps []runtime.Object
|
||||
manifestResponse *apiclient.ManifestResponse
|
||||
managedLiveObjs map[kube.ResourceKey]*unstructured.Unstructured
|
||||
namespacedResources map[kube.ResourceKey]namespacedResource
|
||||
configMapData map[string]string
|
||||
metricsCacheExpiration time.Duration
|
||||
}
|
||||
|
||||
func newFakeController(data *fakeData) *ApplicationController {
|
||||
@@ -97,7 +104,9 @@ func newFakeController(data *fakeData) *ApplicationController {
|
||||
time.Minute,
|
||||
time.Minute,
|
||||
common.DefaultPortArgoCDMetrics,
|
||||
data.metricsCacheExpiration,
|
||||
0,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -120,6 +129,7 @@ func newFakeController(data *fakeData) *ApplicationController {
|
||||
response[k] = v.ResourceNode
|
||||
}
|
||||
mockStateCache.On("GetNamespaceTopLevelResources", mock.Anything, mock.Anything).Return(response, nil)
|
||||
mockStateCache.On("IterateResources", mock.Anything, mock.Anything).Return(nil)
|
||||
mockStateCache.On("GetClusterCache", mock.Anything).Return(&clusterCacheMock, nil)
|
||||
mockStateCache.On("IterateHierarchy", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
|
||||
key := args[1].(kube.ResourceKey)
|
||||
@@ -288,6 +298,31 @@ func TestAutoSync(t *testing.T) {
|
||||
assert.False(t, app.Operation.Sync.Prune)
|
||||
}
|
||||
|
||||
func TestAutoSyncNotAllowEmpty(t *testing.T) {
|
||||
app := newFakeApp()
|
||||
app.Spec.SyncPolicy.Automated.Prune = true
|
||||
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app}})
|
||||
syncStatus := argoappv1.SyncStatus{
|
||||
Status: argoappv1.SyncStatusCodeOutOfSync,
|
||||
Revision: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
}
|
||||
cond := ctrl.autoSync(app, &syncStatus, []argoappv1.ResourceStatus{})
|
||||
assert.NotNil(t, cond)
|
||||
}
|
||||
|
||||
func TestAutoSyncAllowEmpty(t *testing.T) {
|
||||
app := newFakeApp()
|
||||
app.Spec.SyncPolicy.Automated.Prune = true
|
||||
app.Spec.SyncPolicy.Automated.AllowEmpty = true
|
||||
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app}})
|
||||
syncStatus := argoappv1.SyncStatus{
|
||||
Status: argoappv1.SyncStatusCodeOutOfSync,
|
||||
Revision: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
}
|
||||
cond := ctrl.autoSync(app, &syncStatus, []argoappv1.ResourceStatus{})
|
||||
assert.Nil(t, cond)
|
||||
}
|
||||
|
||||
func TestSkipAutoSync(t *testing.T) {
|
||||
// Verify we skip when we previously synced to it in our most recent history
|
||||
// Set current to 'aaaaa', desired to 'aaaa' and mark system OutOfSync
|
||||
@@ -576,15 +611,11 @@ func TestFinalizeAppDeletion(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("DeleteWithDestinationClusterName", func(t *testing.T) {
|
||||
app := newFakeApp()
|
||||
app.Spec.Destination.Namespace = test.FakeArgoCDNamespace
|
||||
app.Spec.Destination.Name = "minikube"
|
||||
app.Spec.Destination.Server = ""
|
||||
app := newFakeAppWithDestName()
|
||||
appObj := kube.MustToUnstructured(&app)
|
||||
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app, &defaultProj}, managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
|
||||
kube.GetResourceKey(appObj): appObj,
|
||||
}})
|
||||
|
||||
patched := false
|
||||
fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset)
|
||||
defaultReactor := fakeAppCs.ReactionChain[0]
|
||||
@@ -600,6 +631,27 @@ func TestFinalizeAppDeletion(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, patched)
|
||||
})
|
||||
|
||||
t.Run("ErrorOnBothDestNameAndServer", func(t *testing.T) {
|
||||
app := newFakeAppWithDestMismatch()
|
||||
appObj := kube.MustToUnstructured(&app)
|
||||
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app, &defaultProj}, managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
|
||||
kube.GetResourceKey(appObj): appObj,
|
||||
}})
|
||||
fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset)
|
||||
func() {
|
||||
fakeAppCs.Lock()
|
||||
defer fakeAppCs.Unlock()
|
||||
|
||||
defaultReactor := fakeAppCs.ReactionChain[0]
|
||||
fakeAppCs.ReactionChain = nil
|
||||
fakeAppCs.AddReactor("get", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return defaultReactor.React(action)
|
||||
})
|
||||
}()
|
||||
_, err := ctrl.finalizeApplicationDeletion(app)
|
||||
assert.EqualError(t, err, "application destination can't have both name and server defined: another-cluster https://localhost:6443")
|
||||
})
|
||||
}
|
||||
|
||||
// TestNormalizeApplication verifies we normalize an application during reconciliation
|
||||
@@ -901,26 +953,6 @@ func TestRefreshAppConditions(t *testing.T) {
|
||||
assert.Equal(t, argoappv1.ApplicationConditionInvalidSpecError, app.Status.Conditions[0].Type)
|
||||
assert.Equal(t, "Application referencing project wrong project which does not exist", app.Status.Conditions[0].Message)
|
||||
})
|
||||
|
||||
t.Run("NoErrorConditionsWithDestNameOnly", func(t *testing.T) {
|
||||
app := newFakeAppWithDestName()
|
||||
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app, &defaultProj}})
|
||||
|
||||
_, hasErrors := ctrl.refreshAppConditions(app)
|
||||
assert.False(t, hasErrors)
|
||||
assert.Len(t, app.Status.Conditions, 0)
|
||||
})
|
||||
|
||||
t.Run("ErrorOnBothDestNameAndServer", func(t *testing.T) {
|
||||
app := newFakeAppWithDestMismatch()
|
||||
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app, &defaultProj}})
|
||||
|
||||
_, hasErrors := ctrl.refreshAppConditions(app)
|
||||
assert.True(t, hasErrors)
|
||||
assert.Len(t, app.Status.Conditions, 1)
|
||||
assert.Equal(t, argoappv1.ApplicationConditionInvalidSpecError, app.Status.Conditions[0].Type)
|
||||
assert.Equal(t, "application destination can't have both name and server defined: another-cluster https://localhost:6443", app.Status.Conditions[0].Message)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateReconciledAt(t *testing.T) {
|
||||
@@ -962,7 +994,7 @@ func TestUpdateReconciledAt(t *testing.T) {
|
||||
|
||||
_, updated, err = unstructured.NestedString(receivedPatch, "status", "observedAt")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, updated)
|
||||
assert.False(t, updated)
|
||||
})
|
||||
|
||||
t.Run("NotUpdatedOnPartialReconciliation", func(t *testing.T) {
|
||||
@@ -978,7 +1010,7 @@ func TestUpdateReconciledAt(t *testing.T) {
|
||||
|
||||
_, updated, err = unstructured.NestedString(receivedPatch, "status", "observedAt")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, updated)
|
||||
assert.False(t, updated)
|
||||
})
|
||||
|
||||
}
|
||||
@@ -1044,6 +1076,34 @@ func TestProcessRequestedAppOperation_FailedNoRetries(t *testing.T) {
|
||||
assert.Equal(t, string(synccommon.OperationError), phase)
|
||||
}
|
||||
|
||||
func TestProcessRequestedAppOperation_InvalidDestination(t *testing.T) {
|
||||
app := newFakeAppWithDestMismatch()
|
||||
app.Spec.Project = "test-project"
|
||||
app.Operation = &argoappv1.Operation{
|
||||
Sync: &argoappv1.SyncOperation{},
|
||||
}
|
||||
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app}})
|
||||
fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset)
|
||||
receivedPatch := map[string]interface{}{}
|
||||
func() {
|
||||
fakeAppCs.Lock()
|
||||
defer fakeAppCs.Unlock()
|
||||
fakeAppCs.PrependReactor("patch", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
if patchAction, ok := action.(kubetesting.PatchAction); ok {
|
||||
assert.NoError(t, json.Unmarshal(patchAction.GetPatch(), &receivedPatch))
|
||||
}
|
||||
return true, nil, nil
|
||||
})
|
||||
}()
|
||||
|
||||
ctrl.processRequestedAppOperation(app)
|
||||
|
||||
phase, _, _ := unstructured.NestedString(receivedPatch, "status", "operationState", "phase")
|
||||
assert.Equal(t, string(synccommon.OperationFailed), phase)
|
||||
message, _, _ := unstructured.NestedString(receivedPatch, "status", "operationState", "message")
|
||||
assert.Contains(t, message, "application destination can't have both name and server defined: another-cluster https://localhost:6443")
|
||||
}
|
||||
|
||||
func TestProcessRequestedAppOperation_FailedHasRetries(t *testing.T) {
|
||||
app := newFakeApp()
|
||||
app.Spec.Project = "invalid-project"
|
||||
@@ -1142,3 +1202,71 @@ func TestProcessRequestedAppOperation_HasRetriesTerminated(t *testing.T) {
|
||||
phase, _, _ := unstructured.NestedString(receivedPatch, "status", "operationState", "phase")
|
||||
assert.Equal(t, string(synccommon.OperationFailed), phase)
|
||||
}
|
||||
|
||||
func TestGetAppHosts(t *testing.T) {
|
||||
app := newFakeApp()
|
||||
data := &fakeData{
|
||||
apps: []runtime.Object{app, &defaultProj},
|
||||
manifestResponse: &apiclient.ManifestResponse{
|
||||
Manifests: []string{},
|
||||
Namespace: test.FakeDestNamespace,
|
||||
Server: test.FakeClusterURL,
|
||||
Revision: "abc123",
|
||||
},
|
||||
}
|
||||
ctrl := newFakeController(data)
|
||||
mockStateCache := &mockstatecache.LiveStateCache{}
|
||||
mockStateCache.On("IterateResources", mock.Anything, mock.MatchedBy(func(callback func(res *clustercache.Resource, info *statecache.ResourceInfo)) bool {
|
||||
// node resource
|
||||
callback(&clustercache.Resource{
|
||||
Ref: corev1.ObjectReference{Name: "minikube", Kind: "Node", APIVersion: "v1"},
|
||||
}, &statecache.ResourceInfo{NodeInfo: &statecache.NodeInfo{
|
||||
Name: "minikube",
|
||||
SystemInfo: corev1.NodeSystemInfo{OSImage: "debian"},
|
||||
Capacity: map[corev1.ResourceName]resource.Quantity{corev1.ResourceCPU: resource.MustParse("5")},
|
||||
}})
|
||||
|
||||
// app pod
|
||||
callback(&clustercache.Resource{
|
||||
Ref: corev1.ObjectReference{Name: "pod1", Kind: kube.PodKind, APIVersion: "v1", Namespace: "default"},
|
||||
}, &statecache.ResourceInfo{PodInfo: &statecache.PodInfo{
|
||||
NodeName: "minikube",
|
||||
ResourceRequests: map[corev1.ResourceName]resource.Quantity{corev1.ResourceCPU: resource.MustParse("1")},
|
||||
}})
|
||||
// neighbor pod
|
||||
callback(&clustercache.Resource{
|
||||
Ref: corev1.ObjectReference{Name: "pod2", Kind: kube.PodKind, APIVersion: "v1", Namespace: "default"},
|
||||
}, &statecache.ResourceInfo{PodInfo: &statecache.PodInfo{
|
||||
NodeName: "minikube",
|
||||
ResourceRequests: map[corev1.ResourceName]resource.Quantity{corev1.ResourceCPU: resource.MustParse("2")},
|
||||
}})
|
||||
return true
|
||||
})).Return(nil)
|
||||
ctrl.stateCache = mockStateCache
|
||||
|
||||
hosts, err := ctrl.getAppHosts(app, []argoappv1.ResourceNode{{
|
||||
ResourceRef: argoappv1.ResourceRef{Name: "pod1", Namespace: "default", Kind: kube.PodKind},
|
||||
Info: []argoappv1.InfoItem{{
|
||||
Name: "Host",
|
||||
Value: "Minikube",
|
||||
}},
|
||||
}})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []argoappv1.HostInfo{{
|
||||
Name: "minikube",
|
||||
SystemInfo: corev1.NodeSystemInfo{OSImage: "debian"},
|
||||
ResourcesInfo: []argoappv1.HostResourceInfo{{
|
||||
ResourceName: corev1.ResourceCPU, Capacity: 5000, RequestedByApp: 1000, RequestedByNeighbors: 2000},
|
||||
}}}, hosts)
|
||||
}
|
||||
|
||||
func TestMetricsExpiration(t *testing.T) {
|
||||
app := newFakeApp()
|
||||
// Check expiration is disabled by default
|
||||
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app}})
|
||||
assert.False(t, ctrl.metricsServer.HasExpiration())
|
||||
// Check expiration is enabled if set
|
||||
ctrl = newFakeController(&fakeData{apps: []runtime.Object{app}, metricsCacheExpiration: 10 * time.Second})
|
||||
assert.True(t, ctrl.metricsServer.HasExpiration())
|
||||
}
|
||||
|
||||
86
controller/cache/cache.go
vendored
86
controller/cache/cache.go
vendored
@@ -2,6 +2,7 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"github.com/argoproj/gitops-engine/pkg/health"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/sync/semaphore"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
@@ -21,6 +23,7 @@ import (
|
||||
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/util/argo"
|
||||
"github.com/argoproj/argo-cd/util/db"
|
||||
logutils "github.com/argoproj/argo-cd/util/log"
|
||||
"github.com/argoproj/argo-cd/util/lua"
|
||||
"github.com/argoproj/argo-cd/util/settings"
|
||||
)
|
||||
@@ -36,6 +39,8 @@ type LiveStateCache interface {
|
||||
IterateHierarchy(server string, key kube.ResourceKey, action func(child appv1.ResourceNode, appName string)) error
|
||||
// Returns state of live nodes which correspond for target nodes of specified application.
|
||||
GetManagedLiveObjs(a *appv1.Application, targetObjs []*unstructured.Unstructured) (map[kube.ResourceKey]*unstructured.Unstructured, error)
|
||||
// IterateResources iterates all resource stored in cache
|
||||
IterateResources(server string, callback func(res *clustercache.Resource, info *ResourceInfo)) error
|
||||
// Returns all top level resources (resources without owner references) of a specified namespace
|
||||
GetNamespaceTopLevelResources(server string, namespace string) (map[kube.ResourceKey]appv1.ResourceNode, error)
|
||||
// Starts watching resources of each controlled cluster.
|
||||
@@ -48,13 +53,28 @@ type LiveStateCache interface {
|
||||
|
||||
type ObjectUpdatedHandler = func(managedByApp map[string]bool, ref v1.ObjectReference)
|
||||
|
||||
type PodInfo struct {
|
||||
NodeName string
|
||||
ResourceRequests v1.ResourceList
|
||||
}
|
||||
|
||||
type NodeInfo struct {
|
||||
Name string
|
||||
Capacity v1.ResourceList
|
||||
SystemInfo v1.NodeSystemInfo
|
||||
}
|
||||
|
||||
type ResourceInfo struct {
|
||||
Info []appv1.InfoItem
|
||||
AppName string
|
||||
// networkingInfo are available only for known types involved into networking: Ingress, Service, Pod
|
||||
Images []string
|
||||
Health *health.HealthStatus
|
||||
// NetworkingInfo are available only for known types involved into networking: Ingress, Service, Pod
|
||||
NetworkingInfo *appv1.ResourceNetworkingInfo
|
||||
Images []string
|
||||
Health *health.HealthStatus
|
||||
// PodInfo is available for pods only
|
||||
PodInfo *PodInfo
|
||||
// NodeInfo is available for nodes only
|
||||
NodeInfo *NodeInfo
|
||||
}
|
||||
|
||||
func NewLiveStateCache(
|
||||
@@ -63,7 +83,8 @@ func NewLiveStateCache(
|
||||
settingsMgr *settings.SettingsManager,
|
||||
kubectl kube.Kubectl,
|
||||
metricsServer *metrics.MetricsServer,
|
||||
onObjectUpdated ObjectUpdatedHandler) LiveStateCache {
|
||||
onObjectUpdated ObjectUpdatedHandler,
|
||||
clusterFilter func(cluster *appv1.Cluster) bool) LiveStateCache {
|
||||
|
||||
return &liveStateCache{
|
||||
appInformer: appInformer,
|
||||
@@ -73,6 +94,9 @@ func NewLiveStateCache(
|
||||
kubectl: kubectl,
|
||||
settingsMgr: settingsMgr,
|
||||
metricsServer: metricsServer,
|
||||
// The default limit of 50 is chosen based on experiments.
|
||||
listSemaphore: semaphore.NewWeighted(50),
|
||||
clusterFilter: clusterFilter,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +112,11 @@ type liveStateCache struct {
|
||||
kubectl kube.Kubectl
|
||||
settingsMgr *settings.SettingsManager
|
||||
metricsServer *metrics.MetricsServer
|
||||
clusterFilter func(cluster *appv1.Cluster) bool
|
||||
|
||||
// listSemaphore is used to limit the number of concurrent memory consuming operations on the
|
||||
// k8s list queries results across all clusters to avoid memory spikes during cache initialization.
|
||||
listSemaphore *semaphore.Weighted
|
||||
|
||||
clusters map[string]clustercache.ClusterCache
|
||||
cacheSettings cacheSettings
|
||||
@@ -231,7 +260,12 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !c.canHandleCluster(cluster) {
|
||||
return nil, fmt.Errorf("controller is configured to ignore cluster %s", cluster.Server)
|
||||
}
|
||||
|
||||
clusterCache = clustercache.NewClusterCache(cluster.RESTConfig(),
|
||||
clustercache.SetListSemaphore(c.listSemaphore),
|
||||
clustercache.SetResyncTimeout(common.K8SClusterResyncDuration),
|
||||
clustercache.SetSettings(cacheSettings.clusterSettings),
|
||||
clustercache.SetNamespaces(cluster.Namespaces),
|
||||
@@ -243,11 +277,13 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
|
||||
if isRoot && appName != "" {
|
||||
res.AppName = appName
|
||||
}
|
||||
gvk := un.GroupVersionKind()
|
||||
|
||||
// edge case. we do not label CRDs, so they miss the tracking label we inject. But we still
|
||||
// want the full resource to be available in our cache (to diff), so we store all CRDs
|
||||
return res, res.AppName != "" || un.GroupVersionKind().Kind == kube.CustomResourceDefinitionKind
|
||||
return res, res.AppName != "" || gvk.Kind == kube.CustomResourceDefinitionKind
|
||||
}),
|
||||
clustercache.SetLogr(logutils.NewLogrusLogger(log.WithField("server", cluster.Server))),
|
||||
)
|
||||
|
||||
_ = clusterCache.OnResourceUpdated(func(newRes *clustercache.Resource, oldRes *clustercache.Resource, namespaceResources map[kube.ResourceKey]*clustercache.Resource) {
|
||||
@@ -276,7 +312,7 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
|
||||
c.metricsServer.IncClusterEventsCount(cluster.Server, gvk.Group, gvk.Kind)
|
||||
})
|
||||
|
||||
c.clusters[cluster.Server] = clusterCache
|
||||
c.clusters[server] = clusterCache
|
||||
|
||||
return clusterCache, nil
|
||||
}
|
||||
@@ -324,12 +360,26 @@ func (c *liveStateCache) IterateHierarchy(server string, key kube.ResourceKey, a
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *liveStateCache) IterateResources(server string, callback func(res *clustercache.Resource, info *ResourceInfo)) error {
|
||||
clusterInfo, err := c.getSyncedCluster(server)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = clusterInfo.FindResources("", func(r *clustercache.Resource) bool {
|
||||
if info, ok := r.Info.(*ResourceInfo); ok {
|
||||
callback(r, info)
|
||||
}
|
||||
return false
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *liveStateCache) GetNamespaceTopLevelResources(server string, namespace string) (map[kube.ResourceKey]appv1.ResourceNode, error) {
|
||||
clusterInfo, err := c.getSyncedCluster(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resources := clusterInfo.GetNamespaceTopLevelResources(namespace)
|
||||
resources := clusterInfo.FindResources(namespace, clustercache.TopLevelResource)
|
||||
res := make(map[kube.ResourceKey]appv1.ResourceNode)
|
||||
for k, r := range resources {
|
||||
res[k] = asResourceNode(r)
|
||||
@@ -418,7 +468,7 @@ func (c *liveStateCache) Init() error {
|
||||
func (c *liveStateCache) Run(ctx context.Context) error {
|
||||
go c.watchSettings(ctx)
|
||||
|
||||
kube.RetryUntilSucceed(ctx, clustercache.ClusterRetryTimeout, "watch clusters", func() error {
|
||||
kube.RetryUntilSucceed(ctx, clustercache.ClusterRetryTimeout, "watch clusters", logutils.NewLogrusLogger(log.New()), func() error {
|
||||
return c.db.WatchClusters(ctx, c.handleAddEvent, c.handleModEvent, c.handleDeleteEvent)
|
||||
})
|
||||
|
||||
@@ -427,7 +477,19 @@ func (c *liveStateCache) Run(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *liveStateCache) canHandleCluster(cluster *appv1.Cluster) bool {
|
||||
if c.clusterFilter == nil {
|
||||
return true
|
||||
}
|
||||
return c.clusterFilter(cluster)
|
||||
}
|
||||
|
||||
func (c *liveStateCache) handleAddEvent(cluster *appv1.Cluster) {
|
||||
if !c.canHandleCluster(cluster) {
|
||||
log.Infof("Ignoring cluster %s", cluster.Server)
|
||||
return
|
||||
}
|
||||
|
||||
c.lock.Lock()
|
||||
_, ok := c.clusters[cluster.Server]
|
||||
c.lock.Unlock()
|
||||
@@ -446,6 +508,14 @@ func (c *liveStateCache) handleModEvent(oldCluster *appv1.Cluster, newCluster *a
|
||||
cluster, ok := c.clusters[newCluster.Server]
|
||||
c.lock.Unlock()
|
||||
if ok {
|
||||
if !c.canHandleCluster(newCluster) {
|
||||
cluster.Invalidate()
|
||||
c.lock.Lock()
|
||||
delete(c.clusters, newCluster.Server)
|
||||
c.lock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
var updateSettings []clustercache.UpdateSettingsFunc
|
||||
if !reflect.DeepEqual(oldCluster.Config, newCluster.Config) {
|
||||
updateSettings = append(updateSettings, clustercache.SetConfig(newCluster.RESTConfig()))
|
||||
|
||||
43
controller/cache/cache_test.go
vendored
43
controller/cache/cache_test.go
vendored
@@ -3,6 +3,8 @@ package cache
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/cache"
|
||||
"github.com/argoproj/gitops-engine/pkg/cache/mocks"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@@ -31,6 +33,32 @@ func TestHandleModEvent_HasChanges(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestHandleModEvent_ClusterExcluded(t *testing.T) {
|
||||
clusterCache := &mocks.ClusterCache{}
|
||||
clusterCache.On("Invalidate", mock.Anything, mock.Anything).Return(nil).Once()
|
||||
clusterCache.On("EnsureSynced").Return(nil).Once()
|
||||
|
||||
clustersCache := liveStateCache{
|
||||
clusters: map[string]cache.ClusterCache{
|
||||
"https://mycluster": clusterCache,
|
||||
},
|
||||
clusterFilter: func(cluster *appv1.Cluster) bool {
|
||||
return false
|
||||
},
|
||||
}
|
||||
|
||||
clustersCache.handleModEvent(&appv1.Cluster{
|
||||
Server: "https://mycluster",
|
||||
Config: appv1.ClusterConfig{Username: "foo"},
|
||||
}, &appv1.Cluster{
|
||||
Server: "https://mycluster",
|
||||
Config: appv1.ClusterConfig{Username: "bar"},
|
||||
Namespaces: []string{"default"},
|
||||
})
|
||||
|
||||
assert.Len(t, clustersCache.clusters, 0)
|
||||
}
|
||||
|
||||
func TestHandleModEvent_NoChanges(t *testing.T) {
|
||||
clusterCache := &mocks.ClusterCache{}
|
||||
clusterCache.On("Invalidate", mock.Anything).Panic("should not invalidate")
|
||||
@@ -50,3 +78,18 @@ func TestHandleModEvent_NoChanges(t *testing.T) {
|
||||
Config: appv1.ClusterConfig{Username: "bar"},
|
||||
})
|
||||
}
|
||||
|
||||
func TestHandleAddEvent_ClusterExcluded(t *testing.T) {
|
||||
clustersCache := liveStateCache{
|
||||
clusters: map[string]cache.ClusterCache{},
|
||||
clusterFilter: func(cluster *appv1.Cluster) bool {
|
||||
return false
|
||||
},
|
||||
}
|
||||
clustersCache.handleAddEvent(&appv1.Cluster{
|
||||
Server: "https://mycluster",
|
||||
Config: appv1.ClusterConfig{Username: "bar"},
|
||||
})
|
||||
|
||||
assert.Len(t, clustersCache.clusters, 0)
|
||||
}
|
||||
|
||||
162
controller/cache/info.go
vendored
162
controller/cache/info.go
vendored
@@ -2,14 +2,16 @@ package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/text"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
k8snode "k8s.io/kubernetes/pkg/util/node"
|
||||
resourcehelper "k8s.io/kubectl/pkg/util/resource"
|
||||
|
||||
"github.com/argoproj/argo-cd/common"
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/util/resource"
|
||||
)
|
||||
@@ -29,6 +31,9 @@ func populateNodeInfo(un *unstructured.Unstructured, res *ResourceInfo) {
|
||||
case kube.ServiceKind:
|
||||
populateServiceInfo(un, res)
|
||||
return
|
||||
case "Node":
|
||||
populateHostNodeInfo(un, res)
|
||||
return
|
||||
}
|
||||
case "extensions", "networking.k8s.io":
|
||||
switch gvk.Kind {
|
||||
@@ -36,6 +41,21 @@ func populateNodeInfo(un *unstructured.Unstructured, res *ResourceInfo) {
|
||||
populateIngressInfo(un, res)
|
||||
return
|
||||
}
|
||||
case "networking.istio.io":
|
||||
switch gvk.Kind {
|
||||
case "VirtualService":
|
||||
populateIstioVirtualServiceInfo(un, res)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range un.GetAnnotations() {
|
||||
if strings.HasPrefix(k, common.AnnotationKeyLinkPrefix) {
|
||||
if res.NetworkingInfo == nil {
|
||||
res.NetworkingInfo = &v1alpha1.ResourceNetworkingInfo{}
|
||||
}
|
||||
res.NetworkingInfo.ExternalURLs = append(res.NetworkingInfo.ExternalURLs, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,36 +132,85 @@ func populateIngressInfo(un *unstructured.Unstructured, res *ResourceInfo) {
|
||||
}] = true
|
||||
}
|
||||
|
||||
if port, ok, err := unstructured.NestedFieldNoCopy(path, "backend", "servicePort"); ok && err == nil && host != "" && host != nil {
|
||||
stringPort := ""
|
||||
switch typedPod := port.(type) {
|
||||
case int64:
|
||||
stringPort = fmt.Sprintf("%d", typedPod)
|
||||
case float64:
|
||||
stringPort = fmt.Sprintf("%d", int64(typedPod))
|
||||
case string:
|
||||
stringPort = typedPod
|
||||
default:
|
||||
stringPort = fmt.Sprintf("%v", port)
|
||||
if host == nil || host == "" {
|
||||
continue
|
||||
}
|
||||
stringPort := "http"
|
||||
if tls, ok, err := unstructured.NestedSlice(un.Object, "spec", "tls"); ok && err == nil {
|
||||
for i := range tls {
|
||||
tlsline, ok := tls[i].(map[string]interface{})
|
||||
secretName := tlsline["secretName"]
|
||||
if ok && secretName != nil {
|
||||
stringPort = "https"
|
||||
}
|
||||
tlshost := tlsline["host"]
|
||||
if tlshost == host {
|
||||
stringPort = "https"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
externalURL := fmt.Sprintf("%s://%s", stringPort, host)
|
||||
|
||||
subPath := ""
|
||||
if nestedPath, ok, err := unstructured.NestedString(path, "path"); ok && err == nil {
|
||||
subPath = strings.TrimSuffix(nestedPath, "*")
|
||||
}
|
||||
externalURL += subPath
|
||||
urlsSet[externalURL] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
targets := make([]v1alpha1.ResourceRef, 0)
|
||||
for target := range targetsMap {
|
||||
targets = append(targets, target)
|
||||
}
|
||||
|
||||
var urls []string
|
||||
if res.NetworkingInfo != nil {
|
||||
urls = res.NetworkingInfo.ExternalURLs
|
||||
}
|
||||
for url := range urlsSet {
|
||||
urls = append(urls, url)
|
||||
}
|
||||
res.NetworkingInfo = &v1alpha1.ResourceNetworkingInfo{TargetRefs: targets, Ingress: ingress, ExternalURLs: urls}
|
||||
}
|
||||
|
||||
func populateIstioVirtualServiceInfo(un *unstructured.Unstructured, res *ResourceInfo) {
|
||||
targetsMap := make(map[v1alpha1.ResourceRef]bool)
|
||||
|
||||
if rules, ok, err := unstructured.NestedSlice(un.Object, "spec", "http"); ok && err == nil {
|
||||
for i := range rules {
|
||||
rule, ok := rules[i].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
routes, ok, err := unstructured.NestedSlice(rule, "route")
|
||||
if !ok || err != nil {
|
||||
continue
|
||||
}
|
||||
for i := range routes {
|
||||
route, ok := routes[i].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if hostName, ok, err := unstructured.NestedString(route, "destination", "host"); ok && err == nil {
|
||||
hostSplits := strings.Split(hostName, ".")
|
||||
serviceName := hostSplits[0]
|
||||
|
||||
var namespace string
|
||||
if len(hostSplits) >= 2 {
|
||||
namespace = hostSplits[1]
|
||||
} else {
|
||||
namespace = un.GetNamespace()
|
||||
}
|
||||
|
||||
var externalURL string
|
||||
switch stringPort {
|
||||
case "80", "http":
|
||||
externalURL = fmt.Sprintf("http://%s", host)
|
||||
case "443", "https":
|
||||
externalURL = fmt.Sprintf("https://%s", host)
|
||||
default:
|
||||
externalURL = fmt.Sprintf("http://%s:%s", host, stringPort)
|
||||
}
|
||||
|
||||
subPath := ""
|
||||
if nestedPath, ok, err := unstructured.NestedString(path, "path"); ok && err == nil {
|
||||
subPath = nestedPath
|
||||
}
|
||||
|
||||
externalURL += subPath
|
||||
urlsSet[externalURL] = true
|
||||
targetsMap[v1alpha1.ResourceRef{
|
||||
Kind: kube.ServiceKind,
|
||||
Name: serviceName,
|
||||
Namespace: namespace,
|
||||
}] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,11 +219,8 @@ func populateIngressInfo(un *unstructured.Unstructured, res *ResourceInfo) {
|
||||
for target := range targetsMap {
|
||||
targets = append(targets, target)
|
||||
}
|
||||
urls := make([]string, 0)
|
||||
for url := range urlsSet {
|
||||
urls = append(urls, url)
|
||||
}
|
||||
res.NetworkingInfo = &v1alpha1.ResourceNetworkingInfo{TargetRefs: targets, Ingress: ingress, ExternalURLs: urls}
|
||||
|
||||
res.NetworkingInfo = &v1alpha1.ResourceNetworkingInfo{TargetRefs: targets}
|
||||
}
|
||||
|
||||
func populatePodInfo(un *unstructured.Unstructured, res *ResourceInfo) {
|
||||
@@ -242,7 +308,12 @@ func populatePodInfo(un *unstructured.Unstructured, res *ResourceInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
if pod.DeletionTimestamp != nil && pod.Status.Reason == k8snode.NodeUnreachablePodReason {
|
||||
// "NodeLost" = https://github.com/kubernetes/kubernetes/blob/cb8ad64243d48d9a3c26b11b2e0945c098457282/pkg/util/node/node.go#L46
|
||||
// But depending on the k8s.io/kubernetes package just for a constant
|
||||
// is not worth it.
|
||||
// See https://github.com/argoproj/argo-cd/issues/5173
|
||||
// and https://github.com/kubernetes/kubernetes/issues/90358#issuecomment-617859364
|
||||
if pod.DeletionTimestamp != nil && pod.Status.Reason == "NodeLost" {
|
||||
reason = "Unknown"
|
||||
} else if pod.DeletionTimestamp != nil {
|
||||
reason = "Terminating"
|
||||
@@ -251,6 +322,27 @@ func populatePodInfo(un *unstructured.Unstructured, res *ResourceInfo) {
|
||||
if reason != "" {
|
||||
res.Info = append(res.Info, v1alpha1.InfoItem{Name: "Status Reason", Value: reason})
|
||||
}
|
||||
|
||||
req, _ := resourcehelper.PodRequestsAndLimits(&pod)
|
||||
res.PodInfo = &PodInfo{NodeName: pod.Spec.NodeName, ResourceRequests: req}
|
||||
|
||||
res.Info = append(res.Info, v1alpha1.InfoItem{Name: "Node", Value: pod.Spec.NodeName})
|
||||
res.Info = append(res.Info, v1alpha1.InfoItem{Name: "Containers", Value: fmt.Sprintf("%d/%d", readyContainers, totalContainers)})
|
||||
if restarts > 0 {
|
||||
res.Info = append(res.Info, v1alpha1.InfoItem{Name: "Restart Count", Value: fmt.Sprintf("%d", restarts)})
|
||||
}
|
||||
res.NetworkingInfo = &v1alpha1.ResourceNetworkingInfo{Labels: un.GetLabels()}
|
||||
}
|
||||
|
||||
func populateHostNodeInfo(un *unstructured.Unstructured, res *ResourceInfo) {
|
||||
node := v1.Node{}
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, &node)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res.NodeInfo = &NodeInfo{
|
||||
Name: node.Name,
|
||||
Capacity: node.Status.Capacity,
|
||||
SystemInfo: node.Status.NodeInfo,
|
||||
}
|
||||
}
|
||||
|
||||
246
controller/cache/info_test.go
vendored
246
controller/cache/info_test.go
vendored
@@ -5,10 +5,13 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
"github.com/argoproj/pkg/errors"
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
@@ -63,10 +66,96 @@ var (
|
||||
serviceName: helm-guestbook
|
||||
servicePort: https
|
||||
path: /
|
||||
tls:
|
||||
- host: helm-guestbook.com
|
||||
secretName: my-tls-secret
|
||||
status:
|
||||
loadBalancer:
|
||||
ingress:
|
||||
- ip: 107.178.210.11`)
|
||||
|
||||
testIngressWildCardPath = strToUnstructured(`
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: helm-guestbook
|
||||
namespace: default
|
||||
uid: "4"
|
||||
spec:
|
||||
backend:
|
||||
serviceName: not-found-service
|
||||
servicePort: 443
|
||||
rules:
|
||||
- host: helm-guestbook.com
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
serviceName: helm-guestbook
|
||||
servicePort: 443
|
||||
path: /*
|
||||
- backend:
|
||||
serviceName: helm-guestbook
|
||||
servicePort: https
|
||||
path: /*
|
||||
tls:
|
||||
- host: helm-guestbook.com
|
||||
secretName: my-tls-secret
|
||||
status:
|
||||
loadBalancer:
|
||||
ingress:
|
||||
- ip: 107.178.210.11`)
|
||||
|
||||
testIngressWithoutTls = strToUnstructured(`
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: helm-guestbook
|
||||
namespace: default
|
||||
uid: "4"
|
||||
spec:
|
||||
backend:
|
||||
serviceName: not-found-service
|
||||
servicePort: 443
|
||||
rules:
|
||||
- host: helm-guestbook.com
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
serviceName: helm-guestbook
|
||||
servicePort: 443
|
||||
path: /
|
||||
- backend:
|
||||
serviceName: helm-guestbook
|
||||
servicePort: https
|
||||
path: /
|
||||
status:
|
||||
loadBalancer:
|
||||
ingress:
|
||||
- ip: 107.178.210.11`)
|
||||
|
||||
testIstioVirtualService = strToUnstructured(`
|
||||
apiVersion: networking.istio.io/v1alpha3
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: hello-world
|
||||
namespace: demo
|
||||
spec:
|
||||
http:
|
||||
- match:
|
||||
- uri:
|
||||
prefix: "/1"
|
||||
route:
|
||||
- destination:
|
||||
host: service_full.demo.svc.cluster.local
|
||||
- destination:
|
||||
host: service_namespace.namespace
|
||||
- match:
|
||||
- uri:
|
||||
prefix: "/2"
|
||||
route:
|
||||
- destination:
|
||||
host: service
|
||||
`)
|
||||
)
|
||||
|
||||
func TestGetPodInfo(t *testing.T) {
|
||||
@@ -84,16 +173,54 @@ func TestGetPodInfo(t *testing.T) {
|
||||
labels:
|
||||
app: guestbook
|
||||
spec:
|
||||
nodeName: minikube
|
||||
containers:
|
||||
- image: bar`)
|
||||
- image: bar
|
||||
resources:
|
||||
requests:
|
||||
memory: 128Mi
|
||||
`)
|
||||
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(pod, info)
|
||||
assert.Equal(t, []v1alpha1.InfoItem{{Name: "Containers", Value: "0/1"}}, info.Info)
|
||||
assert.Equal(t, []v1alpha1.InfoItem{
|
||||
{Name: "Node", Value: "minikube"},
|
||||
{Name: "Containers", Value: "0/1"},
|
||||
}, info.Info)
|
||||
assert.Equal(t, []string{"bar"}, info.Images)
|
||||
assert.Equal(t, &PodInfo{
|
||||
NodeName: "minikube",
|
||||
ResourceRequests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("128Mi")},
|
||||
}, info.PodInfo)
|
||||
assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{Labels: map[string]string{"app": "guestbook"}}, info.NetworkingInfo)
|
||||
}
|
||||
|
||||
func TestGetNodeInfo(t *testing.T) {
|
||||
node := strToUnstructured(`
|
||||
apiVersion: v1
|
||||
kind: Node
|
||||
metadata:
|
||||
name: minikube
|
||||
spec: {}
|
||||
status:
|
||||
capacity:
|
||||
cpu: "6"
|
||||
memory: 6091320Ki
|
||||
nodeInfo:
|
||||
architecture: amd64
|
||||
operatingSystem: linux
|
||||
osImage: Ubuntu 20.04 LTS
|
||||
`)
|
||||
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(node, info)
|
||||
assert.Equal(t, &NodeInfo{
|
||||
Name: "minikube",
|
||||
Capacity: v1.ResourceList{v1.ResourceMemory: resource.MustParse("6091320Ki"), v1.ResourceCPU: resource.MustParse("6")},
|
||||
SystemInfo: v1.NodeSystemInfo{Architecture: "amd64", OperatingSystem: "linux", OSImage: "Ubuntu 20.04 LTS"},
|
||||
}, info.NodeInfo)
|
||||
}
|
||||
|
||||
func TestGetServiceInfo(t *testing.T) {
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(testService, info)
|
||||
@@ -104,6 +231,29 @@ func TestGetServiceInfo(t *testing.T) {
|
||||
}, info.NetworkingInfo)
|
||||
}
|
||||
|
||||
func TestGetIstioVirtualServiceInfo(t *testing.T) {
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(testIstioVirtualService, info)
|
||||
assert.Equal(t, 0, len(info.Info))
|
||||
require.NotNil(t, info.NetworkingInfo)
|
||||
require.NotNil(t, info.NetworkingInfo.TargetRefs)
|
||||
assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{
|
||||
Kind: kube.ServiceKind,
|
||||
Name: "service_full",
|
||||
Namespace: "demo",
|
||||
})
|
||||
assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{
|
||||
Kind: kube.ServiceKind,
|
||||
Name: "service_namespace",
|
||||
Namespace: "namespace",
|
||||
})
|
||||
assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{
|
||||
Kind: kube.ServiceKind,
|
||||
Name: "service",
|
||||
Namespace: "demo",
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetIngressInfo(t *testing.T) {
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(testIngress, info)
|
||||
@@ -128,7 +278,55 @@ func TestGetIngressInfo(t *testing.T) {
|
||||
}, info.NetworkingInfo)
|
||||
}
|
||||
|
||||
func TestGetIngressInfoNoHost(t *testing.T) {
|
||||
func TestGetIngressInfoWildCardPath(t *testing.T) {
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(testIngressWildCardPath, info)
|
||||
assert.Equal(t, 0, len(info.Info))
|
||||
sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool {
|
||||
return strings.Compare(info.NetworkingInfo.TargetRefs[j].Name, info.NetworkingInfo.TargetRefs[i].Name) < 0
|
||||
})
|
||||
assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
|
||||
Ingress: []v1.LoadBalancerIngress{{IP: "107.178.210.11"}},
|
||||
TargetRefs: []v1alpha1.ResourceRef{{
|
||||
Namespace: "default",
|
||||
Group: "",
|
||||
Kind: kube.ServiceKind,
|
||||
Name: "not-found-service",
|
||||
}, {
|
||||
Namespace: "default",
|
||||
Group: "",
|
||||
Kind: kube.ServiceKind,
|
||||
Name: "helm-guestbook",
|
||||
}},
|
||||
ExternalURLs: []string{"https://helm-guestbook.com/"},
|
||||
}, info.NetworkingInfo)
|
||||
}
|
||||
|
||||
func TestGetIngressInfoWithoutTls(t *testing.T) {
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(testIngressWithoutTls, info)
|
||||
assert.Equal(t, 0, len(info.Info))
|
||||
sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool {
|
||||
return strings.Compare(info.NetworkingInfo.TargetRefs[j].Name, info.NetworkingInfo.TargetRefs[i].Name) < 0
|
||||
})
|
||||
assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
|
||||
Ingress: []v1.LoadBalancerIngress{{IP: "107.178.210.11"}},
|
||||
TargetRefs: []v1alpha1.ResourceRef{{
|
||||
Namespace: "default",
|
||||
Group: "",
|
||||
Kind: kube.ServiceKind,
|
||||
Name: "not-found-service",
|
||||
}, {
|
||||
Namespace: "default",
|
||||
Group: "",
|
||||
Kind: kube.ServiceKind,
|
||||
Name: "helm-guestbook",
|
||||
}},
|
||||
ExternalURLs: []string{"http://helm-guestbook.com/"},
|
||||
}, info.NetworkingInfo)
|
||||
}
|
||||
|
||||
func TestGetIngressInfoWithHost(t *testing.T) {
|
||||
ingress := strToUnstructured(`
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
@@ -143,6 +341,8 @@ func TestGetIngressInfoNoHost(t *testing.T) {
|
||||
serviceName: helm-guestbook
|
||||
servicePort: 443
|
||||
path: /
|
||||
tls:
|
||||
- secretName: my-tls
|
||||
status:
|
||||
loadBalancer:
|
||||
ingress:
|
||||
@@ -162,6 +362,38 @@ func TestGetIngressInfoNoHost(t *testing.T) {
|
||||
ExternalURLs: []string{"https://107.178.210.11/"},
|
||||
}, info.NetworkingInfo)
|
||||
}
|
||||
func TestGetIngressInfoNoHost(t *testing.T) {
|
||||
ingress := strToUnstructured(`
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: helm-guestbook
|
||||
namespace: default
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- backend:
|
||||
serviceName: helm-guestbook
|
||||
servicePort: 443
|
||||
path: /
|
||||
tls:
|
||||
- secretName: my-tls
|
||||
`)
|
||||
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(ingress, info)
|
||||
|
||||
assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
|
||||
TargetRefs: []v1alpha1.ResourceRef{{
|
||||
Namespace: "default",
|
||||
Group: "",
|
||||
Kind: kube.ServiceKind,
|
||||
Name: "helm-guestbook",
|
||||
}},
|
||||
}, info.NetworkingInfo)
|
||||
assert.Equal(t, len(info.NetworkingInfo.ExternalURLs), 0)
|
||||
}
|
||||
func TestExternalUrlWithSubPath(t *testing.T) {
|
||||
ingress := strToUnstructured(`
|
||||
apiVersion: extensions/v1beta1
|
||||
@@ -177,6 +409,8 @@ func TestExternalUrlWithSubPath(t *testing.T) {
|
||||
serviceName: helm-guestbook
|
||||
servicePort: 443
|
||||
path: /my/sub/path/
|
||||
tls:
|
||||
- secretName: my-tls
|
||||
status:
|
||||
loadBalancer:
|
||||
ingress:
|
||||
@@ -211,6 +445,8 @@ func TestExternalUrlWithMultipleSubPaths(t *testing.T) {
|
||||
- backend:
|
||||
serviceName: helm-guestbook-3
|
||||
servicePort: 443
|
||||
tls:
|
||||
- secretName: my-tls
|
||||
status:
|
||||
loadBalancer:
|
||||
ingress:
|
||||
@@ -239,6 +475,8 @@ func TestExternalUrlWithNoSubPath(t *testing.T) {
|
||||
- backend:
|
||||
serviceName: helm-guestbook
|
||||
servicePort: 443
|
||||
tls:
|
||||
- secretName: my-tls
|
||||
status:
|
||||
loadBalancer:
|
||||
ingress:
|
||||
@@ -265,6 +503,8 @@ func TestExternalUrlWithNetworkingApi(t *testing.T) {
|
||||
- backend:
|
||||
serviceName: helm-guestbook
|
||||
servicePort: 443
|
||||
tls:
|
||||
- secretName: my-tls
|
||||
status:
|
||||
loadBalancer:
|
||||
ingress:
|
||||
|
||||
16
controller/cache/mocks/LiveStateCache.go
vendored
16
controller/cache/mocks/LiveStateCache.go
vendored
@@ -7,6 +7,8 @@ import (
|
||||
|
||||
cache "github.com/argoproj/gitops-engine/pkg/cache"
|
||||
|
||||
controllercache "github.com/argoproj/argo-cd/controller/cache"
|
||||
|
||||
kube "github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
@@ -189,6 +191,20 @@ func (_m *LiveStateCache) IterateHierarchy(server string, key kube.ResourceKey,
|
||||
return r0
|
||||
}
|
||||
|
||||
// IterateResources provides a mock function with given fields: server, callback
|
||||
func (_m *LiveStateCache) IterateResources(server string, callback func(*cache.Resource, *controllercache.ResourceInfo)) error {
|
||||
ret := _m.Called(server, callback)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, func(*cache.Resource, *controllercache.ResourceInfo)) error); ok {
|
||||
r0 = rf(server, callback)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Run provides a mock function with given fields: ctx
|
||||
func (_m *LiveStateCache) Run(ctx context.Context) error {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
@@ -23,19 +23,21 @@ const (
|
||||
)
|
||||
|
||||
type clusterInfoUpdater struct {
|
||||
infoSource metrics.HasClustersInfo
|
||||
db db.ArgoDB
|
||||
appLister v1alpha1.ApplicationNamespaceLister
|
||||
cache *appstatecache.Cache
|
||||
infoSource metrics.HasClustersInfo
|
||||
db db.ArgoDB
|
||||
appLister v1alpha1.ApplicationNamespaceLister
|
||||
cache *appstatecache.Cache
|
||||
clusterFilter func(cluster *appv1.Cluster) bool
|
||||
}
|
||||
|
||||
func NewClusterInfoUpdater(
|
||||
infoSource metrics.HasClustersInfo,
|
||||
db db.ArgoDB,
|
||||
appLister v1alpha1.ApplicationNamespaceLister,
|
||||
cache *appstatecache.Cache) *clusterInfoUpdater {
|
||||
cache *appstatecache.Cache,
|
||||
clusterFilter func(cluster *appv1.Cluster) bool) *clusterInfoUpdater {
|
||||
|
||||
return &clusterInfoUpdater{infoSource, db, appLister, cache}
|
||||
return &clusterInfoUpdater{infoSource, db, appLister, cache, clusterFilter}
|
||||
}
|
||||
|
||||
func (c *clusterInfoUpdater) Run(ctx context.Context) {
|
||||
@@ -63,13 +65,24 @@ func (c *clusterInfoUpdater) updateClusters() {
|
||||
if err != nil {
|
||||
log.Warnf("Failed to save clusters info: %v", err)
|
||||
}
|
||||
_ = kube.RunAllAsync(len(clusters.Items), func(i int) error {
|
||||
cluster := clusters.Items[i]
|
||||
var clustersFiltered []appv1.Cluster
|
||||
if c.clusterFilter == nil {
|
||||
clustersFiltered = clusters.Items
|
||||
} else {
|
||||
for i := range clusters.Items {
|
||||
if c.clusterFilter(&clusters.Items[i]) {
|
||||
clustersFiltered = append(clustersFiltered, clusters.Items[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = kube.RunAllAsync(len(clustersFiltered), func(i int) error {
|
||||
cluster := clustersFiltered[i]
|
||||
if err := c.updateClusterInfo(cluster, infoByServer[cluster.Server]); err != nil {
|
||||
log.Warnf("Failed to save clusters info: %v", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
log.Debugf("Successfully saved info of %d clusters", len(clustersFiltered))
|
||||
}
|
||||
|
||||
func (c *clusterInfoUpdater) updateClusterInfo(cluster appv1.Cluster, info *cache.ClusterInfo) error {
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestClusterSecretUpdater(t *testing.T) {
|
||||
|
||||
kubeclientset := fake.NewSimpleClientset()
|
||||
appclientset := appsfake.NewSimpleClientset()
|
||||
appInfomer := appinformers.NewApplicationInformer(appclientset, "", time.Minute, cache.Indexers{})
|
||||
appInformer := appinformers.NewApplicationInformer(appclientset, "", time.Minute, cache.Indexers{})
|
||||
settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
|
||||
argoDB := db.NewDB(fakeNamespace, settingsManager, kubeclientset)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
@@ -57,8 +57,8 @@ func TestClusterSecretUpdater(t *testing.T) {
|
||||
SyncError: test.SyncError,
|
||||
}
|
||||
|
||||
lister := applisters.NewApplicationLister(appInfomer.GetIndexer()).Applications(fakeNamespace)
|
||||
updater := NewClusterInfoUpdater(nil, argoDB, lister, appCache)
|
||||
lister := applisters.NewApplicationLister(appInformer.GetIndexer()).Applications(fakeNamespace)
|
||||
updater := NewClusterInfoUpdater(nil, argoDB, lister, appCache, nil)
|
||||
|
||||
err = updater.updateClusterInfo(*cluster, info)
|
||||
assert.NoError(t, err, "Invoking updateClusterInfo failed.")
|
||||
|
||||
@@ -2,6 +2,8 @@ package metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -10,6 +12,7 @@ import (
|
||||
"github.com/argoproj/gitops-engine/pkg/health"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/robfig/cron"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
|
||||
@@ -30,6 +33,8 @@ type MetricsServer struct {
|
||||
reconcileHistogram *prometheus.HistogramVec
|
||||
redisRequestHistogram *prometheus.HistogramVec
|
||||
registry *prometheus.Registry
|
||||
hostname string
|
||||
cron *cron.Cron
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -91,12 +96,12 @@ var (
|
||||
kubectlExecCounter = prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "argocd_kubectl_exec_total",
|
||||
Help: "Number of kubectl executions",
|
||||
}, []string{"command"})
|
||||
}, []string{"hostname", "command"})
|
||||
|
||||
kubectlExecPendingGauge = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "argocd_kubectl_exec_pending",
|
||||
Help: "Number of pending kubectl executions",
|
||||
}, []string{"command"})
|
||||
}, []string{"hostname", "command"})
|
||||
|
||||
reconcileHistogram = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
@@ -118,7 +123,7 @@ var (
|
||||
Name: "argocd_redis_request_total",
|
||||
Help: "Number of kubernetes requests executed during application reconciliation.",
|
||||
},
|
||||
[]string{"initiator", "failed"},
|
||||
[]string{"hostname", "initiator", "failed"},
|
||||
)
|
||||
|
||||
redisRequestHistogram = prometheus.NewHistogramVec(
|
||||
@@ -127,14 +132,18 @@ var (
|
||||
Help: "Redis requests duration.",
|
||||
Buckets: []float64{0.01, 0.05, 0.10, 0.25, .5, 1},
|
||||
},
|
||||
[]string{"initiator"},
|
||||
[]string{"hostname", "initiator"},
|
||||
)
|
||||
)
|
||||
|
||||
// NewMetricsServer returns a new prometheus server which collects application metrics
|
||||
func NewMetricsServer(addr string, appLister applister.ApplicationLister, healthCheck func() error) *MetricsServer {
|
||||
func NewMetricsServer(addr string, appLister applister.ApplicationLister, appFilter func(obj interface{}) bool, healthCheck func(r *http.Request) error) (*MetricsServer, error) {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mux := http.NewServeMux()
|
||||
registry := NewAppRegistry(appLister)
|
||||
registry := NewAppRegistry(appLister, appFilter)
|
||||
mux.Handle(MetricsPath, promhttp.HandlerFor(prometheus.Gatherers{
|
||||
// contains app controller specific metrics
|
||||
registry,
|
||||
@@ -166,7 +175,9 @@ func NewMetricsServer(addr string, appLister applister.ApplicationLister, health
|
||||
clusterEventsCounter: clusterEventsCounter,
|
||||
redisRequestCounter: redisRequestCounter,
|
||||
redisRequestHistogram: redisRequestHistogram,
|
||||
}
|
||||
hostname: hostname,
|
||||
cron: cron.New(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MetricsServer) RegisterClustersInfoSource(ctx context.Context, source HasClustersInfo) {
|
||||
@@ -184,15 +195,15 @@ func (m *MetricsServer) IncSync(app *argoappv1.Application, state *argoappv1.Ope
|
||||
}
|
||||
|
||||
func (m *MetricsServer) IncKubectlExec(command string) {
|
||||
m.kubectlExecCounter.WithLabelValues(command).Inc()
|
||||
m.kubectlExecCounter.WithLabelValues(m.hostname, command).Inc()
|
||||
}
|
||||
|
||||
func (m *MetricsServer) IncKubectlExecPending(command string) {
|
||||
m.kubectlExecPendingGauge.WithLabelValues(command).Inc()
|
||||
m.kubectlExecPendingGauge.WithLabelValues(m.hostname, command).Inc()
|
||||
}
|
||||
|
||||
func (m *MetricsServer) DecKubectlExecPending(command string) {
|
||||
m.kubectlExecPendingGauge.WithLabelValues(command).Dec()
|
||||
m.kubectlExecPendingGauge.WithLabelValues(m.hostname, command).Dec()
|
||||
}
|
||||
|
||||
// IncClusterEventsCount increments the number of cluster events
|
||||
@@ -215,12 +226,12 @@ func (m *MetricsServer) IncKubernetesRequest(app *argoappv1.Application, server,
|
||||
}
|
||||
|
||||
func (m *MetricsServer) IncRedisRequest(failed bool) {
|
||||
m.redisRequestCounter.WithLabelValues("argocd-application-controller", strconv.FormatBool(failed)).Inc()
|
||||
m.redisRequestCounter.WithLabelValues(m.hostname, "argocd-application-controller", strconv.FormatBool(failed)).Inc()
|
||||
}
|
||||
|
||||
// ObserveRedisRequestDuration observes redis request duration
|
||||
func (m *MetricsServer) ObserveRedisRequestDuration(duration time.Duration) {
|
||||
m.redisRequestHistogram.WithLabelValues("argocd-application-controller").Observe(duration.Seconds())
|
||||
m.redisRequestHistogram.WithLabelValues(m.hostname, "argocd-application-controller").Observe(duration.Seconds())
|
||||
}
|
||||
|
||||
// IncReconcile increments the reconcile counter for an application
|
||||
@@ -228,21 +239,53 @@ func (m *MetricsServer) IncReconcile(app *argoappv1.Application, duration time.D
|
||||
m.reconcileHistogram.WithLabelValues(app.Namespace, app.Spec.Destination.Server).Observe(duration.Seconds())
|
||||
}
|
||||
|
||||
// HasExpiration return true if expiration is set
|
||||
func (m *MetricsServer) HasExpiration() bool {
|
||||
return len(m.cron.Entries()) > 0
|
||||
}
|
||||
|
||||
// SetExpiration reset Prometheus metrics based on time duration interval
|
||||
func (m *MetricsServer) SetExpiration(cacheExpiration time.Duration) error {
|
||||
if m.HasExpiration() {
|
||||
return errors.New("Expiration is already set")
|
||||
}
|
||||
|
||||
err := m.cron.AddFunc(fmt.Sprintf("@every %s", cacheExpiration), func() {
|
||||
log.Infof("Reset Prometheus metrics based on existing expiration '%v'", cacheExpiration)
|
||||
m.syncCounter.Reset()
|
||||
m.kubectlExecCounter.Reset()
|
||||
m.kubectlExecPendingGauge.Reset()
|
||||
m.k8sRequestCounter.Reset()
|
||||
m.clusterEventsCounter.Reset()
|
||||
m.redisRequestCounter.Reset()
|
||||
m.reconcileHistogram.Reset()
|
||||
m.redisRequestHistogram.Reset()
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.cron.Start()
|
||||
return nil
|
||||
}
|
||||
|
||||
type appCollector struct {
|
||||
store applister.ApplicationLister
|
||||
store applister.ApplicationLister
|
||||
appFilter func(obj interface{}) bool
|
||||
}
|
||||
|
||||
// NewAppCollector returns a prometheus collector for application metrics
|
||||
func NewAppCollector(appLister applister.ApplicationLister) prometheus.Collector {
|
||||
func NewAppCollector(appLister applister.ApplicationLister, appFilter func(obj interface{}) bool) prometheus.Collector {
|
||||
return &appCollector{
|
||||
store: appLister,
|
||||
store: appLister,
|
||||
appFilter: appFilter,
|
||||
}
|
||||
}
|
||||
|
||||
// NewAppRegistry creates a new prometheus registry that collects applications
|
||||
func NewAppRegistry(appLister applister.ApplicationLister) *prometheus.Registry {
|
||||
func NewAppRegistry(appLister applister.ApplicationLister, appFilter func(obj interface{}) bool) *prometheus.Registry {
|
||||
registry := prometheus.NewRegistry()
|
||||
registry.MustRegister(NewAppCollector(appLister))
|
||||
registry.MustRegister(NewAppCollector(appLister, appFilter))
|
||||
return registry
|
||||
}
|
||||
|
||||
@@ -261,7 +304,9 @@ func (c *appCollector) Collect(ch chan<- prometheus.Metric) {
|
||||
return
|
||||
}
|
||||
for _, app := range apps {
|
||||
collectApps(ch, app)
|
||||
if c.appFilter(app) {
|
||||
collectApps(ch, app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -112,10 +112,14 @@ status:
|
||||
status: Healthy
|
||||
`
|
||||
|
||||
var noOpHealthCheck = func() error {
|
||||
var noOpHealthCheck = func(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var appFilter = func(obj interface{}) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func newFakeApp(fakeAppYAML string) *argoappv1.Application {
|
||||
var app argoappv1.Application
|
||||
err := yaml.Unmarshal([]byte(fakeAppYAML), &app)
|
||||
@@ -146,7 +150,8 @@ func newFakeLister(fakeAppYAMLs ...string) (context.CancelFunc, applister.Applic
|
||||
func testApp(t *testing.T, fakeAppYAMLs []string, expectedResponse string) {
|
||||
cancel, appLister := newFakeLister(fakeAppYAMLs...)
|
||||
defer cancel()
|
||||
metricsServ := NewMetricsServer("localhost:8082", appLister, noOpHealthCheck)
|
||||
metricsServ, err := NewMetricsServer("localhost:8082", appLister, appFilter, noOpHealthCheck)
|
||||
assert.NoError(t, err)
|
||||
req, err := http.NewRequest("GET", "/metrics", nil)
|
||||
assert.NoError(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
@@ -217,7 +222,8 @@ argocd_app_sync_status{name="my-app",namespace="argocd",project="important-proje
|
||||
func TestMetricsSyncCounter(t *testing.T) {
|
||||
cancel, appLister := newFakeLister()
|
||||
defer cancel()
|
||||
metricsServ := NewMetricsServer("localhost:8082", appLister, noOpHealthCheck)
|
||||
metricsServ, err := NewMetricsServer("localhost:8082", appLister, appFilter, noOpHealthCheck)
|
||||
assert.NoError(t, err)
|
||||
|
||||
appSyncTotal := `
|
||||
# HELP argocd_app_sync_total Number of application syncs.
|
||||
@@ -254,10 +260,22 @@ func assertMetricsPrinted(t *testing.T, expectedLines, body string) {
|
||||
}
|
||||
}
|
||||
|
||||
// assertMetricNotPrinted
|
||||
func assertMetricsNotPrinted(t *testing.T, expectedLines, body string) {
|
||||
for _, line := range strings.Split(expectedLines, "\n") {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
assert.False(t, strings.Contains(body, expectedLines))
|
||||
}
|
||||
}
|
||||
|
||||
func TestReconcileMetrics(t *testing.T) {
|
||||
cancel, appLister := newFakeLister()
|
||||
defer cancel()
|
||||
metricsServ := NewMetricsServer("localhost:8082", appLister, noOpHealthCheck)
|
||||
metricsServ, err := NewMetricsServer("localhost:8082", appLister, appFilter, noOpHealthCheck)
|
||||
assert.NoError(t, err)
|
||||
|
||||
appReconcileMetrics := `
|
||||
# HELP argocd_app_reconcile Application reconciliation performance.
|
||||
# TYPE argocd_app_reconcile histogram
|
||||
@@ -284,3 +302,40 @@ argocd_app_reconcile_count{dest_server="https://localhost:6443",namespace="argoc
|
||||
log.Println(body)
|
||||
assertMetricsPrinted(t, appReconcileMetrics, body)
|
||||
}
|
||||
|
||||
func TestMetricsReset(t *testing.T) {
|
||||
cancel, appLister := newFakeLister()
|
||||
defer cancel()
|
||||
metricsServ, err := NewMetricsServer("localhost:8082", appLister, appFilter, noOpHealthCheck)
|
||||
assert.NoError(t, err)
|
||||
|
||||
appSyncTotal := `
|
||||
# HELP argocd_app_sync_total Number of application syncs.
|
||||
# TYPE argocd_app_sync_total counter
|
||||
argocd_app_sync_total{dest_server="https://localhost:6443",name="my-app",namespace="argocd",phase="Error",project="important-project"} 1
|
||||
argocd_app_sync_total{dest_server="https://localhost:6443",name="my-app",namespace="argocd",phase="Failed",project="important-project"} 1
|
||||
argocd_app_sync_total{dest_server="https://localhost:6443",name="my-app",namespace="argocd",phase="Succeeded",project="important-project"} 2
|
||||
`
|
||||
|
||||
req, err := http.NewRequest("GET", "/metrics", nil)
|
||||
assert.NoError(t, err)
|
||||
rr := httptest.NewRecorder()
|
||||
metricsServ.Handler.ServeHTTP(rr, req)
|
||||
assert.Equal(t, rr.Code, http.StatusOK)
|
||||
body := rr.Body.String()
|
||||
assertMetricsPrinted(t, appSyncTotal, body)
|
||||
|
||||
err = metricsServ.SetExpiration(time.Second)
|
||||
assert.NoError(t, err)
|
||||
time.Sleep(2 * time.Second)
|
||||
req, err = http.NewRequest("GET", "/metrics", nil)
|
||||
assert.NoError(t, err)
|
||||
rr = httptest.NewRecorder()
|
||||
metricsServ.Handler.ServeHTTP(rr, req)
|
||||
assert.Equal(t, rr.Code, http.StatusOK)
|
||||
body = rr.Body.String()
|
||||
log.Println(body)
|
||||
assertMetricsNotPrinted(t, appSyncTotal, body)
|
||||
err = metricsServ.SetExpiration(time.Second)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
53
controller/sharding/sharding.go
Normal file
53
controller/sharding/sharding.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package sharding
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
|
||||
func InferShard() (int, error) {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
parts := strings.Split(hostname, "-")
|
||||
if len(parts) == 0 {
|
||||
return 0, fmt.Errorf("hostname should ends with shard number separated by '-' but got: %s", hostname)
|
||||
}
|
||||
shard, err := strconv.Atoi(parts[len(parts)-1])
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("hostname should ends with shard number separated by '-' but got: %s", hostname)
|
||||
}
|
||||
return shard, nil
|
||||
}
|
||||
|
||||
// getShardByID calculates cluster shard as `clusterSecret.UID % replicas count`
|
||||
func getShardByID(id string, replicas int) int {
|
||||
if id == "" {
|
||||
return 0
|
||||
} else {
|
||||
h := fnv.New32a()
|
||||
_, _ = h.Write([]byte(id))
|
||||
return int(h.Sum32() % uint32(replicas))
|
||||
}
|
||||
}
|
||||
|
||||
func GetClusterFilter(replicas int, shard int) func(c *v1alpha1.Cluster) bool {
|
||||
return func(c *v1alpha1.Cluster) bool {
|
||||
clusterShard := 0
|
||||
// cluster might be nil if app is using invalid cluster URL, assume shard 0 in this case.
|
||||
if c != nil {
|
||||
if c.Shard != nil {
|
||||
clusterShard = int(*c.Shard)
|
||||
} else {
|
||||
clusterShard = getShardByID(c.ID, replicas)
|
||||
}
|
||||
}
|
||||
return clusterShard == shard
|
||||
}
|
||||
}
|
||||
29
controller/sharding/sharding_test.go
Normal file
29
controller/sharding/sharding_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package sharding
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetShardByID_NotEmptyID(t *testing.T) {
|
||||
assert.Equal(t, 0, getShardByID("1", 2))
|
||||
assert.Equal(t, 1, getShardByID("2", 2))
|
||||
assert.Equal(t, 0, getShardByID("3", 2))
|
||||
assert.Equal(t, 1, getShardByID("4", 2))
|
||||
}
|
||||
|
||||
func TestGetShardByID_EmptyID(t *testing.T) {
|
||||
shard := getShardByID("", 10)
|
||||
assert.Equal(t, 0, shard)
|
||||
}
|
||||
|
||||
func TestGetClusterFilter(t *testing.T) {
|
||||
filter := GetClusterFilter(2, 1)
|
||||
assert.False(t, filter(&v1alpha1.Cluster{ID: "1"}))
|
||||
assert.True(t, filter(&v1alpha1.Cluster{ID: "2"}))
|
||||
assert.False(t, filter(&v1alpha1.Cluster{ID: "3"}))
|
||||
assert.True(t, filter(&v1alpha1.Cluster{ID: "4"}))
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/diff"
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
hookutil "github.com/argoproj/gitops-engine/pkg/sync/hook"
|
||||
"github.com/argoproj/gitops-engine/pkg/sync/ignore"
|
||||
resourceutil "github.com/argoproj/gitops-engine/pkg/sync/resource"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/io"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
kubeutil "github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
log "github.com/sirupsen/logrus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -29,9 +30,11 @@ import (
|
||||
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
|
||||
"github.com/argoproj/argo-cd/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/util/argo"
|
||||
appstatecache "github.com/argoproj/argo-cd/util/cache/appstate"
|
||||
"github.com/argoproj/argo-cd/util/db"
|
||||
"github.com/argoproj/argo-cd/util/gpg"
|
||||
argohealth "github.com/argoproj/argo-cd/util/health"
|
||||
"github.com/argoproj/argo-cd/util/io"
|
||||
"github.com/argoproj/argo-cd/util/settings"
|
||||
"github.com/argoproj/argo-cd/util/stats"
|
||||
)
|
||||
@@ -44,15 +47,16 @@ func (r *resourceInfoProviderStub) IsNamespaced(_ schema.GroupKind) (bool, error
|
||||
}
|
||||
|
||||
type managedResource struct {
|
||||
Target *unstructured.Unstructured
|
||||
Live *unstructured.Unstructured
|
||||
Diff diff.DiffResult
|
||||
Group string
|
||||
Version string
|
||||
Kind string
|
||||
Namespace string
|
||||
Name string
|
||||
Hook bool
|
||||
Target *unstructured.Unstructured
|
||||
Live *unstructured.Unstructured
|
||||
Diff diff.DiffResult
|
||||
Group string
|
||||
Version string
|
||||
Kind string
|
||||
Namespace string
|
||||
Name string
|
||||
Hook bool
|
||||
ResourceVersion string
|
||||
}
|
||||
|
||||
func GetLiveObjsForApplicationHealth(resources []managedResource, statuses []appv1.ResourceStatus) ([]*appv1.ResourceStatus, []*unstructured.Unstructured) {
|
||||
@@ -84,7 +88,8 @@ type comparisonResult struct {
|
||||
diffNormalizer diff.Normalizer
|
||||
appSourceType v1alpha1.ApplicationSourceType
|
||||
// timings maps phases of comparison to the duration it took to complete (for statistical purposes)
|
||||
timings map[string]time.Duration
|
||||
timings map[string]time.Duration
|
||||
diffResultList *diff.DiffResultList
|
||||
}
|
||||
|
||||
func (res *comparisonResult) GetSyncStatus() *v1alpha1.SyncStatus {
|
||||
@@ -97,15 +102,17 @@ func (res *comparisonResult) GetHealthStatus() *v1alpha1.HealthStatus {
|
||||
|
||||
// appStateManager allows to compare applications to git
|
||||
type appStateManager struct {
|
||||
metricsServer *metrics.MetricsServer
|
||||
db db.ArgoDB
|
||||
settingsMgr *settings.SettingsManager
|
||||
appclientset appclientset.Interface
|
||||
projInformer cache.SharedIndexInformer
|
||||
kubectl kubeutil.Kubectl
|
||||
repoClientset apiclient.Clientset
|
||||
liveStateCache statecache.LiveStateCache
|
||||
namespace string
|
||||
metricsServer *metrics.MetricsServer
|
||||
db db.ArgoDB
|
||||
settingsMgr *settings.SettingsManager
|
||||
appclientset appclientset.Interface
|
||||
projInformer cache.SharedIndexInformer
|
||||
kubectl kubeutil.Kubectl
|
||||
repoClientset apiclient.Clientset
|
||||
liveStateCache statecache.LiveStateCache
|
||||
cache *appstatecache.Cache
|
||||
namespace string
|
||||
statusRefreshTimeout time.Duration
|
||||
}
|
||||
|
||||
func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1.ApplicationSource, appLabelKey, revision string, noCache, verifySignature bool) ([]*unstructured.Unstructured, *apiclient.ManifestResponse, error) {
|
||||
@@ -160,7 +167,7 @@ func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1
|
||||
Revision: revision,
|
||||
NoCache: noCache,
|
||||
AppLabelKey: appLabelKey,
|
||||
AppLabelValue: app.Name,
|
||||
AppName: app.Name,
|
||||
Namespace: app.Spec.Destination.Namespace,
|
||||
ApplicationSource: &source,
|
||||
Plugins: tools,
|
||||
@@ -266,7 +273,7 @@ func (m *appStateManager) getComparisonSettings(app *appv1.Application) (string,
|
||||
func verifyGnuPGSignature(revision string, project *appv1.AppProject, manifestInfo *apiclient.ManifestResponse) []appv1.ApplicationCondition {
|
||||
now := metav1.Now()
|
||||
conditions := make([]appv1.ApplicationCondition, 0)
|
||||
// We need to have some data in the verificatin result to parse, otherwise there was no signature
|
||||
// We need to have some data in the verification result to parse, otherwise there was no signature
|
||||
if manifestInfo.VerifyResult != "" {
|
||||
verifyResult, err := gpg.ParseGitCommitVerification(manifestInfo.VerifyResult)
|
||||
if err != nil {
|
||||
@@ -305,6 +312,56 @@ func verifyGnuPGSignature(revision string, project *appv1.AppProject, manifestIn
|
||||
return conditions
|
||||
}
|
||||
|
||||
func (m *appStateManager) diffArrayCached(configArray []*unstructured.Unstructured, liveArray []*unstructured.Unstructured, cachedDiff []*appv1.ResourceDiff, opts ...diff.Option) (*diff.DiffResultList, error) {
|
||||
numItems := len(configArray)
|
||||
if len(liveArray) != numItems {
|
||||
return nil, fmt.Errorf("left and right arrays have mismatched lengths")
|
||||
}
|
||||
|
||||
diffByKey := map[kube.ResourceKey]*appv1.ResourceDiff{}
|
||||
for i := range cachedDiff {
|
||||
res := cachedDiff[i]
|
||||
diffByKey[kube.NewResourceKey(res.Group, res.Kind, res.Namespace, res.Name)] = cachedDiff[i]
|
||||
}
|
||||
|
||||
diffResultList := diff.DiffResultList{
|
||||
Diffs: make([]diff.DiffResult, numItems),
|
||||
}
|
||||
|
||||
for i := 0; i < numItems; i++ {
|
||||
config := configArray[i]
|
||||
live := liveArray[i]
|
||||
resourceVersion := ""
|
||||
var key kube.ResourceKey
|
||||
if live != nil {
|
||||
key = kube.GetResourceKey(live)
|
||||
resourceVersion = live.GetResourceVersion()
|
||||
} else {
|
||||
key = kube.GetResourceKey(config)
|
||||
}
|
||||
var dr *diff.DiffResult
|
||||
if cachedDiff, ok := diffByKey[key]; ok && cachedDiff.ResourceVersion == resourceVersion {
|
||||
dr = &diff.DiffResult{
|
||||
NormalizedLive: []byte(cachedDiff.NormalizedLiveState),
|
||||
PredictedLive: []byte(cachedDiff.PredictedLiveState),
|
||||
Modified: cachedDiff.Modified,
|
||||
}
|
||||
} else {
|
||||
res, err := diff.Diff(configArray[i], liveArray[i], opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dr = res
|
||||
}
|
||||
diffResultList.Diffs[i] = *dr
|
||||
if dr != nil && dr.Modified {
|
||||
diffResultList.Modified = true
|
||||
}
|
||||
}
|
||||
|
||||
return &diffResultList, nil
|
||||
}
|
||||
|
||||
// CompareAppState compares application git state to the live app state, using the specified
|
||||
// revision and supplied source. If revision or overrides are empty, then compares against
|
||||
// revision and overrides in the app spec.
|
||||
@@ -413,7 +470,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
|
||||
if appInstanceName != "" && appInstanceName != app.Name {
|
||||
conditions = append(conditions, v1alpha1.ApplicationCondition{
|
||||
Type: v1alpha1.ApplicationConditionSharedResourceWarning,
|
||||
Message: fmt.Sprintf("%s/%s is part of a different application: %s", liveObj.GetKind(), liveObj.GetName(), appInstanceName),
|
||||
Message: fmt.Sprintf("%s/%s is part of applications %s and %s", liveObj.GetKind(), liveObj.GetName(), app.Name, appInstanceName),
|
||||
LastTransitionTime: &now,
|
||||
})
|
||||
}
|
||||
@@ -426,12 +483,31 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
|
||||
compareOptions, err := m.settingsMgr.GetResourceCompareOptions()
|
||||
if err != nil {
|
||||
log.Warnf("Could not get compare options from ConfigMap (assuming defaults): %v", err)
|
||||
compareOptions = diff.GetDefaultDiffOptions()
|
||||
compareOptions = settings.GetDefaultDiffOptions()
|
||||
}
|
||||
|
||||
logCtx.Debugf("built managed objects list")
|
||||
// Do the actual comparison
|
||||
diffResults, err := diff.DiffArray(reconciliation.Target, reconciliation.Live, diffNormalizer, compareOptions)
|
||||
var diffResults *diff.DiffResultList
|
||||
|
||||
diffOpts := []diff.Option{
|
||||
diff.WithNormalizer(diffNormalizer),
|
||||
diff.IgnoreAggregatedRoles(compareOptions.IgnoreAggregatedRoles),
|
||||
}
|
||||
cachedDiff := make([]*appv1.ResourceDiff, 0)
|
||||
// restore comparison using cached diff result if previous comparison was performed for the same revision
|
||||
revisionChanged := manifestInfo == nil || app.Status.Sync.Revision != manifestInfo.Revision
|
||||
specChanged := !reflect.DeepEqual(app.Status.Sync.ComparedTo, appv1.ComparedTo{Source: app.Spec.Source, Destination: app.Spec.Destination})
|
||||
|
||||
_, refreshRequested := app.IsRefreshRequested()
|
||||
noCache = noCache || refreshRequested || app.Status.Expired(m.statusRefreshTimeout)
|
||||
|
||||
if noCache || specChanged || revisionChanged || m.cache.GetAppManagedResources(app.Name, &cachedDiff) != nil {
|
||||
// (rare) cache miss
|
||||
diffResults, err = diff.DiffArray(reconciliation.Target, reconciliation.Live, diffOpts...)
|
||||
} else {
|
||||
diffResults, err = m.diffArrayCached(reconciliation.Target, reconciliation.Live, cachedDiff, diffOpts...)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
diffResults = &diff.DiffResultList{}
|
||||
failedToLoadObjs = true
|
||||
@@ -499,16 +575,22 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
|
||||
if failedToLoadObjs {
|
||||
resState.Status = v1alpha1.SyncStatusCodeUnknown
|
||||
}
|
||||
|
||||
resourceVersion := ""
|
||||
if liveObj != nil {
|
||||
resourceVersion = liveObj.GetResourceVersion()
|
||||
}
|
||||
managedResources[i] = managedResource{
|
||||
Name: resState.Name,
|
||||
Namespace: resState.Namespace,
|
||||
Group: resState.Group,
|
||||
Kind: resState.Kind,
|
||||
Version: resState.Version,
|
||||
Live: liveObj,
|
||||
Target: targetObj,
|
||||
Diff: diffResult,
|
||||
Hook: resState.Hook,
|
||||
Name: resState.Name,
|
||||
Namespace: resState.Namespace,
|
||||
Group: resState.Group,
|
||||
Kind: resState.Kind,
|
||||
Version: resState.Version,
|
||||
Live: liveObj,
|
||||
Target: targetObj,
|
||||
Diff: diffResult,
|
||||
Hook: resState.Hook,
|
||||
ResourceVersion: resourceVersion,
|
||||
}
|
||||
resourceSummaries[i] = resState
|
||||
}
|
||||
@@ -538,7 +620,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
|
||||
}
|
||||
|
||||
// Git has already performed the signature verification via its GPG interface, and the result is available
|
||||
// in the manifest info received from the repository server. We now need to form our oppinion about the result
|
||||
// in the manifest info received from the repository server. We now need to form our opinion about the result
|
||||
// and stop processing if we do not agree about the outcome.
|
||||
if gpg.IsGPGEnabled() && verifySignature && manifestInfo != nil {
|
||||
conditions = append(conditions, verifyGnuPGSignature(revision, project, manifestInfo)...)
|
||||
@@ -551,6 +633,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
|
||||
managedResources: managedResources,
|
||||
reconciliationResult: reconciliation,
|
||||
diffNormalizer: diffNormalizer,
|
||||
diffResultList: diffResults,
|
||||
}
|
||||
if manifestInfo != nil {
|
||||
compRes.appSourceType = v1alpha1.ApplicationSourceType(manifestInfo.SourceType)
|
||||
@@ -604,16 +687,20 @@ func NewAppStateManager(
|
||||
liveStateCache statecache.LiveStateCache,
|
||||
projInformer cache.SharedIndexInformer,
|
||||
metricsServer *metrics.MetricsServer,
|
||||
cache *appstatecache.Cache,
|
||||
statusRefreshTimeout time.Duration,
|
||||
) AppStateManager {
|
||||
return &appStateManager{
|
||||
liveStateCache: liveStateCache,
|
||||
db: db,
|
||||
appclientset: appclientset,
|
||||
kubectl: kubectl,
|
||||
repoClientset: repoClientset,
|
||||
namespace: namespace,
|
||||
settingsMgr: settingsMgr,
|
||||
projInformer: projInformer,
|
||||
metricsServer: metricsServer,
|
||||
liveStateCache: liveStateCache,
|
||||
cache: cache,
|
||||
db: db,
|
||||
appclientset: appclientset,
|
||||
kubectl: kubectl,
|
||||
repoClientset: repoClientset,
|
||||
namespace: namespace,
|
||||
settingsMgr: settingsMgr,
|
||||
projInformer: projInformer,
|
||||
metricsServer: metricsServer,
|
||||
statusRefreshTimeout: statusRefreshTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package controller
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
@@ -14,16 +16,24 @@ import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
cdcommon "github.com/argoproj/argo-cd/common"
|
||||
"github.com/argoproj/argo-cd/controller/metrics"
|
||||
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
|
||||
listersv1alpha1 "github.com/argoproj/argo-cd/pkg/client/listers/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/util/argo"
|
||||
logutils "github.com/argoproj/argo-cd/util/log"
|
||||
"github.com/argoproj/argo-cd/util/lua"
|
||||
"github.com/argoproj/argo-cd/util/rand"
|
||||
)
|
||||
|
||||
var syncIdPrefix uint64 = 0
|
||||
|
||||
const (
|
||||
// EnvVarSyncWaveDelay is an environment variable which controls the delay in seconds between
|
||||
// each sync-wave
|
||||
EnvVarSyncWaveDelay = "ARGOCD_SYNC_WAVE_DELAY"
|
||||
)
|
||||
|
||||
func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha1.OperationState) {
|
||||
// Sync requests might be requested with ambiguous revisions (e.g. master, HEAD, v1.2.3).
|
||||
// This can change meaning when resuming operations (e.g a hook sync). After calculating a
|
||||
@@ -68,7 +78,7 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
|
||||
revision = syncOp.Revision
|
||||
}
|
||||
|
||||
proj, err := argo.GetAppProject(&app.Spec, listersv1alpha1.NewAppProjectLister(m.projInformer.GetIndexer()), m.namespace)
|
||||
proj, err := argo.GetAppProject(&app.Spec, listersv1alpha1.NewAppProjectLister(m.projInformer.GetIndexer()), m.namespace, m.settingsMgr)
|
||||
if err != nil {
|
||||
state.Phase = common.OperationError
|
||||
state.Message = fmt.Sprintf("Failed to load application project: %v", err)
|
||||
@@ -125,7 +135,15 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
|
||||
Order: i + 1,
|
||||
})
|
||||
}
|
||||
syncCtx, err := sync.NewSyncContext(compareResult.syncStatus.Revision, compareResult.reconciliationResult, restConfig, rawConfig, m.kubectl, app.Spec.Destination.Namespace, logEntry,
|
||||
|
||||
syncCtx, err := sync.NewSyncContext(
|
||||
compareResult.syncStatus.Revision,
|
||||
compareResult.reconciliationResult,
|
||||
restConfig,
|
||||
rawConfig,
|
||||
m.kubectl,
|
||||
app.Spec.Destination.Namespace,
|
||||
sync.WithLogr(logutils.NewLogrusLogger(logEntry)),
|
||||
sync.WithHealthOverride(lua.ResourceHealthOverrides(resourceOverrides)),
|
||||
sync.WithPermissionValidator(func(un *unstructured.Unstructured, res *v1.APIResource) error {
|
||||
if !proj.IsGroupKindPermitted(un.GroupVersionKind().GroupKind(), res.Namespaced) {
|
||||
@@ -137,12 +155,21 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
|
||||
return nil
|
||||
}),
|
||||
sync.WithOperationSettings(syncOp.DryRun, syncOp.Prune, syncOp.SyncStrategy.Force(), syncOp.IsApplyStrategy() || len(syncOp.Resources) > 0),
|
||||
sync.WithInitialState(state.Phase, state.Message, initialResourcesRes),
|
||||
sync.WithInitialState(state.Phase, state.Message, initialResourcesRes, state.StartedAt),
|
||||
sync.WithResourcesFilter(func(key kube.ResourceKey, target *unstructured.Unstructured, live *unstructured.Unstructured) bool {
|
||||
return len(syncOp.Resources) == 0 || argo.ContainsSyncResource(key.Name, key.Namespace, schema.GroupVersionKind{Kind: key.Kind, Group: key.Group}, syncOp.Resources)
|
||||
}),
|
||||
sync.WithManifestValidation(!syncOp.SyncOptions.HasOption("Validate=false")),
|
||||
sync.WithNamespaceCreation(syncOp.SyncOptions.HasOption("CreateNamespace=true")),
|
||||
sync.WithNamespaceCreation(syncOp.SyncOptions.HasOption("CreateNamespace=true"), func(un *unstructured.Unstructured) bool {
|
||||
if un != nil && kube.GetAppInstanceLabel(un, cdcommon.LabelKeyAppInstance) != "" {
|
||||
kube.UnsetLabel(un, cdcommon.LabelKeyAppInstance)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}),
|
||||
sync.WithSyncWaveHook(delayBetweenSyncWaves),
|
||||
sync.WithPruneLast(syncOp.SyncOptions.HasOption("PruneLast=true")),
|
||||
sync.WithResourceModificationChecker(syncOp.SyncOptions.HasOption("ApplyOutOfSyncOnly=true"), compareResult.diffResultList),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
@@ -185,3 +212,25 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// delayBetweenSyncWaves is a gitops-engine SyncWaveHook which introduces an artificial delay
|
||||
// between each sync wave. We introduce an artificial delay in order give other controllers a
|
||||
// _chance_ to react to the spec change that we just applied. This is important because without
|
||||
// this, Argo CD will likely assess resource health too quickly (against the stale object), causing
|
||||
// hooks to fire prematurely. See: https://github.com/argoproj/argo-cd/issues/4669.
|
||||
// Note, this is not foolproof, since a proper fix would require the CRD record
|
||||
// status.observedGeneration coupled with a health.lua that verifies
|
||||
// status.observedGeneration == metadata.generation
|
||||
func delayBetweenSyncWaves(phase common.SyncPhase, wave int, finalWave bool) error {
|
||||
if !finalWave {
|
||||
delaySec := 2
|
||||
if delaySecStr := os.Getenv(EnvVarSyncWaveDelay); delaySecStr != "" {
|
||||
if val, err := strconv.Atoi(delaySecStr); err == nil {
|
||||
delaySec = val
|
||||
}
|
||||
}
|
||||
duration := time.Duration(delaySec) * time.Second
|
||||
time.Sleep(duration)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Please refer to [the Contribution Guide](https://argoproj.github.io/argo-cd/developer-guide/contributing/)
|
||||
@@ -1,6 +0,0 @@
|
||||
# Support
|
||||
|
||||
1. Make sure you've read [understanding the basics](understand_the_basics.md) the [getting started guide](getting_started.md).
|
||||
2. Looked for an answer [the frequently asked questions](faq.md).
|
||||
3. Ask a question in [the Argo CD Slack channel ⧉](https://argoproj.github.io/community/join-slack).
|
||||
4. [Read issues, report a bug, or request a feature ⧉](https://github.com/argoproj/argo-cd/issues)
|
||||
0
docs/advanced/diffing.md
Normal file
0
docs/advanced/diffing.md
Normal file
0
docs/advanced/health.md
Normal file
0
docs/advanced/health.md
Normal file
0
docs/advanced/sync_waves.md
Normal file
0
docs/advanced/sync_waves.md
Normal file
BIN
docs/assets/argocd-per-cluster.jpg
Normal file
BIN
docs/assets/argocd-per-cluster.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
BIN
docs/assets/argocd-ui-example.png
Normal file
BIN
docs/assets/argocd-ui-example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 164 KiB |
BIN
docs/assets/azure-enterprise-claims.png
Executable file
BIN
docs/assets/azure-enterprise-claims.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 7.2 KiB |
BIN
docs/assets/azure-enterprise-saml-urls.png
Executable file
BIN
docs/assets/azure-enterprise-saml-urls.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user