Compare commits

..

131 Commits

Author SHA1 Message Date
1102
74b8bfac72 fix: Ensure RevisionMetadataPanel refreshes on revision change (#19580)
Signed-off-by: nueavv <nuguni@kakao.com>
2024-08-20 11:50:12 -04:00
Kunho Lee
7a1dfc2307 feat(hydrator): handle sourceHydrator fields from webhook (#19397)
* feat(hydrator): handle sourceHydrator fields from webhook

Signed-off-by: daengdaengLee <gunho1020@gmail.com>

* fix: use GetDrySource instead of GetHydratorDrySource

Signed-off-by: daengdaengLee <gunho1020@gmail.com>

* test: test if the hydration is properly triggered in the webhook when SourceHydrator is configured

Signed-off-by: daengdaengLee <gunho1020@gmail.com>

---------

Signed-off-by: daengdaengLee <gunho1020@gmail.com>
2024-08-19 10:13:17 -04:00
Michael Crenshaw
89c22c2f95 Merge branch 'commit-server' into hydrator
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-14 15:10:23 -04:00
Michael Crenshaw
cb2feec273 feat(hydrator): add commit-server component
Co-authored-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
Co-authored-by: Omer Azmon <omer_azmon@intuit.com>
Co-authored-by: daengdaengLee <gunho1020@gmail.com>
Co-authored-by: Juwon Hwang (Kevin) <juwon8891@gmail.com>
Co-authored-by: thisishwan2 <feel000617@gmail.com>
Co-authored-by: mirageoasis <kimhw0820@naver.com>
Co-authored-by: Robin Lieb <robin.j.lieb@gmail.com>
Co-authored-by: miiiinju1 <gms07073@ynu.ac.kr>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

go mod tidy

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

one test file for both implementations

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

simplify

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

fix test for linux

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

fix git client mock

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

fix git client mock

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

address comments

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

unit tests

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

lint

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-14 12:30:45 -04:00
Michael Crenshaw
73371f981a hacky but functional creds UI support
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-14 12:29:56 -04:00
Michael Crenshaw
79829eca98 feat(hydrator): add sourceHydrator types
Co-authored-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
Co-authored-by: Omer Azmon <omer_azmon@intuit.com>
Co-authored-by: daengdaengLee <gunho1020@gmail.com>
Co-authored-by: Juwon Hwang (Kevin) <juwon8891@gmail.com>
Co-authored-by: thisishwan2 <feel000617@gmail.com>
Co-authored-by: mirageoasis <kimhw0820@naver.com>
Co-authored-by: Robin Lieb <robin.j.lieb@gmail.com>
Co-authored-by: miiiinju1 <gms07073@ynu.ac.kr>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-12 17:08:02 -04:00
Michael Crenshaw
77029cbdc9 partial creds add UI
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-12 16:02:22 -04:00
Michael Crenshaw
b8601fe940 more logs
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-12 13:24:50 -04:00
Michael Crenshaw
dc675de3dd test error
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-12 12:44:31 -04:00
Michael Crenshaw
ac27d50d31 simplify
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-12 12:38:38 -04:00
Kevin
6045ea243a feat: Refactor writeManifests function for simplification and better error handling (#19447)
Signed-off-by: Juwon Hwang (Kevin) <juwon8891@gmail.com>
2024-08-12 12:23:51 -04:00
Michael Crenshaw
0789d44429 simplify, more tests
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-11 20:56:41 -04:00
Michael Crenshaw
e41b4851b0 lint
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-11 17:07:23 -04:00
Michael Crenshaw
01cd32916c ui improvements
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-11 16:57:54 -04:00
Michael Crenshaw
1e233c1949 fix enum, ui fixes
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-11 16:36:51 -04:00
Michael Crenshaw
4911a41e00 tidy
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-11 16:17:24 -04:00
Michael Crenshaw
f4a368cd44 ui improvements; new queue for app hydration; lots of bug fixes
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-11 16:09:04 -04:00
Michael Crenshaw
257c419550 revert unnecessary changes
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-10 20:44:51 -04:00
Michael Crenshaw
f541a8e9d5 remove previewer code; will wait for another PR
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-10 20:23:10 -04:00
Michael Crenshaw
4507984205 Merge remote-tracking branch 'origin/master' into hydrator
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-10 20:19:58 -04:00
Michael Crenshaw
95021e1984 more logging, don't always re-hydrate
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-10 19:01:43 -04:00
Michael Crenshaw
053d0f98f3 test adding a second app
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-10 17:03:53 -04:00
Michael Crenshaw
38ddab3c78 handle changes to sourceHydrator field
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-10 16:28:09 -04:00
Kim Minju
91e63d75f8 test: add Unit Tests for SecureMkdirAll Function (#19471)
* Add test for SecureMkdirAll with existing directory

- Verify that SecureMkdirAll correctly handles the case where the directory already exists.
- Ensure that the same path is returned when creating an existing directory.

Signed-off-by: miiiinju1 <gms07073@ynu.ac.kr>

* Add test for SecureMkdirAll with file path

- Ensure SecureMkdirAll returns an error when a file path is provided instead of a directory.
- Check that the error message indicates a failure to create a directory.

Signed-off-by: miiiinju1 <gms07073@ynu.ac.kr>

* Add test for SecureMkdirAll with dot-dot path

- Test SecureMkdirAll with a path containing  to ensure it doesn't traverse outside the root directory.
- Verify that the resulting path is as expected and does not use relative paths that escape the root directory.

Signed-off-by: miiiinju1 <gms07073@ynu.ac.kr>

---------

Signed-off-by: miiiinju1 <gms07073@ynu.ac.kr>
2024-08-10 13:19:32 -04:00
Michael Crenshaw
9ea972556b test failed sync with hydrateTo
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-09 15:22:52 -04:00
Michael Crenshaw
e227d71abf better filenames
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-09 14:53:10 -04:00
Michael Crenshaw
d82f140300 Merge remote-tracking branch 'origin/master' into hydrator
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-09 14:06:51 -04:00
Michael Crenshaw
b0693a642c Merge remote-tracking branch 'origin/master' into hydrator
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-09 10:18:50 -04:00
Robin Lieb
8727b17067 feat(hydrator): add metrics on getting git creds user info (#19427)
* feat(hydrator): add metrics on getting git creds user info

Signed-off-by: Robin Lieb <robin.j.lieb@gmail.com>

* feat: move credential helper to private function

---------

Signed-off-by: Robin Lieb <robin.j.lieb@gmail.com>
2024-08-08 16:13:05 -04:00
Michael Crenshaw
06d499deb0 remove unused fields
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-08 12:02:25 -04:00
Michael Crenshaw
17cd8b756e Merge remote-tracking branch 'origin/master' into hydrator
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-06 16:41:48 -04:00
Michael Crenshaw
768bc92d26 fix import
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-06 16:39:37 -04:00
Michael Crenshaw
8802205257 remove unused service
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-06 16:39:06 -04:00
Michael Crenshaw
6b8a00342d remove duplicate implementation of secure dir creation logic
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-06 14:54:28 -04:00
Michael Crenshaw
012887a246 fix test
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-06 13:24:14 -04:00
mirageoasis
d83ceab37c test: add unit test for util.go makeSecureTempDir function (#19369)
* test: added test for util.go

Signed-off-by: mirageoasis <kimhw0820@naver.com>

* refactor: fixed to require statement

Signed-off-by: mirageoasis <kimhw0820@naver.com>

* style tweaks

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

---------

Signed-off-by: mirageoasis <kimhw0820@naver.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-06 12:17:21 -04:00
Michael Crenshaw
fcd240b704 Merge remote-tracking branch 'origin/master' into hydrator
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-06 11:58:43 -04:00
Michael Crenshaw
fa2338016b fix build error
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-04 22:00:26 -04:00
Michael Crenshaw
2819ef942f fix build error
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-04 21:50:36 -04:00
Kunho Lee
9d29353961 test: add unit test for util.git.Client.CommitAndPush (#19354)
Signed-off-by: daengdaengLee <gunho1020@gmail.com>
2024-08-04 18:00:41 -04:00
thisishwan2
c7cf4bb9e2 feat: use MkdirAll by OS(#19301) (#19323)
* feat: add mkdir provider interface and Implementation for os

Signed-off-by: thisishwan2 <feel000617@gmail.com>

* fix: Using MkdirAll Functions by OS

Signed-off-by: thisishwan2 <feel000617@gmail.com>

* fix: Modifying implementations for linux and non-linux

Signed-off-by: thisishwan2 <feel000617@gmail.com>

* remove: unused interface

Signed-off-by: thisishwan2 <feel000617@gmail.com>

* fix: remove struct & use SecureMkdirAll func

Signed-off-by: thisishwan2 <feel000617@gmail.com>

* fix: use SecureMkdirAll func

Signed-off-by: thisishwan2 <feel000617@gmail.com>

* feat: add unit test SecureMkdirAll func

Signed-off-by: thisishwan2 <feel000617@gmail.com>

* tweaks

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

---------

Signed-off-by: thisishwan2 <feel000617@gmail.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-04 17:57:37 -04:00
Michael Crenshaw
14b19dae7a rename endpoints to allow more clarity when more are added
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-01 15:29:41 -04:00
Michael Crenshaw
fbcdc65308 Merge remote-tracking branch 'origin/master' into hydrator
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-01 13:25:15 -04:00
Michael Crenshaw
7cc04b830e Merge remote-tracking branch 'origin/master' into hydrator
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-31 13:53:55 -04:00
Kevin
1bb5763bd7 test: Add unit tests for WriteForPaths function (#19311)
* test: Add unit tests for WriteForPaths function

Signed-off-by: Juwon Hwang (Kevin) <juwon8891@gmail.com>

* test: refactor unit tests for WriteForPaths function

Signed-off-by: Juwon Hwang (Kevin) <juwon8891@gmail.com>

---------

Signed-off-by: Juwon Hwang (Kevin) <juwon8891@gmail.com>
2024-07-31 13:40:14 -04:00
Michael Crenshaw
2d8824482c Merge remote-tracking branch 'origin/master' into hydrator
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-30 17:10:45 -04:00
Michael Crenshaw
c49ffae139 housekeeping
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-30 14:53:12 -04:00
Kevin
2315f8ae44 test: Add unit tests for HydratorHelper methods (#19308)
* test: Add unit tests for HydratorHelper methods

Signed-off-by: Juwon Hwang (Kevin) <juwon8891@gmail.com>

* lint

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

---------

Signed-off-by: Juwon Hwang (Kevin) <juwon8891@gmail.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-30 14:15:51 -04:00
Michael Crenshaw
9221cdd780 simplify commit API, more docstrings
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-30 14:07:57 -04:00
Michael Crenshaw
652541c065 lint, refactor
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-30 12:56:28 -04:00
Michael Crenshaw
e781a08d86 graceful shutdown of commit server
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-30 11:19:36 -04:00
Michael Crenshaw
519fadc538 make directory for commit server output
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-30 10:39:24 -04:00
Michael Crenshaw
62ff4093e8 record e2e coverage
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-30 09:35:40 -04:00
Michael Crenshaw
31b46348d9 fix test
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-29 21:05:04 -04:00
Michael Crenshaw
e9385c8949 codegen
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-29 21:04:59 -04:00
Michael Crenshaw
d516fd82e1 Merge remote-tracking branch 'origin/master' into hydrator
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-29 20:46:45 -04:00
Michael Crenshaw
40b7280953 lint, fix old function calls
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-29 20:45:33 -04:00
Michael Crenshaw
3b7bbcefdf wait feature for CLI, more debug lines in commit server, e2e test works
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-29 20:42:03 -04:00
Kunho Lee
f7fece9ad2 test: add unit test for util.git.Client.RemoveContents (#19288)
Signed-off-by: daengdaengLee <gunho1020@gmail.com>
2024-07-29 10:09:05 -04:00
Michael Crenshaw
4d0969aa28 lint and codegen
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-27 18:54:15 -04:00
Michael Crenshaw
64c1d4d4af Merge remote-tracking branch 'origin/master' into hydrator
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-27 18:21:59 -04:00
Kunho Lee
9554f57690 test: add unit test for util.git.Client.CheckoutOrNew (#19274)
Signed-off-by: daengdaengLee <gunho1020@gmail.com>
2024-07-27 18:20:27 -04:00
Michael Crenshaw
73b953ddb9 add app create CLI support and stub out an e2e test
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-27 18:15:56 -04:00
Michael Crenshaw
4a39e608b1 Merge remote-tracking branch 'origin/master' into hydrator
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-26 16:51:08 -04:00
Michael Crenshaw
8e6fd8ff46 more cleanup
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-26 16:50:25 -04:00
Michael Crenshaw
51bd8b11c3 clean up commit.go
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-26 15:10:52 -04:00
Michael Crenshaw
0c8242f7e4 more metrics, plus docs
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-25 17:04:30 -04:00
Michael Crenshaw
d69a78f63f add more git metrics
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-25 17:00:00 -04:00
Michael Crenshaw
11daa4e153 Merge remote-tracking branch 'origin/master' into hydrator
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-25 16:35:42 -04:00
Michael Crenshaw
6acd67e26b add metrics server
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-24 13:14:30 -04:00
Kunho Lee
449fa4e29f test: add unit test for util.git.Client.CheckoutOrOrphan (#19191)
* test: add unit test for util.git.Client.CheckoutOrOrphan

Signed-off-by: daengdaengLee <gunho1020@gmail.com>

* test,fix: set author before do git actions

Signed-off-by: daengdaengLee <gunho1020@gmail.com>

---------

Signed-off-by: daengdaengLee <gunho1020@gmail.com>
2024-07-24 11:38:22 -04:00
Michael Crenshaw
e79aa171fa Merge remote-tracking branch 'origin/master' into hydrator
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-23 12:18:24 -04:00
Michael Crenshaw
40028b4078 fix nil reference error
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-23 11:39:40 -04:00
Michael Crenshaw
2cdb25ad4c support kustomize commands
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-23 11:22:24 -04:00
Michael Crenshaw
6f3ddda2d7 lint
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-23 10:29:01 -04:00
Michael Crenshaw
4b0eeb6a18 Merge remote-tracking branch 'origin/master' into hydrator
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-23 10:26:33 -04:00
Michael Crenshaw
cf90d8ce50 clean up generated swagger doc
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-22 16:12:06 -04:00
Michael Crenshaw
a9ba5cd3a1 better package name
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-22 16:00:16 -04:00
Michael Crenshaw
3910aa088a better package name
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-22 15:59:38 -04:00
Michael Crenshaw
f8f7baf467 fix incorrect socket path
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-22 15:58:39 -04:00
Michael Crenshaw
28bae8fe4d centralize user info code
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-22 15:57:13 -04:00
Michael Crenshaw
4eaeee320d remove accidental file
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-22 14:40:18 -04:00
Michael Crenshaw
a2501be80a Merge branch 'hydrator' of https://github.com/argoproj/argo-cd into hydrator
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-22 14:34:57 -04:00
Michael Crenshaw
1edba58774 use mockery.yaml
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-22 14:33:37 -04:00
Kunho Lee
c3efa44a58 test: add unit test for util.git.Client.SetAuthor (#19128)
Signed-off-by: daengdaengLee <gunho1020@gmail.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-22 13:59:39 -04:00
Michael Crenshaw
e8c505fe3c update mocks, basic path redaction, fix tests
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-22 12:57:34 -04:00
Michael Crenshaw
6b9c743b42 added basic dupe destination detection; still buggy
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-19 17:24:31 -04:00
Michael Crenshaw
282702e571 use different socket paths to avoid collisions during local development
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-19 17:02:08 -04:00
Michael Crenshaw
51122b913d simplify hydrate queue processor - only process for the requested source branch
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-19 16:35:22 -04:00
Michael Crenshaw
8c1983de60 fix field names, readability
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-19 16:15:59 -04:00
Michael Crenshaw
a4422a4745 Merge remote-tracking branch 'origin/master' into hydrator
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-19 13:49:10 -04:00
Michael Crenshaw
bbe8b0cc2e Merge remote-tracking branch 'origin/master' into hydrator
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-15 16:10:44 -04:00
Michael Crenshaw
34a00bdc10 don't expose unnecessary public function
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-15 15:54:58 -04:00
Michael Crenshaw
c5961c9f5c lint
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-08 15:58:22 -04:00
Michael Crenshaw
8046ec330f fix retry, use main git client
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-08 15:47:21 -04:00
Michael Crenshaw
36b74225fc Merge remote-tracking branch 'origin/master' into previewer
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-05 15:53:46 -04:00
Michael Crenshaw
1dfd0eb9ff fix UI error
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-06-14 10:10:31 -04:00
Michael Crenshaw
d27d8c0bae lint
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-06-11 22:24:45 -04:00
Michael Crenshaw
bbd83207ca lint
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-06-11 22:14:24 -04:00
Michael Crenshaw
f4a12e8cdc lint ui
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-06-11 22:07:20 -04:00
Michael Crenshaw
a677d43c1b Merge remote-tracking branch 'origin/master' into previewer
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-06-11 22:02:53 -04:00
Michael Crenshaw
02b7258c68 more docs, some abstraction of functions
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-06-11 21:29:45 -04:00
Michael Crenshaw
e0fc424011 commit service as standalone deployment
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-06-10 21:43:05 -04:00
Michael Crenshaw
26c4235361 remove temp dir
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-06-10 16:27:44 -04:00
Michael Crenshaw
e784875825 Merge remote-tracking branch 'origin/master' into previewer
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-06-06 13:18:08 -04:00
Michael Crenshaw
ffcbb8068a Merge remote-tracking branch 'origin/master' into previewer
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-06-06 10:28:18 -04:00
Michael Crenshaw
d28b53f43d fix diff order and update bot name
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-06-03 14:29:10 -04:00
Michael Crenshaw
bc11a49e25 add merge action
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-05-29 13:23:19 -04:00
Michael Crenshaw
e598333528 use shared auth, better error handling
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-05-29 13:23:02 -04:00
Michael Crenshaw
f1c58bb7db finish previewer PoC
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-05-28 16:14:19 -04:00
Michael Crenshaw
68c968047c Merge remote-tracking branch 'crenshaw-dev/manifest-hydrator' into previewer
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-05-24 16:36:42 -04:00
Omer Azmon
b277580aff add sleep to main loop 2024-05-24 10:16:37 -07:00
Omer Azmon
044375a797 add previewer POC untested 2024-05-23 19:47:49 -07:00
Alexandre Gaudreault
62d4894d51 fix: for helm namespace (#23)
* feat: manifest hydrator

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* it's monitoring both branches now

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* push works w/ my personal creds

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* write metadata, readme, and commands

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* handle missing branches, missing manifest files, and no-op changes

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* don't set release name or namespace to values from the app CR

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* more determinism

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* handle new branches

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* show hydration progress

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* use workqueue

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* use securejoin, use log contexts, clean up temp dirs

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* use app auth for github only

Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>

* it works

Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>

* retry failed operations

Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>

* fix

Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>

* codegen

Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>

* it just works

Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>

---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-05-23 15:42:20 -04:00
Michael Crenshaw
74d4e980f9 use securejoin, use log contexts, clean up temp dirs
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-05-21 20:08:31 -04:00
Michael Crenshaw
9dc94b08a6 use workqueue
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-05-21 20:08:31 -04:00
Michael Crenshaw
29d8937de6 show hydration progress
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-05-21 20:08:28 -04:00
Michael Crenshaw
0caa4d3d33 handle new branches
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-05-21 20:05:24 -04:00
Michael Crenshaw
207f0aba5d more determinism
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-05-21 20:05:21 -04:00
Michael Crenshaw
8257311db2 don't set release name or namespace to values from the app CR
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-05-21 20:02:33 -04:00
Michael Crenshaw
9944e2a8d1 handle missing branches, missing manifest files, and no-op changes
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-05-21 20:02:33 -04:00
Michael Crenshaw
87d2f3f263 write metadata, readme, and commands
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-05-21 20:02:28 -04:00
Michael Crenshaw
55f3fa8b53 push works w/ my personal creds
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-05-21 19:59:30 -04:00
Michael Crenshaw
ccf18147b2 it's monitoring both branches now
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-05-21 19:59:27 -04:00
Michael Crenshaw
a1e8e1f17d feat: manifest hydrator
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-05-21 19:55:05 -04:00
Argo CD Hydrator
f816ada864 hydrate 6241416c8ef1a94d3c80f38fa2f1f865608886f7
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-04-24 11:09:24 -04:00
Argo CD Hydrator
ebb71a0018 hydrate f0cc9868393be8abf156adb963f66b9ec8417f37
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-04-24 10:56:51 -04:00
Argo CD Hydrator
8bd52a7b74 hydrate 665d6fd139b3bcf2df0ff4b464f2fe42597f2455
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-04-24 10:54:59 -04:00
Argo CD Hydrator
2d43a8331f hydrate
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-04-24 10:54:56 -04:00
Michael Crenshaw
dd7952e389 it's monitoring both branches now
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-04-23 17:02:39 -04:00
Michael Crenshaw
037d098d7c feat: manifest hydrator
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-04-22 10:47:52 -04:00
363 changed files with 15203 additions and 3504 deletions

View File

@@ -323,8 +323,6 @@ jobs:
NODE_ENV: production
NODE_ONLINE_ENV: online
HOST_ARCH: amd64
# If we're on the master branch, set the codecov token so that we upload bundle analysis
CODECOV_TOKEN: ${{ github.ref == 'refs/heads/master' && secrets.CODECOV_TOKEN || '' }}
working-directory: ui/
- name: Run ESLint
run: yarn lint
@@ -367,11 +365,11 @@ jobs:
path: test-results
- name: combine-go-coverage
# We generate coverage reports for all Argo CD components, but only the applicationset-controller,
# app-controller, and repo-server report contain coverage data. The other components currently don't shut down
# gracefully, so no coverage data is produced. Once those components are fixed, we can add references to their
# coverage output directories.
# app-controller, repo-server, and commit-server report contain coverage data. The other components currently
# don't shut down gracefully, so no coverage data is produced. Once those components are fixed, we can add
# references to their coverage output directories.
run: |
go tool covdata percent -i=test-results,e2e-code-coverage/applicationset-controller,e2e-code-coverage/repo-server,e2e-code-coverage/app-controller -o test-results/full-coverage.out
go tool covdata percent -i=test-results,e2e-code-coverage/applicationset-controller,e2e-code-coverage/repo-server,e2e-code-coverage/app-controller,e2e-code-coverage/commit-server -o test-results/full-coverage.out
- name: Upload code coverage information to codecov.io
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
with:

View File

@@ -143,7 +143,7 @@ jobs:
- name: Build and push container image
id: image
uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 #v6.7.0
uses: docker/build-push-action@16ebe778df0e7752d2cfcbd924afdbbd89c1a755 #v6.6.1
with:
context: .
platforms: ${{ inputs.platforms }}

View File

@@ -4,10 +4,6 @@ issues:
- SA5011
max-issues-per-linter: 0
max-same-issues: 0
exclude-rules:
- path: '(.+)_test\.go'
linters:
- unparam
linters:
enable:
- errcheck
@@ -21,7 +17,6 @@ linters:
- misspell
- staticcheck
- testifylint
- unparam
- unused
- whitespace
linters-settings:

View File

@@ -26,6 +26,13 @@ packages:
github.com/argoproj/argo-cd/v2/applicationset/utils:
interfaces:
Renderer:
github.com/argoproj/argo-cd/v2/commitserver/commit:
interfaces:
RepoClientFactory:
github.com/argoproj/argo-cd/v2/commitserver/apiclient:
interfaces:
CommitServiceClient:
Clientset:
github.com/argoproj/argo-cd/v2/controller/cache:
interfaces:
LiveStateCache:

View File

@@ -472,6 +472,7 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
mkdir -p /tmp/coverage/repo-server
mkdir -p /tmp/coverage/applicationset-controller
mkdir -p /tmp/coverage/notification
mkdir -p /tmp/coverage/commit-server
# 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 \

View File

@@ -1,9 +1,11 @@
controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/app-controller} HOSTNAME=testappcontroller-1 FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''} --server-side-diff-enabled=${ARGOCD_APPLICATION_CONTROLLER_SERVER_SIDE_DIFF:-'false'}"
controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/app-controller} HOSTNAME=testappcontroller-1 FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --commit-server localhost:${ARGOCD_E2E_COMMITSERVER_PORT:-8086} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''} --server-side-diff-enabled=${ARGOCD_APPLICATION_CONTROLLER_SERVER_SIDE_DIFF:-'false'}"
api-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/api-server} FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-server $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''}"
dex: sh -c "ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/v2/cmd gendexcfg -o `pwd`/dist/dex.yaml && (test -f dist/dex.yaml || { echo 'Failed to generate dex configuration'; exit 1; }) && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:$(grep "image: ghcr.io/dexidp/dex" manifests/base/dex/argocd-dex-server-deployment.yaml | cut -d':' -f3) dex serve /dex.yaml"
redis: bash -c "if [ \"$ARGOCD_REDIS_LOCAL\" = 'true' ]; then redis-server --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; else docker run --rm --name argocd-redis -i -p ${ARGOCD_E2E_REDIS_PORT:-6379}:${ARGOCD_E2E_REDIS_PORT:-6379} docker.io/library/redis:$(grep "image: redis" manifests/base/redis/argocd-redis-deployment.yaml | cut -d':' -f3) --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; fi"
repo-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/repo-server} FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-repo-server ARGOCD_GPG_ENABLED=${ARGOCD_GPG_ENABLED:-false} $COMMAND --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --otlp-address=${ARGOCD_OTLP_ADDRESS}"
commit-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/commit-server} FORCE_LOG_COLORS=1 ARGOCD_BINARY_NAME=argocd-commit-server $COMMAND --loglevel debug --port ${ARGOCD_E2E_COMMITSERVER_PORT:-8086}"
cmp-server: [ "$ARGOCD_E2E_TEST" = 'true' ] && exit 0 || [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_BINARY_NAME=argocd-cmp-server ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} $COMMAND --config-dir-path ./test/cmp --loglevel debug --otlp-address=${ARGOCD_OTLP_ADDRESS}"
commit-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/commit-server} FORCE_LOG_COLORS=1 ARGOCD_BINARY_NAME=argocd-commit-server $COMMAND --loglevel debug --port ${ARGOCD_E2E_COMMITSERVER_PORT:-8086}"
ui: sh -c 'cd ui && ${ARGOCD_E2E_YARN_CMD:-yarn} start'
git-server: test/fixture/testrepos/start-git.sh
helm-registry: test/fixture/testrepos/start-helm-registry.sh

View File

@@ -242,8 +242,20 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
if r.EnableProgressiveSyncs {
// trigger appropriate application syncs if RollingSync strategy is enabled
if progressiveSyncsRollingSyncStrategyEnabled(&applicationSetInfo) {
validApps = r.syncValidApplications(logCtx, &applicationSetInfo, appSyncMap, appMap, validApps)
if progressiveSyncsStrategyEnabled(&applicationSetInfo, "RollingSync") {
validApps, err = r.syncValidApplications(logCtx, &applicationSetInfo, appSyncMap, appMap, validApps)
if err != nil {
_ = r.setApplicationSetStatusCondition(ctx,
&applicationSetInfo,
argov1alpha1.ApplicationSetCondition{
Type: argov1alpha1.ApplicationSetConditionErrorOccurred,
Message: err.Error(),
Reason: argov1alpha1.ApplicationSetReasonSyncApplicationError,
Status: argov1alpha1.ApplicationSetConditionStatusTrue,
}, parametersGenerated,
)
return ctrl.Result{}, err
}
}
}
@@ -491,7 +503,7 @@ func (r *ApplicationSetReconciler) getMinRequeueAfter(applicationSetInfo *argov1
func ignoreNotAllowedNamespaces(namespaces []string) predicate.Predicate {
return predicate.Funcs{
CreateFunc: func(e event.CreateEvent) bool {
return glob.MatchStringInList(namespaces, e.Object.GetNamespace(), glob.REGEXP)
return glob.MatchStringInList(namespaces, e.Object.GetNamespace(), false)
},
}
}
@@ -847,9 +859,12 @@ func (r *ApplicationSetReconciler) removeOwnerReferencesOnDeleteAppSet(ctx conte
}
func (r *ApplicationSetReconciler) performProgressiveSyncs(ctx context.Context, logCtx *log.Entry, appset argov1alpha1.ApplicationSet, applications []argov1alpha1.Application, desiredApplications []argov1alpha1.Application, appMap map[string]argov1alpha1.Application) (map[string]bool, error) {
appDependencyList, appStepMap := r.buildAppDependencyList(logCtx, appset, desiredApplications)
appDependencyList, appStepMap, err := r.buildAppDependencyList(logCtx, appset, desiredApplications)
if err != nil {
return nil, fmt.Errorf("failed to build app dependency list: %w", err)
}
_, err := r.updateApplicationSetApplicationStatus(ctx, logCtx, &appset, applications, appStepMap)
_, err = r.updateApplicationSetApplicationStatus(ctx, logCtx, &appset, applications, appStepMap)
if err != nil {
return nil, fmt.Errorf("failed to update applicationset app status: %w", err)
}
@@ -859,27 +874,34 @@ func (r *ApplicationSetReconciler) performProgressiveSyncs(ctx context.Context,
logCtx.Infof("step %v: %+v", i+1, step)
}
appSyncMap := r.buildAppSyncMap(appset, appDependencyList, appMap)
appSyncMap, err := r.buildAppSyncMap(ctx, appset, appDependencyList, appMap)
if err != nil {
return nil, fmt.Errorf("failed to build app sync map: %w", err)
}
logCtx.Infof("Application allowed to sync before maxUpdate?: %+v", appSyncMap)
_, err = r.updateApplicationSetApplicationStatusProgress(ctx, logCtx, &appset, appSyncMap, appStepMap)
_, err = r.updateApplicationSetApplicationStatusProgress(ctx, logCtx, &appset, appSyncMap, appStepMap, appMap)
if err != nil {
return nil, fmt.Errorf("failed to update applicationset application status progress: %w", err)
}
_ = r.updateApplicationSetApplicationStatusConditions(ctx, &appset)
_, err = r.updateApplicationSetApplicationStatusConditions(ctx, &appset)
if err != nil {
return nil, fmt.Errorf("failed to update applicationset application status conditions: %w", err)
}
return appSyncMap, nil
}
// this list tracks which Applications belong to each RollingUpdate step
func (r *ApplicationSetReconciler) buildAppDependencyList(logCtx *log.Entry, applicationSet argov1alpha1.ApplicationSet, applications []argov1alpha1.Application) ([][]string, map[string]int) {
func (r *ApplicationSetReconciler) buildAppDependencyList(logCtx *log.Entry, applicationSet argov1alpha1.ApplicationSet, applications []argov1alpha1.Application) ([][]string, map[string]int, error) {
if applicationSet.Spec.Strategy == nil || applicationSet.Spec.Strategy.Type == "" || applicationSet.Spec.Strategy.Type == "AllAtOnce" {
return [][]string{}, map[string]int{}
return [][]string{}, map[string]int{}, nil
}
steps := []argov1alpha1.ApplicationSetRolloutStep{}
if progressiveSyncsRollingSyncStrategyEnabled(&applicationSet) {
if progressiveSyncsStrategyEnabled(&applicationSet, "RollingSync") {
steps = applicationSet.Spec.Strategy.RollingSync.Steps
}
@@ -920,7 +942,7 @@ func (r *ApplicationSetReconciler) buildAppDependencyList(logCtx *log.Entry, app
}
}
return appDependencyList, appStepMap
return appDependencyList, appStepMap, nil
}
func labelMatchedExpression(logCtx *log.Entry, val string, matchExpression argov1alpha1.ApplicationMatchExpression) bool {
@@ -944,7 +966,7 @@ func labelMatchedExpression(logCtx *log.Entry, val string, matchExpression argov
}
// this map is used to determine which stage of Applications are ready to be updated in the reconciler loop
func (r *ApplicationSetReconciler) buildAppSyncMap(applicationSet argov1alpha1.ApplicationSet, appDependencyList [][]string, appMap map[string]argov1alpha1.Application) map[string]bool {
func (r *ApplicationSetReconciler) buildAppSyncMap(ctx context.Context, applicationSet argov1alpha1.ApplicationSet, appDependencyList [][]string, appMap map[string]argov1alpha1.Application) (map[string]bool, error) {
appSyncMap := map[string]bool{}
syncEnabled := true
@@ -981,11 +1003,11 @@ func (r *ApplicationSetReconciler) buildAppSyncMap(applicationSet argov1alpha1.A
}
}
return appSyncMap
return appSyncMap, nil
}
func appSyncEnabledForNextStep(appset *argov1alpha1.ApplicationSet, app argov1alpha1.Application, appStatus argov1alpha1.ApplicationSetApplicationStatus) bool {
if progressiveSyncsRollingSyncStrategyEnabled(appset) {
if progressiveSyncsStrategyEnabled(appset, "RollingSync") {
// we still need to complete the current step if the Application is not yet Healthy or there are still pending Application changes
return isApplicationHealthy(app) && appStatus.Status == "Healthy"
}
@@ -993,8 +1015,16 @@ func appSyncEnabledForNextStep(appset *argov1alpha1.ApplicationSet, app argov1al
return true
}
func progressiveSyncsRollingSyncStrategyEnabled(appset *argov1alpha1.ApplicationSet) bool {
return appset.Spec.Strategy != nil && appset.Spec.Strategy.RollingSync != nil && appset.Spec.Strategy.Type == "RollingSync"
func progressiveSyncsStrategyEnabled(appset *argov1alpha1.ApplicationSet, strategyType string) bool {
if appset.Spec.Strategy == nil || appset.Spec.Strategy.Type != strategyType {
return false
}
if strategyType == "RollingSync" && appset.Spec.Strategy.RollingSync == nil {
return false
}
return true
}
func isApplicationHealthy(app argov1alpha1.Application) bool {
@@ -1052,7 +1082,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
}
appOutdated := false
if progressiveSyncsRollingSyncStrategyEnabled(applicationSet) {
if progressiveSyncsStrategyEnabled(applicationSet, "RollingSync") {
appOutdated = syncStatusString == "OutOfSync"
}
@@ -1118,7 +1148,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
}
// check Applications that are in Waiting status and promote them to Pending if needed
func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress(ctx context.Context, logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appStepMap map[string]int) ([]argov1alpha1.ApplicationSetApplicationStatus, error) {
func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress(ctx context.Context, logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appStepMap map[string]int, appMap map[string]argov1alpha1.Application) ([]argov1alpha1.ApplicationSetApplicationStatus, error) {
now := metav1.Now()
appStatuses := make([]argov1alpha1.ApplicationSetApplicationStatus, 0, len(applicationSet.Status.ApplicationStatus))
@@ -1129,7 +1159,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
totalCountMap := []int{}
length := 0
if progressiveSyncsRollingSyncStrategyEnabled(applicationSet) {
if progressiveSyncsStrategyEnabled(applicationSet, "RollingSync") {
length = len(applicationSet.Spec.Strategy.RollingSync.Steps)
}
for s := 0; s < length; s++ {
@@ -1141,7 +1171,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
for _, appStatus := range applicationSet.Status.ApplicationStatus {
totalCountMap[appStepMap[appStatus.Application]] += 1
if progressiveSyncsRollingSyncStrategyEnabled(applicationSet) {
if progressiveSyncsStrategyEnabled(applicationSet, "RollingSync") {
if appStatus.Status == "Pending" || appStatus.Status == "Progressing" {
updateCountMap[appStepMap[appStatus.Application]] += 1
}
@@ -1151,7 +1181,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
for _, appStatus := range applicationSet.Status.ApplicationStatus {
maxUpdateAllowed := true
maxUpdate := &intstr.IntOrString{}
if progressiveSyncsRollingSyncStrategyEnabled(applicationSet) {
if progressiveSyncsStrategyEnabled(applicationSet, "RollingSync") {
maxUpdate = applicationSet.Spec.Strategy.RollingSync.Steps[appStepMap[appStatus.Application]].MaxUpdate
}
@@ -1195,7 +1225,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
return appStatuses, nil
}
func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusConditions(ctx context.Context, applicationSet *argov1alpha1.ApplicationSet) []argov1alpha1.ApplicationSetCondition {
func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusConditions(ctx context.Context, applicationSet *argov1alpha1.ApplicationSet) ([]argov1alpha1.ApplicationSetCondition, error) {
appSetProgressing := false
for _, appStatus := range applicationSet.Status.ApplicationStatus {
if appStatus.Status != "Healthy" {
@@ -1234,7 +1264,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusConditio
)
}
return applicationSet.Status.Conditions
return applicationSet.Status.Conditions, nil
}
func findApplicationStatusIndex(appStatuses []argov1alpha1.ApplicationSetApplicationStatus, application string) int {
@@ -1344,7 +1374,7 @@ func (r *ApplicationSetReconciler) setAppSetApplicationStatus(ctx context.Contex
return nil
}
func (r *ApplicationSetReconciler) syncValidApplications(logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appMap map[string]argov1alpha1.Application, validApps []argov1alpha1.Application) []argov1alpha1.Application {
func (r *ApplicationSetReconciler) syncValidApplications(logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appMap map[string]argov1alpha1.Application, validApps []argov1alpha1.Application) ([]argov1alpha1.Application, error) {
rolloutApps := []argov1alpha1.Application{}
for i := range validApps {
pruneEnabled := false
@@ -1365,15 +1395,15 @@ func (r *ApplicationSetReconciler) syncValidApplications(logCtx *log.Entry, appl
// check appSyncMap to determine which Applications are ready to be updated and which should be skipped
if appSyncMap[validApps[i].Name] && appMap[validApps[i].Name].Status.Sync.Status == "OutOfSync" && appSetStatusPending {
logCtx.Infof("triggering sync for application: %v, prune enabled: %v", validApps[i].Name, pruneEnabled)
validApps[i] = syncApplication(validApps[i], pruneEnabled)
validApps[i], _ = syncApplication(validApps[i], pruneEnabled)
}
rolloutApps = append(rolloutApps, validApps[i])
}
return rolloutApps
return rolloutApps, nil
}
// used by the RollingSync Progressive Sync strategy to trigger a sync of a particular Application resource
func syncApplication(application argov1alpha1.Application, prune bool) argov1alpha1.Application {
func syncApplication(application argov1alpha1.Application, prune bool) (argov1alpha1.Application, error) {
operation := argov1alpha1.Operation{
InitiatedBy: argov1alpha1.OperationInitiator{
Username: "applicationset-controller",
@@ -1399,7 +1429,7 @@ func syncApplication(application argov1alpha1.Application, prune bool) argov1alp
}
application.Operation = &operation
return application
return application, nil
}
func getOwnsHandlerPredicates(enableProgressiveSyncs bool) predicate.Funcs {

View File

@@ -3666,7 +3666,8 @@ func TestBuildAppDependencyList(t *testing.T) {
KubeClientset: kubeclientset,
}
appDependencyList, appStepMap := r.buildAppDependencyList(log.NewEntry(log.StandardLogger()), cc.appSet, cc.apps)
appDependencyList, appStepMap, err := r.buildAppDependencyList(log.NewEntry(log.StandardLogger()), cc.appSet, cc.apps)
require.NoError(t, err, "expected no errors, but errors occurred")
assert.Equal(t, cc.expectedList, appDependencyList, "expected appDependencyList did not match actual")
assert.Equal(t, cc.expectedStepMap, appStepMap, "expected appStepMap did not match actual")
})
@@ -4256,7 +4257,8 @@ func TestBuildAppSyncMap(t *testing.T) {
KubeClientset: kubeclientset,
}
appSyncMap := r.buildAppSyncMap(cc.appSet, cc.appDependencyList, cc.appMap)
appSyncMap, err := r.buildAppSyncMap(context.TODO(), cc.appSet, cc.appDependencyList, cc.appMap)
require.NoError(t, err, "expected no errors, but errors occurred")
assert.Equal(t, cc.expectedMap, appSyncMap, "expected appSyncMap did not match actual")
})
}
@@ -5798,7 +5800,7 @@ func TestUpdateApplicationSetApplicationStatusProgress(t *testing.T) {
KubeClientset: kubeclientset,
}
appStatuses, err := r.updateApplicationSetApplicationStatusProgress(context.TODO(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.appSyncMap, cc.appStepMap)
appStatuses, err := r.updateApplicationSetApplicationStatusProgress(context.TODO(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.appSyncMap, cc.appStepMap, cc.appMap)
// opt out of testing the LastTransitionTime is accurate
for i := range appStatuses {

View File

@@ -60,7 +60,7 @@ func TestRequeueAfter(t *testing.T) {
terminalGenerators := map[string]generators.Generator{
"List": generators.NewListGenerator(),
"Clusters": generators.NewClusterGenerator(k8sClient, ctx, appClientset, "argocd"),
"Git": generators.NewGitGenerator(mockServer, "namespace"),
"Git": generators.NewGitGenerator(mockServer),
"SCMProvider": generators.NewSCMProviderGenerator(fake.NewClientBuilder().WithObjects(&corev1.Secret{}).Build(), scmConfig),
"ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, fakeDynClient, appClientset, "argocd"),
"PullRequest": generators.NewPullRequestGenerator(k8sClient, scmConfig),

View File

@@ -346,7 +346,7 @@ func getMockClusterGenerator() Generator {
func getMockGitGenerator() Generator {
argoCDServiceMock := mocks.Repos{}
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return([]string{"app1", "app2", "app_3", "p1/app4"}, nil)
gitGenerator := NewGitGenerator(&argoCDServiceMock, "namespace")
gitGenerator := NewGitGenerator(&argoCDServiceMock)
return gitGenerator
}

View File

@@ -24,16 +24,13 @@ import (
var _ Generator = (*GitGenerator)(nil)
type GitGenerator struct {
repos services.Repos
namespace string
repos services.Repos
}
func NewGitGenerator(repos services.Repos, namespace string) Generator {
func NewGitGenerator(repos services.Repos) Generator {
g := &GitGenerator{
repos: repos,
namespace: namespace,
repos: repos,
}
return g
}
@@ -62,25 +59,21 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
noRevisionCache := appSet.RefreshRequired()
verifyCommit := false
// When the project field is templated, the contents of the git repo are required to run the git generator and get the templated value,
// but git generator cannot be called without verifying the commit signature.
// In this case, we skip the signature verification.
if !strings.Contains(appSet.Spec.Template.Spec.Project, "{{") {
project := appSet.Spec.Template.Spec.Project
appProject := &argoprojiov1alpha1.AppProject{}
namespace := g.namespace
if namespace == "" {
namespace = appSet.Namespace
}
if err := client.Get(context.TODO(), types.NamespacedName{Name: project, Namespace: namespace}, appProject); err != nil {
return nil, fmt.Errorf("error getting project %s: %w", project, err)
}
// we need to verify the signature on the Git revision if GPG is enabled
verifyCommit = appProject.Spec.SignatureKeys != nil && len(appProject.Spec.SignatureKeys) > 0 && gpg.IsGPGEnabled()
var project string
if strings.Contains(appSet.Spec.Template.Spec.Project, "{{") {
project = appSetGenerator.Git.Template.Spec.Project
} else {
project = appSet.Spec.Template.Spec.Project
}
appProject := &argoprojiov1alpha1.AppProject{}
if err := client.Get(context.TODO(), types.NamespacedName{Name: appSet.Spec.Template.Spec.Project, Namespace: appSet.Namespace}, appProject); err != nil {
return nil, fmt.Errorf("error getting project %s: %w", project, err)
}
// we need to verify the signature on the Git revision if GPG is enabled
verifyCommit := appProject.Spec.SignatureKeys != nil && len(appProject.Spec.SignatureKeys) > 0 && gpg.IsGPGEnabled()
var err error
var res []map[string]interface{}
if len(appSetGenerator.Git.Directories) != 0 {

View File

@@ -323,7 +323,7 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
gitGenerator := NewGitGenerator(&argoCDServiceMock, "")
gitGenerator := NewGitGenerator(&argoCDServiceMock)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -624,7 +624,7 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
gitGenerator := NewGitGenerator(&argoCDServiceMock, "")
gitGenerator := NewGitGenerator(&argoCDServiceMock)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -989,7 +989,7 @@ cluster:
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
gitGenerator := NewGitGenerator(&argoCDServiceMock, "")
gitGenerator := NewGitGenerator(&argoCDServiceMock)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -1345,7 +1345,7 @@ cluster:
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
gitGenerator := NewGitGenerator(&argoCDServiceMock, "")
gitGenerator := NewGitGenerator(&argoCDServiceMock)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -1383,114 +1383,3 @@ cluster:
})
}
}
func TestGitGenerator_GenerateParams(t *testing.T) {
cases := []struct {
name string
directories []argoprojiov1alpha1.GitDirectoryGeneratorItem
pathParamPrefix string
repoApps []string
repoPathsError error
repoFileContents map[string][]byte
values map[string]string
expected []map[string]interface{}
expectedError error
appset argoprojiov1alpha1.ApplicationSet
callGetDirectories bool
}{
{
name: "Signature Verification - ignores templated project field",
repoApps: []string{
"app1",
},
repoPathsError: nil,
appset: argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
Namespace: "namespace",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
Git: &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL",
Revision: "Revision",
Directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
PathParamPrefix: "",
Values: map[string]string{
"foo": "bar",
},
},
}},
Template: argoprojiov1alpha1.ApplicationSetTemplate{
Spec: argoprojiov1alpha1.ApplicationSpec{
Project: "{{.project}}",
},
},
},
},
callGetDirectories: true,
expected: []map[string]interface{}{{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "path[0]": "app1", "values.foo": "bar"}},
expectedError: nil,
},
{
name: "Signature Verification - Checks for non-templated project field",
repoApps: []string{
"app1",
},
repoPathsError: nil,
appset: argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
Namespace: "namespace",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
Git: &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL",
Revision: "Revision",
Directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
PathParamPrefix: "",
Values: map[string]string{
"foo": "bar",
},
},
}},
Template: argoprojiov1alpha1.ApplicationSetTemplate{
Spec: argoprojiov1alpha1.ApplicationSpec{
Project: "project",
},
},
},
},
callGetDirectories: false,
expected: []map[string]interface{}{{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "path[0]": "app1", "values.foo": "bar"}},
expectedError: fmt.Errorf("error getting project project: appprojects.argoproj.io \"project\" not found"),
},
}
for _, testCase := range cases {
argoCDServiceMock := mocks.Repos{}
if testCase.callGetDirectories {
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCase.repoApps, testCase.repoPathsError)
}
gitGenerator := NewGitGenerator(&argoCDServiceMock, "namespace")
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
appProject := argoprojiov1alpha1.AppProject{}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
got, err := gitGenerator.GenerateParams(&testCase.appset.Spec.Generators[0], &testCase.appset, client)
if testCase.expectedError != nil {
require.EqualError(t, err, testCase.expectedError.Error())
} else {
require.NoError(t, err)
assert.Equal(t, testCase.expected, got)
}
argoCDServiceMock.AssertExpectations(t)
}
}

View File

@@ -1089,7 +1089,7 @@ func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
repoServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(map[string][]byte{
"some/path.json": []byte("test: content"),
}, nil)
gitGenerator := NewGitGenerator(repoServiceMock, "")
gitGenerator := NewGitGenerator(repoServiceMock)
matrixGenerator := NewMatrixGenerator(map[string]Generator{
"List": listGeneratorMock,

View File

@@ -14,7 +14,7 @@ func GetGenerators(ctx context.Context, c client.Client, k8sClient kubernetes.In
terminalGenerators := map[string]Generator{
"List": NewListGenerator(),
"Clusters": NewClusterGenerator(c, ctx, k8sClient, namespace),
"Git": NewGitGenerator(argoCDService, namespace),
"Git": NewGitGenerator(argoCDService),
"SCMProvider": NewSCMProviderGenerator(c, scmConfig),
"ClusterDecisionResource": NewDuckTypeGenerator(ctx, dynamicClient, k8sClient, namespace),
"PullRequest": NewPullRequestGenerator(c, scmConfig),

165
assets/swagger.json generated
View File

@@ -3290,6 +3290,12 @@
"description": "App project for query.",
"name": "appProject",
"in": "query"
},
{
"type": "string",
"description": "Type determines what kind of credential we're interacting with. It can be \"read\", \"write\", or \"both\". Default is\n\"read\".",
"name": "type",
"in": "query"
}
],
"responses": {
@@ -3334,6 +3340,12 @@
"description": "Whether to operate on credential set instead of repository.",
"name": "credsOnly",
"in": "query"
},
{
"type": "boolean",
"description": "Write determines whether the credential will be stored as a read credential or a write credential.",
"name": "write",
"in": "query"
}
],
"responses": {
@@ -3374,6 +3386,12 @@
"schema": {
"$ref": "#/definitions/v1alpha1Repository"
}
},
{
"type": "boolean",
"description": "Write determines whether the credential to be updated is a read credential or a write credential.",
"name": "write",
"in": "query"
}
],
"responses": {
@@ -3418,6 +3436,12 @@
"description": "App project for query.",
"name": "appProject",
"in": "query"
},
{
"type": "string",
"description": "Type determines what kind of credential we're interacting with. It can be \"read\", \"write\", or \"both\". Default is\n\"read\".",
"name": "type",
"in": "query"
}
],
"responses": {
@@ -3460,6 +3484,12 @@
"description": "App project for query.",
"name": "appProject",
"in": "query"
},
{
"type": "string",
"description": "Type determines what kind of credential we're interacting with. It can be \"read\", \"write\", or \"both\". Default is\n\"read\".",
"name": "type",
"in": "query"
}
],
"responses": {
@@ -3550,6 +3580,12 @@
"description": "App project for query.",
"name": "appProject",
"in": "query"
},
{
"type": "string",
"description": "Type determines what kind of credential we're interacting with. It can be \"read\", \"write\", or \"both\". Default is\n\"read\".",
"name": "type",
"in": "query"
}
],
"responses": {
@@ -3593,6 +3629,12 @@
"description": "App project for query.",
"name": "appProject",
"in": "query"
},
{
"type": "string",
"description": "Type determines what kind of credential we're interacting with. It can be \"read\", \"write\", or \"both\". Default is\n\"read\".",
"name": "type",
"in": "query"
}
],
"responses": {
@@ -6747,6 +6789,9 @@
"source": {
"$ref": "#/definitions/v1alpha1ApplicationSource"
},
"sourceHydrator": {
"$ref": "#/definitions/v1alpha1SourceHydrator"
},
"sources": {
"type": "array",
"title": "Sources is a reference to the location of the application's manifests or chart",
@@ -6804,6 +6849,9 @@
"$ref": "#/definitions/v1alpha1ResourceStatus"
}
},
"sourceHydrator": {
"$ref": "#/definitions/v1alpha1SourceHydratorStatus"
},
"sourceType": {
"type": "string",
"title": "SourceType specifies the type of this application"
@@ -7214,6 +7262,24 @@
}
}
},
"v1alpha1DrySource": {
"description": "DrySource specifies a location for dry \"don't repeat yourself\" manifest source information.",
"type": "object",
"properties": {
"path": {
"type": "string",
"title": "Path is a directory path within the Git repository where the manifests are located"
},
"repoURL": {
"type": "string",
"title": "RepoURL is the URL to the git repository that contains the application manifests"
},
"targetRevision": {
"type": "string",
"title": "TargetRevision defines the revision of the source to hydrate"
}
}
},
"v1alpha1DuckTypeGenerator": {
"description": "DuckType defines a generator to match against clusters registered with ArgoCD.",
"type": "object",
@@ -7465,6 +7531,47 @@
}
}
},
"v1alpha1HydrateOperation": {
"type": "object",
"title": "HydrateOperation contains information about the most recent hydrate operation",
"properties": {
"drySHA": {
"type": "string",
"title": "DrySHA holds the resolved revision (sha) of the dry source as of the most recent reconciliation"
},
"finishedAt": {
"$ref": "#/definitions/v1Time"
},
"hydratedSHA": {
"type": "string",
"title": "HydratedSHA holds the resolved revision (sha) of the hydrated source as of the most recent reconciliation"
},
"message": {
"type": "string",
"title": "Message contains a message describing the current status of the hydrate operation"
},
"phase": {
"type": "string",
"title": "Phase indicates the status of the hydrate operation"
},
"sourceHydrator": {
"$ref": "#/definitions/v1alpha1SourceHydrator"
},
"startedAt": {
"$ref": "#/definitions/v1Time"
}
}
},
"v1alpha1HydrateTo": {
"description": "HydrateTo specifies a location to which hydrated manifests should be pushed as a \"staging area\" before being moved to\nthe SyncSource. The RepoURL and Path are assumed based on the associated SyncSource config in the SourceHydrator.",
"type": "object",
"properties": {
"targetBranch": {
"type": "string",
"title": "TargetBranch is the branch to which hydrated manifests should be committed"
}
}
},
"v1alpha1Info": {
"type": "object",
"properties": {
@@ -9101,6 +9208,50 @@
}
}
},
"v1alpha1SourceHydrator": {
"description": "SourceHydrator specifies a dry \"don't repeat yourself\" source for manifests, a sync source from which to sync\nhydrated manifests, and an optional hydrateTo location to act as a \"staging\" aread for hydrated manifests.",
"type": "object",
"properties": {
"drySource": {
"$ref": "#/definitions/v1alpha1DrySource"
},
"hydrateTo": {
"$ref": "#/definitions/v1alpha1HydrateTo"
},
"syncSource": {
"$ref": "#/definitions/v1alpha1SyncSource"
}
}
},
"v1alpha1SourceHydratorStatus": {
"type": "object",
"title": "SourceHydratorStatus contains information about the current state of source hydration",
"properties": {
"currentOperation": {
"$ref": "#/definitions/v1alpha1HydrateOperation"
},
"lastSuccessfulOperation": {
"$ref": "#/definitions/v1alpha1SuccessfulHydrateOperation"
}
}
},
"v1alpha1SuccessfulHydrateOperation": {
"type": "object",
"title": "SuccessfulHydrateOperation contains information about the most recent successful hydrate operation",
"properties": {
"drySHA": {
"type": "string",
"title": "DrySHA holds the resolved revision (sha) of the dry source as of the most recent reconciliation"
},
"hydratedSHA": {
"type": "string",
"title": "HydratedSHA holds the resolved revision (sha) of the hydrated source as of the most recent reconciliation"
},
"sourceHydrator": {
"$ref": "#/definitions/v1alpha1SourceHydrator"
}
}
},
"v1alpha1SyncOperation": {
"description": "SyncOperation contains details about a sync operation.",
"type": "object",
@@ -9255,6 +9406,20 @@
}
}
},
"v1alpha1SyncSource": {
"description": "SyncSource specifies a location from which hydrated manifests may be synced. RepoURL is assumed based on the\nassociated DrySource config in the SourceHydrator.",
"type": "object",
"properties": {
"path": {
"description": "Path is a directory path within the git repository where hydrated manifests should be committed to and synced\nfrom. If hydrateTo is set, this is just the path from which hydrated manifests will be synced.",
"type": "string"
},
"targetBranch": {
"type": "string",
"title": "TargetBranch is the branch to which hydrated manifests should be committed"
}
}
},
"v1alpha1SyncStatus": {
"type": "object",
"title": "SyncStatus contains information about the currently observed live and desired states of an application",

View File

@@ -17,6 +17,7 @@ import (
"k8s.io/client-go/tools/clientcmd"
cmdutil "github.com/argoproj/argo-cd/v2/cmd/util"
commitclient "github.com/argoproj/argo-cd/v2/commitserver/apiclient"
"github.com/argoproj/argo-cd/v2/common"
"github.com/argoproj/argo-cd/v2/controller"
"github.com/argoproj/argo-cd/v2/controller/sharding"
@@ -55,6 +56,7 @@ func NewCommand() *cobra.Command {
repoErrorGracePeriod int64
repoServerAddress string
repoServerTimeoutSeconds int
commitServerAddress string
selfHealTimeoutSeconds int
statusProcessors int
operationProcessors int
@@ -139,6 +141,8 @@ func NewCommand() *cobra.Command {
repoClientset := apiclient.NewRepoServerClientset(repoServerAddress, repoServerTimeoutSeconds, tlsConfig)
commitClientset := commitclient.NewCommitServerClientset(commitServerAddress)
cache, err := cacheSource()
errors.CheckError(err)
cache.Cache.SetClient(cacheutil.NewTwoLevelClient(cache.Cache.GetClient(), 10*time.Minute))
@@ -157,6 +161,7 @@ func NewCommand() *cobra.Command {
kubeClient,
appClient,
repoClientset,
commitClientset,
cache,
kubectl,
resyncDuration,
@@ -217,6 +222,7 @@ func NewCommand() *cobra.Command {
command.Flags().Int64Var(&repoErrorGracePeriod, "repo-error-grace-period-seconds", int64(env.ParseDurationFromEnv("ARGOCD_REPO_ERROR_GRACE_PERIOD_SECONDS", defaultAppResyncPeriod*time.Second, 0, math.MaxInt64).Seconds()), "Grace period in seconds for ignoring consecutive errors while communicating with repo server.")
command.Flags().StringVar(&repoServerAddress, "repo-server", env.StringFromEnv("ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER", common.DefaultRepoServerAddr), "Repo server address.")
command.Flags().IntVar(&repoServerTimeoutSeconds, "repo-server-timeout-seconds", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_TIMEOUT_SECONDS", 60, 0, math.MaxInt64), "Repo server RPC call timeout seconds.")
command.Flags().StringVar(&commitServerAddress, "commit-server", env.StringFromEnv("ARGOCD_APPLICATION_CONTROLLER_COMMIT_SERVER", common.DefaultCommitServerAddr), "Commit server address.")
command.Flags().IntVar(&statusProcessors, "status-processors", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_STATUS_PROCESSORS", 20, 0, math.MaxInt32), "Number of application status processors")
command.Flags().IntVar(&operationProcessors, "operation-processors", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_OPERATION_PROCESSORS", 10, 0, math.MaxInt32), "Number of application operation processors")
command.Flags().StringVar(&cmdutil.LogFormat, "logformat", env.StringFromEnv("ARGOCD_APPLICATION_CONTROLLER_LOGFORMAT", "text"), "Set the logging format. One of: text|json")

View File

@@ -0,0 +1,91 @@
package commands
import (
"fmt"
"net"
"net/http"
"os"
"os/signal"
"sync"
"syscall"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
cmdutil "github.com/argoproj/argo-cd/v2/cmd/util"
"github.com/argoproj/argo-cd/v2/commitserver"
"github.com/argoproj/argo-cd/v2/commitserver/metrics"
"github.com/argoproj/argo-cd/v2/common"
"github.com/argoproj/argo-cd/v2/reposerver/askpass"
"github.com/argoproj/argo-cd/v2/util/cli"
"github.com/argoproj/argo-cd/v2/util/env"
"github.com/argoproj/argo-cd/v2/util/errors"
)
// NewCommand returns a new instance of an argocd-commit-server command
func NewCommand() *cobra.Command {
var (
listenHost string
listenPort int
metricsPort int
metricsHost string
)
command := &cobra.Command{
Use: "argocd-commit-server",
Short: "Run Argo CD Commit Server",
Long: "Argo CD Commit Server is an internal service which commits and pushes hydrated manifests to git. This command runs Commit Server in the foreground.",
RunE: func(cmd *cobra.Command, args []string) error {
vers := common.GetVersion()
vers.LogStartupInfo(
"Argo CD Commit Server",
map[string]any{
"port": listenPort,
},
)
cli.SetLogFormat(cmdutil.LogFormat)
cli.SetLogLevel(cmdutil.LogLevel)
metricsServer := metrics.NewMetricsServer()
http.Handle("/metrics", metricsServer.GetHandler())
go func() { errors.CheckError(http.ListenAndServe(fmt.Sprintf("%s:%d", metricsHost, metricsPort), nil)) }()
askPassServer := askpass.NewServer(askpass.CommitServerSocketPath)
go func() { errors.CheckError(askPassServer.Run()) }()
server := commitserver.NewServer(askPassServer, metricsServer)
grpc := server.CreateGRPC()
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", listenHost, listenPort))
errors.CheckError(err)
// Graceful shutdown code adapted from here: https://gist.github.com/embano1/e0bf49d24f1cdd07cffad93097c04f0a
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
s := <-sigCh
log.Printf("got signal %v, attempting graceful shutdown", s)
grpc.GracefulStop()
wg.Done()
}()
log.Println("starting grpc server")
err = grpc.Serve(listener)
errors.CheckError(err)
wg.Wait()
log.Println("clean shutdown")
return nil
},
}
command.Flags().StringVar(&cmdutil.LogFormat, "logformat", env.StringFromEnv("ARGOCD_COMMIT_SERVER_LOGFORMAT", "text"), "Set the logging format. One of: text|json")
command.Flags().StringVar(&cmdutil.LogLevel, "loglevel", env.StringFromEnv("ARGOCD_COMMIT_SERVER_LOGLEVEL", "info"), "Set the logging level. One of: debug|info|warn|error")
command.Flags().StringVar(&listenHost, "address", env.StringFromEnv("ARGOCD_COMMIT_SERVER_LISTEN_ADDRESS", common.DefaultAddressCommitServer), "Listen on given address for incoming connections")
command.Flags().IntVar(&listenPort, "port", common.DefaultPortCommitServer, "Listen on given port for incoming connections")
command.Flags().StringVar(&metricsHost, "metrics-address", env.StringFromEnv("ARGOCD_COMMIT_SERVER_METRICS_LISTEN_ADDRESS", common.DefaultAddressCommitServerMetrics), "Listen on given address for metrics")
command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortCommitServerMetrics, "Start metrics server on given port")
return command
}

View File

@@ -1,13 +1,10 @@
package admin
import (
"context"
"reflect"
"strings"
"github.com/spf13/cobra"
apiv1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -86,12 +83,11 @@ func newArgoCDClientsets(config *rest.Config, namespace string) *argoCDClientset
dynamicIf, err := dynamic.NewForConfig(config)
errors.CheckError(err)
return &argoCDClientsets{
configMaps: dynamicIf.Resource(configMapResource).Namespace(namespace),
secrets: dynamicIf.Resource(secretResource).Namespace(namespace),
// To support applications and applicationsets in any namespace we will watch all namespaces and filter them afterwards
applications: dynamicIf.Resource(applicationsResource),
configMaps: dynamicIf.Resource(configMapResource).Namespace(namespace),
secrets: dynamicIf.Resource(secretResource).Namespace(namespace),
applications: dynamicIf.Resource(applicationsResource).Namespace(namespace),
projects: dynamicIf.Resource(appprojectsResource).Namespace(namespace),
applicationSets: dynamicIf.Resource(appplicationSetResource),
applicationSets: dynamicIf.Resource(appplicationSetResource).Namespace(namespace),
}
}
@@ -222,52 +218,3 @@ func specsEqual(left, right unstructured.Unstructured) bool {
}
return false
}
type argocdAdditonalNamespaces struct {
applicationNamespaces []string
applicationsetNamespaces []string
}
const (
applicationsetNamespacesCmdParamsKey = "applicationsetcontroller.namespaces"
applicationNamespacesCmdParamsKey = "application.namespaces"
)
// Get additional namespaces from argocd-cmd-params
func getAdditionalNamespaces(ctx context.Context, argocdClientsets *argoCDClientsets) *argocdAdditonalNamespaces {
applicationNamespaces := make([]string, 0)
applicationsetNamespaces := make([]string, 0)
un, err := argocdClientsets.configMaps.Get(ctx, common.ArgoCDCmdParamsConfigMapName, v1.GetOptions{})
errors.CheckError(err)
var cm apiv1.ConfigMap
err = runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, &cm)
errors.CheckError(err)
namespacesListFromString := func(namespaces string) []string {
listOfNamespaces := []string{}
ss := strings.Split(namespaces, ",")
for _, namespace := range ss {
if namespace != "" {
listOfNamespaces = append(listOfNamespaces, strings.TrimSpace(namespace))
}
}
return listOfNamespaces
}
if strNamespaces, ok := cm.Data[applicationNamespacesCmdParamsKey]; ok {
applicationNamespaces = namespacesListFromString(strNamespaces)
}
if strNamespaces, ok := cm.Data[applicationsetNamespacesCmdParamsKey]; ok {
applicationsetNamespaces = namespacesListFromString(strNamespaces)
}
return &argocdAdditonalNamespaces{
applicationNamespaces: applicationNamespaces,
applicationsetNamespaces: applicationsetNamespaces,
}
}

View File

@@ -1,75 +0,0 @@
package admin
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
dynfake "k8s.io/client-go/dynamic/fake"
)
func TestGetAdditionalNamespaces(t *testing.T) {
createArgoCDCmdCMWithKeys := func(data map[string]interface{}) *unstructured.Unstructured {
return &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "argocd-cmd-params-cm",
"namespace": "argocd",
},
"data": data,
},
}
}
testCases := []struct {
CmdParamsKeys map[string]interface{}
expected argocdAdditonalNamespaces
description string
}{
{
description: "empty configmap should return no additional namespaces",
CmdParamsKeys: map[string]interface{}{},
expected: argocdAdditonalNamespaces{applicationNamespaces: []string{}, applicationsetNamespaces: []string{}},
},
{
description: "empty strings in respective keys in cm shoud return empty namespace list",
CmdParamsKeys: map[string]interface{}{applicationsetNamespacesCmdParamsKey: "", applicationNamespacesCmdParamsKey: ""},
expected: argocdAdditonalNamespaces{applicationNamespaces: []string{}, applicationsetNamespaces: []string{}},
},
{
description: "when only one of the keys in the cm is set only correct respective list of namespaces should be returned",
CmdParamsKeys: map[string]interface{}{applicationNamespacesCmdParamsKey: "foo, bar*"},
expected: argocdAdditonalNamespaces{applicationsetNamespaces: []string{}, applicationNamespaces: []string{"foo", "bar*"}},
},
{
description: "when only one of the keys in the cm is set only correct respective list of namespaces should be returned",
CmdParamsKeys: map[string]interface{}{applicationsetNamespacesCmdParamsKey: "foo, bar*"},
expected: argocdAdditonalNamespaces{applicationNamespaces: []string{}, applicationsetNamespaces: []string{"foo", "bar*"}},
},
{
description: "whitespaces are removed for both multiple and single namespace",
CmdParamsKeys: map[string]interface{}{applicationNamespacesCmdParamsKey: " bar ", applicationsetNamespacesCmdParamsKey: " foo , bar* "},
expected: argocdAdditonalNamespaces{applicationNamespaces: []string{"bar"}, applicationsetNamespaces: []string{"foo", "bar*"}},
},
}
for _, c := range testCases {
fakeDynClient := dynfake.NewSimpleDynamicClient(runtime.NewScheme(), createArgoCDCmdCMWithKeys(c.CmdParamsKeys))
argoCDClientsets := &argoCDClientsets{
configMaps: fakeDynClient.Resource(configMapResource).Namespace("argocd"),
applications: fakeDynClient.Resource(schema.GroupVersionResource{}),
applicationSets: fakeDynClient.Resource(schema.GroupVersionResource{}),
secrets: fakeDynClient.Resource(schema.GroupVersionResource{}),
projects: fakeDynClient.Resource(schema.GroupVersionResource{}),
}
result := getAdditionalNamespaces(context.TODO(), argoCDClientsets)
assert.Equal(t, c.expected, *result)
}
}

View File

@@ -20,16 +20,13 @@ import (
"github.com/argoproj/argo-cd/v2/pkg/apis/application"
"github.com/argoproj/argo-cd/v2/util/cli"
"github.com/argoproj/argo-cd/v2/util/errors"
secutil "github.com/argoproj/argo-cd/v2/util/security"
)
// NewExportCommand defines a new command for exporting Kubernetes and Argo CD resources.
func NewExportCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
out string
applicationNamespaces []string
applicationsetNamespaces []string
clientConfig clientcmd.ClientConfig
out string
)
command := cobra.Command{
Use: "export",
@@ -61,47 +58,34 @@ func NewExportCommand() *cobra.Command {
acdClients := newArgoCDClientsets(config, namespace)
acdConfigMap, err := acdClients.configMaps.Get(ctx, common.ArgoCDConfigMapName, v1.GetOptions{})
errors.CheckError(err)
export(writer, *acdConfigMap, namespace)
export(writer, *acdConfigMap)
acdRBACConfigMap, err := acdClients.configMaps.Get(ctx, common.ArgoCDRBACConfigMapName, v1.GetOptions{})
errors.CheckError(err)
export(writer, *acdRBACConfigMap, namespace)
export(writer, *acdRBACConfigMap)
acdKnownHostsConfigMap, err := acdClients.configMaps.Get(ctx, common.ArgoCDKnownHostsConfigMapName, v1.GetOptions{})
errors.CheckError(err)
export(writer, *acdKnownHostsConfigMap, namespace)
export(writer, *acdKnownHostsConfigMap)
acdTLSCertsConfigMap, err := acdClients.configMaps.Get(ctx, common.ArgoCDTLSCertsConfigMapName, v1.GetOptions{})
errors.CheckError(err)
export(writer, *acdTLSCertsConfigMap, namespace)
export(writer, *acdTLSCertsConfigMap)
referencedSecrets := getReferencedSecrets(*acdConfigMap)
secrets, err := acdClients.secrets.List(ctx, v1.ListOptions{})
errors.CheckError(err)
for _, secret := range secrets.Items {
if isArgoCDSecret(referencedSecrets, secret) {
export(writer, secret, namespace)
export(writer, secret)
}
}
projects, err := acdClients.projects.List(ctx, v1.ListOptions{})
errors.CheckError(err)
for _, proj := range projects.Items {
export(writer, proj, namespace)
export(writer, proj)
}
additionalNamespaces := getAdditionalNamespaces(ctx, acdClients)
if len(applicationNamespaces) == 0 {
applicationNamespaces = additionalNamespaces.applicationNamespaces
}
if len(applicationsetNamespaces) == 0 {
applicationsetNamespaces = additionalNamespaces.applicationsetNamespaces
}
applications, err := acdClients.applications.List(ctx, v1.ListOptions{})
errors.CheckError(err)
for _, app := range applications.Items {
// Export application only if it is in one of the enabled namespaces
if secutil.IsNamespaceEnabled(app.GetNamespace(), namespace, applicationNamespaces) {
export(writer, app, namespace)
}
export(writer, app)
}
applicationSets, err := acdClients.applicationSets.List(ctx, v1.ListOptions{})
if err != nil && !apierr.IsNotFound(err) {
@@ -113,9 +97,7 @@ func NewExportCommand() *cobra.Command {
}
if applicationSets != nil {
for _, appSet := range applicationSets.Items {
if secutil.IsNamespaceEnabled(appSet.GetNamespace(), namespace, applicationsetNamespaces) {
export(writer, appSet, namespace)
}
export(writer, appSet)
}
}
},
@@ -123,21 +105,18 @@ func NewExportCommand() *cobra.Command {
clientConfig = cli.AddKubectlFlagsToCmd(&command)
command.Flags().StringVarP(&out, "out", "o", "-", "Output to the specified file instead of stdout")
command.Flags().StringSliceVarP(&applicationNamespaces, "application-namespaces", "", []string{}, fmt.Sprintf("Comma separated list of namespace globs to export applications from. If not provided value from '%s' in %s will be used,if it's not defined only applications from Argo CD namespace will be exported", applicationNamespacesCmdParamsKey, common.ArgoCDCmdParamsConfigMapName))
command.Flags().StringSliceVarP(&applicationsetNamespaces, "applicationset-namespaces", "", []string{}, fmt.Sprintf("Comma separated list of namespace globs to export applicationsets from. If not provided value from '%s' in %s will be used,if it's not defined only applicationsets from Argo CD namespace will be exported", applicationsetNamespacesCmdParamsKey, common.ArgoCDCmdParamsConfigMapName))
return &command
}
// NewImportCommand defines a new command for exporting Kubernetes and Argo CD resources.
func NewImportCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
prune bool
dryRun bool
verbose bool
stopOperation bool
applicationNamespaces []string
applicationsetNamespaces []string
clientConfig clientcmd.ClientConfig
prune bool
dryRun bool
verbose bool
stopOperation bool
)
command := cobra.Command{
Use: "import SOURCE",
@@ -156,8 +135,6 @@ func NewImportCommand() *cobra.Command {
namespace, _, err := clientConfig.Namespace()
errors.CheckError(err)
acdClients := newArgoCDClientsets(config, namespace)
client, err := dynamic.NewForConfig(config)
errors.CheckError(err)
var input []byte
if in := args[0]; in == "-" {
@@ -171,15 +148,6 @@ func NewImportCommand() *cobra.Command {
dryRunMsg = " (dry run)"
}
additionalNamespaces := getAdditionalNamespaces(ctx, acdClients)
if len(applicationNamespaces) == 0 {
applicationNamespaces = additionalNamespaces.applicationNamespaces
}
if len(applicationsetNamespaces) == 0 {
applicationsetNamespaces = additionalNamespaces.applicationsetNamespaces
}
// pruneObjects tracks live objects and it's current resource version. any remaining
// items in this map indicates the resource should be pruned since it no longer appears
// in the backup
@@ -191,7 +159,7 @@ func NewImportCommand() *cobra.Command {
var referencedSecrets map[string]bool
for _, cm := range configMaps.Items {
if isArgoCDConfigMap(cm.GetName()) {
pruneObjects[kube.ResourceKey{Group: "", Kind: "ConfigMap", Name: cm.GetName(), Namespace: cm.GetNamespace()}] = cm
pruneObjects[kube.ResourceKey{Group: "", Kind: "ConfigMap", Name: cm.GetName()}] = cm
}
if cm.GetName() == common.ArgoCDConfigMapName {
referencedSecrets = getReferencedSecrets(cm)
@@ -202,20 +170,18 @@ func NewImportCommand() *cobra.Command {
errors.CheckError(err)
for _, secret := range secrets.Items {
if isArgoCDSecret(referencedSecrets, secret) {
pruneObjects[kube.ResourceKey{Group: "", Kind: "Secret", Name: secret.GetName(), Namespace: secret.GetNamespace()}] = secret
pruneObjects[kube.ResourceKey{Group: "", Kind: "Secret", Name: secret.GetName()}] = secret
}
}
applications, err := acdClients.applications.List(ctx, v1.ListOptions{})
errors.CheckError(err)
for _, app := range applications.Items {
if secutil.IsNamespaceEnabled(app.GetNamespace(), namespace, applicationNamespaces) {
pruneObjects[kube.ResourceKey{Group: application.Group, Kind: application.ApplicationKind, Name: app.GetName(), Namespace: app.GetNamespace()}] = app
}
pruneObjects[kube.ResourceKey{Group: application.Group, Kind: application.ApplicationKind, Name: app.GetName()}] = app
}
projects, err := acdClients.projects.List(ctx, v1.ListOptions{})
errors.CheckError(err)
for _, proj := range projects.Items {
pruneObjects[kube.ResourceKey{Group: application.Group, Kind: application.AppProjectKind, Name: proj.GetName(), Namespace: proj.GetNamespace()}] = proj
pruneObjects[kube.ResourceKey{Group: application.Group, Kind: application.AppProjectKind, Name: proj.GetName()}] = proj
}
applicationSets, err := acdClients.applicationSets.List(ctx, v1.ListOptions{})
if apierr.IsForbidden(err) || apierr.IsNotFound(err) {
@@ -225,9 +191,7 @@ func NewImportCommand() *cobra.Command {
}
if applicationSets != nil {
for _, appSet := range applicationSets.Items {
if secutil.IsNamespaceEnabled(appSet.GetNamespace(), namespace, applicationsetNamespaces) {
pruneObjects[kube.ResourceKey{Group: application.Group, Kind: application.ApplicationSetKind, Name: appSet.GetName(), Namespace: appSet.GetNamespace()}] = appSet
}
pruneObjects[kube.ResourceKey{Group: application.Group, Kind: application.ApplicationSetKind, Name: appSet.GetName()}] = appSet
}
}
@@ -236,33 +200,21 @@ func NewImportCommand() *cobra.Command {
errors.CheckError(err)
for _, bakObj := range backupObjects {
gvk := bakObj.GroupVersionKind()
// For objects without namespace, assume they belong in ArgoCD namespace
if bakObj.GetNamespace() == "" {
bakObj.SetNamespace(namespace)
}
key := kube.ResourceKey{Group: gvk.Group, Kind: gvk.Kind, Name: bakObj.GetName(), Namespace: bakObj.GetNamespace()}
key := kube.ResourceKey{Group: gvk.Group, Kind: gvk.Kind, Name: bakObj.GetName()}
liveObj, exists := pruneObjects[key]
delete(pruneObjects, key)
var dynClient dynamic.ResourceInterface
switch bakObj.GetKind() {
case "Secret":
dynClient = client.Resource(secretResource).Namespace(bakObj.GetNamespace())
dynClient = acdClients.secrets
case "ConfigMap":
dynClient = client.Resource(configMapResource).Namespace(bakObj.GetNamespace())
dynClient = acdClients.configMaps
case application.AppProjectKind:
dynClient = client.Resource(appprojectsResource).Namespace(bakObj.GetNamespace())
dynClient = acdClients.projects
case application.ApplicationKind:
dynClient = client.Resource(applicationsResource).Namespace(bakObj.GetNamespace())
// If application is not in one of the allowed namespaces do not import it
if !secutil.IsNamespaceEnabled(bakObj.GetNamespace(), namespace, applicationNamespaces) {
continue
}
dynClient = acdClients.applications
case application.ApplicationSetKind:
dynClient = client.Resource(appplicationSetResource).Namespace(bakObj.GetNamespace())
// If applicationset is not in one of the allowed namespaces do not import it
if !secutil.IsNamespaceEnabled(bakObj.GetNamespace(), namespace, applicationsetNamespaces) {
continue
}
dynClient = acdClients.applicationSets
}
if !exists {
isForbidden := false
@@ -276,7 +228,7 @@ func NewImportCommand() *cobra.Command {
}
}
if !isForbidden {
fmt.Printf("%s/%s %s in namespace %s created%s\n", gvk.Group, gvk.Kind, bakObj.GetName(), bakObj.GetNamespace(), dryRunMsg)
fmt.Printf("%s/%s %s created%s\n", gvk.Group, gvk.Kind, bakObj.GetName(), dryRunMsg)
}
} else if specsEqual(*bakObj, liveObj) && checkAppHasNoNeedToStopOperation(liveObj, stopOperation) {
if verbose {
@@ -295,7 +247,7 @@ func NewImportCommand() *cobra.Command {
}
}
if !isForbidden {
fmt.Printf("%s/%s %s in namespace %s updated%s\n", gvk.Group, gvk.Kind, bakObj.GetName(), bakObj.GetNamespace(), dryRunMsg)
fmt.Printf("%s/%s %s updated%s\n", gvk.Group, gvk.Kind, bakObj.GetName(), dryRunMsg)
}
}
}
@@ -306,11 +258,11 @@ func NewImportCommand() *cobra.Command {
var dynClient dynamic.ResourceInterface
switch key.Kind {
case "Secret":
dynClient = client.Resource(secretResource).Namespace(liveObj.GetNamespace())
dynClient = acdClients.secrets
case application.AppProjectKind:
dynClient = client.Resource(appprojectsResource).Namespace(liveObj.GetNamespace())
dynClient = acdClients.projects
case application.ApplicationKind:
dynClient = client.Resource(applicationsResource).Namespace(liveObj.GetNamespace())
dynClient = acdClients.applications
if !dryRun {
if finalizers := liveObj.GetFinalizers(); len(finalizers) > 0 {
newLive := liveObj.DeepCopy()
@@ -322,7 +274,7 @@ func NewImportCommand() *cobra.Command {
}
}
case application.ApplicationSetKind:
dynClient = client.Resource(appplicationSetResource).Namespace(liveObj.GetNamespace())
dynClient = acdClients.applicationSets
default:
log.Fatalf("Unexpected kind '%s' in prune list", key.Kind)
}
@@ -351,8 +303,6 @@ func NewImportCommand() *cobra.Command {
command.Flags().BoolVar(&prune, "prune", false, "Prune secrets, applications and projects which do not appear in the backup")
command.Flags().BoolVar(&verbose, "verbose", false, "Verbose output (versus only changed output)")
command.Flags().BoolVar(&stopOperation, "stop-operation", false, "Stop any existing operations")
command.Flags().StringSliceVarP(&applicationNamespaces, "application-namespaces", "", []string{}, fmt.Sprintf("Comma separated list of namespace globs to which import of applications is allowed. If not provided value from '%s' in %s will be used,if it's not defined only applications without an explicit namespace will be imported to the Argo CD namespace", applicationNamespacesCmdParamsKey, common.ArgoCDCmdParamsConfigMapName))
command.Flags().StringSliceVarP(&applicationsetNamespaces, "applicationset-namespaces", "", []string{}, fmt.Sprintf("Comma separated list of namespace globs which import of applicationsets is allowed. If not provided value from '%s' in %s will be used,if it's not defined only applicationsets without an explicit namespace will be imported to the Argo CD namespace", applicationsetNamespacesCmdParamsKey, common.ArgoCDCmdParamsConfigMapName))
return &command
}
@@ -370,14 +320,13 @@ func checkAppHasNoNeedToStopOperation(liveObj unstructured.Unstructured, stopOpe
}
// export writes the unstructured object and removes extraneous cruft from output before writing
func export(w io.Writer, un unstructured.Unstructured, argocdNamespace string) {
func export(w io.Writer, un unstructured.Unstructured) {
name := un.GetName()
finalizers := un.GetFinalizers()
apiVersion := un.GetAPIVersion()
kind := un.GetKind()
labels := un.GetLabels()
annotations := un.GetAnnotations()
namespace := un.GetNamespace()
unstructured.RemoveNestedField(un.Object, "metadata")
un.SetName(name)
un.SetFinalizers(finalizers)
@@ -385,9 +334,6 @@ func export(w io.Writer, un unstructured.Unstructured, argocdNamespace string) {
un.SetKind(kind)
un.SetLabels(labels)
un.SetAnnotations(annotations)
if namespace != argocdNamespace {
un.SetNamespace(namespace)
}
data, err := yaml.Marshal(un.Object)
errors.CheckError(err)
_, err = w.Write(data)

View File

@@ -35,8 +35,7 @@ func NewNotificationsCommand() *cobra.Command {
"notifications",
"argocd admin notifications",
applications,
settings.GetFactorySettingsForCLI(&argocdService, "argocd-notifications-secret", "argocd-notifications-cm", false),
func(clientConfig clientcmd.ClientConfig) {
settings.GetFactorySettings(argocdService, "argocd-notifications-secret", "argocd-notifications-cm", false), func(clientConfig clientcmd.ClientConfig) {
k8sCfg, err := clientConfig.ClientConfig()
if err != nil {
log.Fatalf("Failed to parse k8s config: %v", err)

View File

@@ -108,6 +108,7 @@ type watchOpts struct {
suspended bool
degraded bool
delete bool
hydrated bool
}
// NewApplicationCreateCommand returns a new instance of an `argocd app create` command
@@ -1762,6 +1763,7 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
command.Flags().BoolVar(&watch.suspended, "suspended", false, "Wait for suspended")
command.Flags().BoolVar(&watch.degraded, "degraded", false, "Wait for degraded")
command.Flags().BoolVar(&watch.delete, "delete", false, "Wait for delete")
command.Flags().BoolVar(&watch.hydrated, "hydrated", false, "Wait for hydration operations")
command.Flags().StringVarP(&selector, "selector", "l", "", "Wait for apps by label. Supports '=', '==', '!=', in, notin, exists & not exists. Matching apps must satisfy all of the specified label constraints.")
command.Flags().StringArrayVar(&resources, "resource", []string{}, fmt.Sprintf("Sync only specific resources as GROUP%[1]sKIND%[1]sNAME or %[2]sGROUP%[1]sKIND%[1]sNAME. Fields may be blank and '*' can be used. This option may be specified repeatedly", resourceFieldDelimiter, resourceExcludeIndicator))
command.Flags().BoolVar(&watch.operation, "operation", false, "Wait for pending operations")
@@ -2298,7 +2300,7 @@ func groupResourceStates(app *argoappv1.Application, selectedResources []*argoap
}
// check if resource health, sync and operation statuses matches watch options
func checkResourceStatus(watch watchOpts, healthStatus string, syncStatus string, operationStatus *argoappv1.Operation) bool {
func checkResourceStatus(watch watchOpts, healthStatus string, syncStatus string, operationStatus *argoappv1.Operation, hydrationFinished bool) bool {
if watch.delete {
return false
}
@@ -2328,7 +2330,8 @@ func checkResourceStatus(watch watchOpts, healthStatus string, syncStatus string
synced := !watch.sync || syncStatus == string(argoappv1.SyncStatusCodeSynced)
operational := !watch.operation || operationStatus == nil
return synced && healthCheckPassed && operational
hydration := !watch.hydrated || (watch.hydrated && hydrationFinished)
return synced && healthCheckPassed && operational && hydration
}
// resourceParentChild gets the latest state of the app and the latest state of the app's resource tree and then
@@ -2492,13 +2495,15 @@ func waitOnApplicationStatus(ctx context.Context, acdClient argocdclient.Client,
}
}
hydrationFinished := app.Status.SourceHydrator.CurrentOperation != nil && app.Status.SourceHydrator.CurrentOperation.Phase == argoappv1.HydrateOperationPhaseHydrated && app.Status.SourceHydrator.CurrentOperation.SourceHydrator.DeepEquals(app.Status.SourceHydrator.LastSuccessfulOperation.SourceHydrator) && app.Status.SourceHydrator.CurrentOperation.DrySHA == app.Status.SourceHydrator.LastSuccessfulOperation.DrySHA
var selectedResourcesAreReady bool
// If selected resources are included, wait only on those resources, otherwise wait on the application as a whole.
if len(selectedResources) > 0 {
selectedResourcesAreReady = true
for _, state := range getResourceStates(app, selectedResources) {
resourceIsReady := checkResourceStatus(watch, state.Health, state.Status, appEvent.Application.Operation)
resourceIsReady := checkResourceStatus(watch, state.Health, state.Status, appEvent.Application.Operation, hydrationFinished)
if !resourceIsReady {
selectedResourcesAreReady = false
break
@@ -2506,7 +2511,7 @@ func waitOnApplicationStatus(ctx context.Context, acdClient argocdclient.Client,
}
} else {
// Wait on the application as a whole
selectedResourcesAreReady = checkResourceStatus(watch, string(app.Status.Health.Status), string(app.Status.Sync.Status), appEvent.Application.Operation)
selectedResourcesAreReady = checkResourceStatus(watch, string(app.Status.Health.Status), string(app.Status.Sync.Status), appEvent.Application.Operation, hydrationFinished)
}
if selectedResourcesAreReady && (!operationInProgress || !watch.operation) {

View File

@@ -36,7 +36,7 @@ func TestPrintTreeViewAppResources(t *testing.T) {
buf := &bytes.Buffer{}
w := tabwriter.NewWriter(buf, 0, 0, 2, ' ', 0)
printTreeViewAppResourcesNotOrphaned(nodeMapping, mapParentToChild, parentNode, w)
printTreeViewAppResourcesNotOrphaned(nodeMapping, mapParentToChild, parentNode, false, false, w)
if err := w.Flush(); err != nil {
t.Fatal(err)
}
@@ -77,7 +77,7 @@ func TestPrintTreeViewDetailedAppResources(t *testing.T) {
buf := &bytes.Buffer{}
w := tabwriter.NewWriter(buf, 0, 0, 2, ' ', 0)
printDetailedTreeViewAppResourcesNotOrphaned(nodeMapping, mapParentToChild, parentNode, w)
printDetailedTreeViewAppResourcesNotOrphaned(nodeMapping, mapParentToChild, parentNode, false, false, w)
if err := w.Flush(); err != nil {
t.Fatal(err)
}

View File

@@ -175,25 +175,25 @@ func parentChildInfo(nodes []v1alpha1.ResourceNode) (map[string]v1alpha1.Resourc
return mapUidToNode, mapParentToChild, parentNode
}
func printDetailedTreeViewAppResourcesNotOrphaned(nodeMapping map[string]v1alpha1.ResourceNode, parentChildMapping map[string][]string, parentNodes map[string]struct{}, w *tabwriter.Writer) {
func printDetailedTreeViewAppResourcesNotOrphaned(nodeMapping map[string]v1alpha1.ResourceNode, parentChildMapping map[string][]string, parentNodes map[string]struct{}, orphaned bool, listAll bool, w *tabwriter.Writer) {
for uid := range parentNodes {
detailedTreeViewAppResourcesNotOrphaned("", nodeMapping, parentChildMapping, nodeMapping[uid], w)
}
}
func printDetailedTreeViewAppResourcesOrphaned(nodeMapping map[string]v1alpha1.ResourceNode, parentChildMapping map[string][]string, parentNodes map[string]struct{}, w *tabwriter.Writer) {
func printDetailedTreeViewAppResourcesOrphaned(nodeMapping map[string]v1alpha1.ResourceNode, parentChildMapping map[string][]string, parentNodes map[string]struct{}, orphaned bool, listAll bool, w *tabwriter.Writer) {
for uid := range parentNodes {
detailedTreeViewAppResourcesOrphaned("", nodeMapping, parentChildMapping, nodeMapping[uid], w)
}
}
func printTreeViewAppResourcesNotOrphaned(nodeMapping map[string]v1alpha1.ResourceNode, parentChildMapping map[string][]string, parentNodes map[string]struct{}, w *tabwriter.Writer) {
func printTreeViewAppResourcesNotOrphaned(nodeMapping map[string]v1alpha1.ResourceNode, parentChildMapping map[string][]string, parentNodes map[string]struct{}, orphaned bool, listAll bool, w *tabwriter.Writer) {
for uid := range parentNodes {
treeViewAppResourcesNotOrphaned("", nodeMapping, parentChildMapping, nodeMapping[uid], w)
}
}
func printTreeViewAppResourcesOrphaned(nodeMapping map[string]v1alpha1.ResourceNode, parentChildMapping map[string][]string, parentNodes map[string]struct{}, w *tabwriter.Writer) {
func printTreeViewAppResourcesOrphaned(nodeMapping map[string]v1alpha1.ResourceNode, parentChildMapping map[string][]string, parentNodes map[string]struct{}, orphaned bool, listAll bool, w *tabwriter.Writer) {
for uid := range parentNodes {
treeViewAppResourcesOrphaned("", nodeMapping, parentChildMapping, nodeMapping[uid], w)
}
@@ -206,24 +206,24 @@ func printResources(listAll bool, orphaned bool, appResourceTree *v1alpha1.Appli
if !orphaned || listAll {
mapUidToNode, mapParentToChild, parentNode := parentChildInfo(appResourceTree.Nodes)
printDetailedTreeViewAppResourcesNotOrphaned(mapUidToNode, mapParentToChild, parentNode, w)
printDetailedTreeViewAppResourcesNotOrphaned(mapUidToNode, mapParentToChild, parentNode, orphaned, listAll, w)
}
if orphaned || listAll {
mapUidToNode, mapParentToChild, parentNode := parentChildInfo(appResourceTree.OrphanedNodes)
printDetailedTreeViewAppResourcesOrphaned(mapUidToNode, mapParentToChild, parentNode, w)
printDetailedTreeViewAppResourcesOrphaned(mapUidToNode, mapParentToChild, parentNode, orphaned, listAll, w)
}
} else if output == "tree" {
fmt.Fprintf(w, "GROUP\tKIND\tNAMESPACE\tNAME\tORPHANED\n")
if !orphaned || listAll {
mapUidToNode, mapParentToChild, parentNode := parentChildInfo(appResourceTree.Nodes)
printTreeViewAppResourcesNotOrphaned(mapUidToNode, mapParentToChild, parentNode, w)
printTreeViewAppResourcesNotOrphaned(mapUidToNode, mapParentToChild, parentNode, orphaned, listAll, w)
}
if orphaned || listAll {
mapUidToNode, mapParentToChild, parentNode := parentChildInfo(appResourceTree.OrphanedNodes)
printTreeViewAppResourcesOrphaned(mapUidToNode, mapParentToChild, parentNode, w)
printTreeViewAppResourcesOrphaned(mapUidToNode, mapParentToChild, parentNode, orphaned, listAll, w)
}
} else {
headers := []interface{}{"GROUP", "KIND", "NAMESPACE", "NAME", "ORPHANED"}

View File

@@ -1700,7 +1700,7 @@ func TestCheckResourceStatus(t *testing.T) {
suspended: true,
health: true,
degraded: true,
}, string(health.HealthStatusHealthy), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{})
}, string(health.HealthStatusHealthy), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.True(t, res)
})
t.Run("Degraded, Suspended and health status failed", func(t *testing.T) {
@@ -1708,57 +1708,57 @@ func TestCheckResourceStatus(t *testing.T) {
suspended: true,
health: true,
degraded: true,
}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{})
}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.False(t, res)
})
t.Run("Suspended and health status passed", func(t *testing.T) {
res := checkResourceStatus(watchOpts{
suspended: true,
health: true,
}, string(health.HealthStatusHealthy), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{})
}, string(health.HealthStatusHealthy), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.True(t, res)
})
t.Run("Suspended and health status failed", func(t *testing.T) {
res := checkResourceStatus(watchOpts{
suspended: true,
health: true,
}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{})
}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.False(t, res)
})
t.Run("Suspended passed", func(t *testing.T) {
res := checkResourceStatus(watchOpts{
suspended: true,
health: false,
}, string(health.HealthStatusSuspended), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{})
}, string(health.HealthStatusSuspended), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.True(t, res)
})
t.Run("Suspended failed", func(t *testing.T) {
res := checkResourceStatus(watchOpts{
suspended: true,
health: false,
}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{})
}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.False(t, res)
})
t.Run("Health passed", func(t *testing.T) {
res := checkResourceStatus(watchOpts{
suspended: false,
health: true,
}, string(health.HealthStatusHealthy), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{})
}, string(health.HealthStatusHealthy), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.True(t, res)
})
t.Run("Health failed", func(t *testing.T) {
res := checkResourceStatus(watchOpts{
suspended: false,
health: true,
}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{})
}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.False(t, res)
})
t.Run("Synced passed", func(t *testing.T) {
res := checkResourceStatus(watchOpts{}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{})
res := checkResourceStatus(watchOpts{}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.True(t, res)
})
t.Run("Synced failed", func(t *testing.T) {
res := checkResourceStatus(watchOpts{}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeOutOfSync), &v1alpha1.Operation{})
res := checkResourceStatus(watchOpts{}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeOutOfSync), &v1alpha1.Operation{}, true)
assert.True(t, res)
})
t.Run("Degraded passed", func(t *testing.T) {
@@ -1766,7 +1766,7 @@ func TestCheckResourceStatus(t *testing.T) {
suspended: false,
health: false,
degraded: true,
}, string(health.HealthStatusDegraded), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{})
}, string(health.HealthStatusDegraded), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.True(t, res)
})
t.Run("Degraded failed", func(t *testing.T) {
@@ -1774,7 +1774,7 @@ func TestCheckResourceStatus(t *testing.T) {
suspended: false,
health: false,
degraded: true,
}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{})
}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.False(t, res)
})
}

View File

@@ -34,10 +34,6 @@ const (
clusterFieldName = "name"
// cluster field is 'namespaces'
clusterFieldNamespaces = "namespaces"
// cluster field is 'labels'
clusterFieldLabel = "labels"
// cluster field is 'annotations'
clusterFieldAnnotation = "annotations"
// indicates managing all namespaces
allNamespaces = "*"
)
@@ -224,8 +220,6 @@ func NewClusterSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
var (
clusterOptions cmdutil.ClusterOptions
clusterName string
labels []string
annotations []string
)
command := &cobra.Command{
Use: "set NAME",
@@ -244,25 +238,17 @@ func NewClusterSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
conn, clusterIf := headless.NewClientOrDie(clientOpts, c).NewClusterClientOrDie()
defer io.Close(conn)
// checks the fields that needs to be updated
updatedFields := checkFieldsToUpdate(clusterOptions, labels, annotations)
updatedFields := checkFieldsToUpdate(clusterOptions)
namespaces := clusterOptions.Namespaces
// check if all namespaces have to be considered
if len(namespaces) == 1 && strings.EqualFold(namespaces[0], allNamespaces) {
namespaces[0] = ""
}
// parse the labels you're receiving from the label flag
labelsMap, err := label.Parse(labels)
errors.CheckError(err)
// parse the annotations you're receiving from the annotation flag
annotationsMap, err := label.Parse(annotations)
errors.CheckError(err)
if updatedFields != nil {
clusterUpdateRequest := clusterpkg.ClusterUpdateRequest{
Cluster: &argoappv1.Cluster{
Name: clusterOptions.Name,
Namespaces: namespaces,
Labels: labelsMap,
Annotations: annotationsMap,
Name: clusterOptions.Name,
Namespaces: namespaces,
},
UpdatedFields: updatedFields,
Id: &clusterpkg.ClusterID{
@@ -280,13 +266,11 @@ func NewClusterSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
}
command.Flags().StringVar(&clusterOptions.Name, "name", "", "Overwrite the cluster name")
command.Flags().StringArrayVar(&clusterOptions.Namespaces, "namespace", nil, "List of namespaces which are allowed to manage. Specify '*' to manage all namespaces")
command.Flags().StringArrayVar(&labels, "label", nil, "Set metadata labels (e.g. --label key=value)")
command.Flags().StringArrayVar(&annotations, "annotation", nil, "Set metadata annotations (e.g. --annotation key=value)")
return command
}
// checkFieldsToUpdate returns the fields that needs to be updated
func checkFieldsToUpdate(clusterOptions cmdutil.ClusterOptions, labels []string, annotations []string) []string {
func checkFieldsToUpdate(clusterOptions cmdutil.ClusterOptions) []string {
var updatedFields []string
if clusterOptions.Name != "" {
updatedFields = append(updatedFields, clusterFieldName)
@@ -294,12 +278,6 @@ func checkFieldsToUpdate(clusterOptions cmdutil.ClusterOptions, labels []string,
if clusterOptions.Namespaces != nil {
updatedFields = append(updatedFields, clusterFieldNamespaces)
}
if labels != nil {
updatedFields = append(updatedFields, clusterFieldLabel)
}
if annotations != nil {
updatedFields = append(updatedFields, clusterFieldAnnotation)
}
return updatedFields
}

View File

@@ -80,7 +80,6 @@ func NewCommand() *cobra.Command {
command.PersistentFlags().StringVar(&clientOpts.PortForwardNamespace, "port-forward-namespace", config.GetFlag("port-forward-namespace", ""), "Namespace name which should be used for port forwarding")
command.PersistentFlags().IntVar(&clientOpts.HttpRetryMax, "http-retry-max", config.GetIntFlag("http-retry-max", 0), "Maximum number of retries to establish http connection to Argo CD server")
command.PersistentFlags().BoolVar(&clientOpts.Core, "core", config.GetBoolFlag("core"), "If set to true then CLI talks directly to Kubernetes instead of talking to Argo CD API server")
command.PersistentFlags().StringVar(&clientOpts.Context, "argocd-context", "", "The name of the Argo-CD server context to use")
command.PersistentFlags().StringVar(&clientOpts.ServerName, "server-name", env.StringFromEnv(common.EnvServerName, common.DefaultServerName), fmt.Sprintf("Name of the Argo CD API server; set this or the %s environment variable when the server's name label differs from the default, for example when installing via the Helm chart", common.EnvServerName))
command.PersistentFlags().StringVar(&clientOpts.AppControllerName, "controller-name", env.StringFromEnv(common.EnvAppControllerName, common.DefaultApplicationControllerName), fmt.Sprintf("Name of the Argo CD Application controller; set this or the %s environment variable when the controller's name label differs from the default, for example when installing via the Helm chart", common.EnvAppControllerName))
command.PersistentFlags().StringVar(&clientOpts.RedisHaProxyName, "redis-haproxy-name", env.StringFromEnv(common.EnvRedisHaProxyName, common.DefaultRedisHaProxyName), fmt.Sprintf("Name of the Redis HA Proxy; set this or the %s environment variable when the HA Proxy's name label differs from the default, for example when installing via the Helm chart", common.EnvRedisHaProxyName))

View File

@@ -9,6 +9,7 @@ import (
appcontroller "github.com/argoproj/argo-cd/v2/cmd/argocd-application-controller/commands"
applicationset "github.com/argoproj/argo-cd/v2/cmd/argocd-applicationset-controller/commands"
cmpserver "github.com/argoproj/argo-cd/v2/cmd/argocd-cmp-server/commands"
commitserver "github.com/argoproj/argo-cd/v2/cmd/argocd-commit-server/commands"
dex "github.com/argoproj/argo-cd/v2/cmd/argocd-dex/commands"
gitaskpass "github.com/argoproj/argo-cd/v2/cmd/argocd-git-ask-pass/commands"
k8sauth "github.com/argoproj/argo-cd/v2/cmd/argocd-k8s-auth/commands"
@@ -40,6 +41,8 @@ func main() {
command = reposerver.NewCommand()
case "argocd-cmp-server":
command = cmpserver.NewCommand()
case "argocd-commit-server":
command = commitserver.NewCommand()
case "argocd-dex":
command = dex.NewCommand()
case "argocd-notifications":

View File

@@ -86,6 +86,12 @@ type AppOptions struct {
retryBackoffMaxDuration time.Duration
retryBackoffFactor int64
ref string
drySourceRepo string
drySourceRevision string
drySourcePath string
syncSourceBranch string
syncSourcePath string
hydrateToBranch string
}
func AddAppFlags(command *cobra.Command, opts *AppOptions) {
@@ -94,6 +100,12 @@ func AddAppFlags(command *cobra.Command, opts *AppOptions) {
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().StringVar(&opts.drySourceRepo, "dry-source-repo", "", "Repository URL of the app dry source")
command.Flags().StringVar(&opts.drySourceRevision, "dry-source-revision", "", "Revision of the app dry source")
command.Flags().StringVar(&opts.drySourcePath, "dry-source-path", "", "Path in repository to the app directory for the dry source")
command.Flags().StringVar(&opts.syncSourceBranch, "sync-source-branch", "", "The branch from which the app will sync")
command.Flags().StringVar(&opts.syncSourcePath, "sync-source-path", "", "The path in the repository from which the app will sync")
command.Flags().StringVar(&opts.hydrateToBranch, "hydrate-to-branch", "", "The branch to hydrate the app to")
command.Flags().IntVar(&opts.revisionHistoryLimit, "revision-history-limit", argoappv1.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)")
@@ -154,21 +166,28 @@ func SetAppSpecOptions(flags *pflag.FlagSet, spec *argoappv1.ApplicationSpec, ap
if flags == nil {
return visited
}
source := spec.GetSourcePtrByPosition(sourcePosition)
if source == nil {
source = &argoappv1.ApplicationSource{}
}
source, visited = ConstructSource(source, *appOpts, flags)
if spec.HasMultipleSources() {
if sourcePosition == 0 {
spec.Sources[sourcePosition] = *source
} else if sourcePosition > 0 {
spec.Sources[sourcePosition-1] = *source
} else {
spec.Sources = append(spec.Sources, *source)
}
var h *argoappv1.SourceHydrator
h, hasHydratorFlag := constructSourceHydrator(spec.SourceHydrator, *appOpts, flags)
if hasHydratorFlag {
spec.SourceHydrator = h
} else {
spec.Source = source
source := spec.GetSourcePtrByPosition(sourcePosition)
if source == nil {
source = &argoappv1.ApplicationSource{}
}
source, visited = ConstructSource(source, *appOpts, flags)
if spec.HasMultipleSources() {
if sourcePosition == 0 {
spec.Sources[sourcePosition] = *source
} else if sourcePosition > 0 {
spec.Sources[sourcePosition-1] = *source
} else {
spec.Sources = append(spec.Sources, *source)
}
} else {
spec.Source = source
}
}
flags.Visit(func(f *pflag.Flag) {
visited++
@@ -563,9 +582,7 @@ func constructAppsBaseOnName(appName string, labels, annotations, args []string,
Name: appName,
Namespace: appNs,
},
Spec: argoappv1.ApplicationSpec{
Source: &argoappv1.ApplicationSource{},
},
Spec: argoappv1.ApplicationSpec{},
}
SetAppSpecOptions(flags, &app.Spec, &appOpts, 0)
SetParameterOverrides(app, appOpts.Parameters, 0)
@@ -733,6 +750,47 @@ func ConstructSource(source *argoappv1.ApplicationSource, appOpts AppOptions, fl
return source, visited
}
// constructSourceHydrator constructs a source hydrator from the command line flags. It returns the modified source
// hydrator and a boolean indicating if any hydrator flags were set. We return instead of just modifying the source
// hydrator in place because the given hydrator `h` might be nil. In that case, we need to create a new source hydrator
// and return it.
func constructSourceHydrator(h *argoappv1.SourceHydrator, appOpts AppOptions, flags *pflag.FlagSet) (*argoappv1.SourceHydrator, bool) {
hasHydratorFlag := false
ensureNotNil := func(notEmpty bool) {
hasHydratorFlag = true
if notEmpty && h == nil {
h = &argoappv1.SourceHydrator{}
}
}
flags.Visit(func(f *pflag.Flag) {
switch f.Name {
case "dry-source-repo":
ensureNotNil(appOpts.drySourceRepo != "")
h.DrySource.RepoURL = appOpts.drySourceRepo
case "dry-source-path":
ensureNotNil(appOpts.drySourcePath != "")
h.DrySource.Path = appOpts.drySourcePath
case "dry-source-revision":
ensureNotNil(appOpts.drySourceRevision != "")
h.DrySource.TargetRevision = appOpts.drySourceRevision
case "sync-source-branch":
ensureNotNil(appOpts.syncSourceBranch != "")
h.SyncSource.TargetBranch = appOpts.syncSourceBranch
case "sync-source-path":
ensureNotNil(appOpts.syncSourcePath != "")
h.SyncSource.Path = appOpts.syncSourcePath
case "hydrate-to-branch":
ensureNotNil(appOpts.hydrateToBranch != "")
if appOpts.hydrateToBranch == "" {
h.HydrateTo = nil
} else {
h.HydrateTo = &argoappv1.HydrateTo{TargetBranch: appOpts.hydrateToBranch}
}
}
})
return h, hasHydratorFlag
}
func mergeLabels(app *argoappv1.Application, labels []string) {
mapLabels, err := label.Parse(labels)
errors.CheckError(err)

View File

@@ -284,6 +284,28 @@ func Test_setAppSpecOptions(t *testing.T) {
require.NoError(t, f.SetFlag("helm-api-versions", "v2"))
assert.Equal(t, []string{"v1", "v2"}, f.spec.Source.Helm.APIVersions)
})
t.Run("source hydrator", func(t *testing.T) {
require.NoError(t, f.SetFlag("dry-source-repo", "https://github.com/argoproj/argocd-example-apps"))
assert.Equal(t, "https://github.com/argoproj/argocd-example-apps", f.spec.SourceHydrator.DrySource.RepoURL)
require.NoError(t, f.SetFlag("dry-source-path", "apps"))
assert.Equal(t, "apps", f.spec.SourceHydrator.DrySource.Path)
require.NoError(t, f.SetFlag("dry-source-revision", "HEAD"))
assert.Equal(t, "HEAD", f.spec.SourceHydrator.DrySource.TargetRevision)
require.NoError(t, f.SetFlag("sync-source-branch", "env/test"))
assert.Equal(t, "env/test", f.spec.SourceHydrator.SyncSource.TargetBranch)
require.NoError(t, f.SetFlag("sync-source-path", "apps"))
assert.Equal(t, "apps", f.spec.SourceHydrator.SyncSource.Path)
require.NoError(t, f.SetFlag("hydrate-to-branch", "env/test-next"))
assert.Equal(t, "env/test-next", f.spec.SourceHydrator.HydrateTo.TargetBranch)
require.NoError(t, f.SetFlag("hydrate-to-branch", ""))
assert.Nil(t, f.spec.SourceHydrator.HydrateTo)
})
}
func newMultiSourceAppOptionsFixture() *appOptionsFixture {

View File

@@ -0,0 +1,47 @@
package apiclient
import (
"fmt"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"github.com/argoproj/argo-cd/v2/util/io"
)
// Clientset represents commit server api clients
type Clientset interface {
NewCommitServerClient() (io.Closer, CommitServiceClient, error)
}
type clientSet struct {
address string
}
// NewCommitServerClient creates new instance of commit server client
func (c *clientSet) NewCommitServerClient() (io.Closer, CommitServiceClient, error) {
conn, err := NewConnection(c.address)
if err != nil {
return nil, nil, fmt.Errorf("failed to open a new connection to commit server: %w", err)
}
return conn, NewCommitServiceClient(conn), nil
}
// NewConnection creates new connection to commit server
func NewConnection(address string) (*grpc.ClientConn, error) {
var opts []grpc.DialOption
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
conn, err := grpc.Dial(address, opts...)
if err != nil {
log.Errorf("Unable to connect to commit service with address %s", address)
return nil, err
}
return conn, nil
}
// NewCommitServerClientset creates new instance of commit server Clientset
func NewCommitServerClientset(address string) Clientset {
return &clientSet{address: address}
}

1382
commitserver/apiclient/commit.pb.go generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,68 @@
// Code generated by mockery v2.43.2. DO NOT EDIT.
package mocks
import (
apiclient "github.com/argoproj/argo-cd/v2/commitserver/apiclient"
io "github.com/argoproj/argo-cd/v2/util/io"
mock "github.com/stretchr/testify/mock"
)
// Clientset is an autogenerated mock type for the Clientset type
type Clientset struct {
mock.Mock
}
// NewCommitServerClient provides a mock function with given fields:
func (_m *Clientset) NewCommitServerClient() (io.Closer, apiclient.CommitServiceClient, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for NewCommitServerClient")
}
var r0 io.Closer
var r1 apiclient.CommitServiceClient
var r2 error
if rf, ok := ret.Get(0).(func() (io.Closer, apiclient.CommitServiceClient, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() io.Closer); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(io.Closer)
}
}
if rf, ok := ret.Get(1).(func() apiclient.CommitServiceClient); ok {
r1 = rf()
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(apiclient.CommitServiceClient)
}
}
if rf, ok := ret.Get(2).(func() error); ok {
r2 = rf()
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// NewClientset creates a new instance of Clientset. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewClientset(t interface {
mock.TestingT
Cleanup(func())
}) *Clientset {
mock := &Clientset{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@@ -0,0 +1,69 @@
// Code generated by mockery v2.43.2. DO NOT EDIT.
package mocks
import (
context "context"
apiclient "github.com/argoproj/argo-cd/v2/commitserver/apiclient"
grpc "google.golang.org/grpc"
mock "github.com/stretchr/testify/mock"
)
// CommitServiceClient is an autogenerated mock type for the CommitServiceClient type
type CommitServiceClient struct {
mock.Mock
}
// CommitHydratedManifests provides a mock function with given fields: ctx, in, opts
func (_m *CommitServiceClient) CommitHydratedManifests(ctx context.Context, in *apiclient.CommitHydratedManifestsRequest, opts ...grpc.CallOption) (*apiclient.CommitHydratedManifestsResponse, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for CommitHydratedManifests")
}
var r0 *apiclient.CommitHydratedManifestsResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *apiclient.CommitHydratedManifestsRequest, ...grpc.CallOption) (*apiclient.CommitHydratedManifestsResponse, error)); ok {
return rf(ctx, in, opts...)
}
if rf, ok := ret.Get(0).(func(context.Context, *apiclient.CommitHydratedManifestsRequest, ...grpc.CallOption) *apiclient.CommitHydratedManifestsResponse); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*apiclient.CommitHydratedManifestsResponse)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *apiclient.CommitHydratedManifestsRequest, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewCommitServiceClient creates a new instance of CommitServiceClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewCommitServiceClient(t interface {
mock.TestingT
Cleanup(func())
}) *CommitServiceClient {
mock := &CommitServiceClient{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@@ -0,0 +1,218 @@
package commit
import (
"context"
"fmt"
"os"
"time"
log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v2/commitserver/apiclient"
"github.com/argoproj/argo-cd/v2/commitserver/metrics"
"github.com/argoproj/argo-cd/v2/util/git"
"github.com/argoproj/argo-cd/v2/util/io/files"
)
// Service is the service that handles commit requests.
type Service struct {
gitCredsStore git.CredsStore
metricsServer *metrics.Server
repoClientFactory RepoClientFactory
}
// NewService returns a new instance of the commit service.
func NewService(gitCredsStore git.CredsStore, metricsServer *metrics.Server) *Service {
return &Service{
gitCredsStore: gitCredsStore,
metricsServer: metricsServer,
repoClientFactory: NewRepoClientFactory(gitCredsStore, metricsServer),
}
}
// CommitHydratedManifests handles a commit request. It clones the repository, checks out the sync branch, checks out
// the target branch, clears the repository contents, writes the manifests to the repository, commits the changes, and
// pushes the changes. It returns the hydrated revision SHA and an error if one occurred.
func (s *Service) CommitHydratedManifests(ctx context.Context, r *apiclient.CommitHydratedManifestsRequest) (*apiclient.CommitHydratedManifestsResponse, error) {
// This method is intentionally short. It's a wrapper around handleCommitRequest that adds metrics and logging.
// Keep logic here minimal and put most of the logic in handleCommitRequest.
startTime := time.Now()
// We validate for a nil repo in handleCommitRequest, but we need to check for a nil repo here to get the repo URL
// for metrics.
var repoURL string
if r.Repo != nil {
repoURL = r.Repo.Repo
}
s.metricsServer.IncPendingCommitRequest(repoURL)
defer s.metricsServer.DecPendingCommitRequest(repoURL)
logCtx := log.WithFields(log.Fields{"branch": r.TargetBranch, "drySHA": r.DrySha})
out, sha, err := s.handleCommitRequest(ctx, logCtx, r)
if err != nil {
logCtx.WithError(err).WithField("output", out).Error("failed to handle commit request")
s.metricsServer.IncCommitRequest(repoURL, metrics.CommitResponseTypeFailure)
s.metricsServer.ObserveCommitRequestDuration(repoURL, metrics.CommitResponseTypeFailure, time.Since(startTime))
// No need to wrap this error, sufficient context is build in handleCommitRequest.
return &apiclient.CommitHydratedManifestsResponse{}, err
}
logCtx.Info("Successfully handled commit request")
s.metricsServer.IncCommitRequest(repoURL, metrics.CommitResponseTypeSuccess)
s.metricsServer.ObserveCommitRequestDuration(repoURL, metrics.CommitResponseTypeSuccess, time.Since(startTime))
return &apiclient.CommitHydratedManifestsResponse{
HydratedSha: sha,
}, nil
}
// handleCommitRequest handles the commit request. It clones the repository, checks out the sync branch, checks out the
// target branch, clears the repository contents, writes the manifests to the repository, commits the changes, and pushes
// the changes. It returns the output of the git commands and an error if one occurred.
func (s *Service) handleCommitRequest(ctx context.Context, logCtx *log.Entry, r *apiclient.CommitHydratedManifestsRequest) (string, string, error) {
if r.Repo == nil {
return "", "", fmt.Errorf("repo is required")
}
if r.Repo.Repo == "" {
return "", "", fmt.Errorf("repo URL is required")
}
if r.TargetBranch == "" {
return "", "", fmt.Errorf("target branch is required")
}
if r.SyncBranch == "" {
return "", "", fmt.Errorf("sync branch is required")
}
logCtx = logCtx.WithField("repo", r.Repo.Repo)
logCtx.Debug("Initiating git client")
gitClient, dirPath, cleanup, err := s.initGitClient(ctx, logCtx, r)
if err != nil {
return "", "", fmt.Errorf("failed to init git client: %w", err)
}
defer cleanup()
logCtx.Debugf("Checking out sync branch %s", r.SyncBranch)
var out string
out, err = gitClient.CheckoutOrOrphan(r.SyncBranch, false)
if err != nil {
return out, "", fmt.Errorf("failed to checkout sync branch: %w", err)
}
logCtx.Debugf("Checking out target branch %s", r.TargetBranch)
out, err = gitClient.CheckoutOrNew(r.TargetBranch, r.SyncBranch, false)
if err != nil {
return out, "", fmt.Errorf("failed to checkout target branch: %w", err)
}
logCtx.Debug("Clearing repo contents")
out, err = gitClient.RemoveContents()
if err != nil {
return out, "", fmt.Errorf("failed to clear repo: %w", err)
}
logCtx.Debug("Writing manifests")
err = WriteForPaths(dirPath, r.Repo.Repo, r.DrySha, r.Paths)
if err != nil {
return "", "", fmt.Errorf("failed to write manifests: %w", err)
}
logCtx.Debug("Committing and pushing changes")
out, err = gitClient.CommitAndPush(r.TargetBranch, r.CommitMessage)
if err != nil {
return out, "", fmt.Errorf("failed to commit and push: %w", err)
}
logCtx.Debug("Getting commit SHA")
sha, err := gitClient.CommitSHA()
if err != nil {
return "", "", fmt.Errorf("failed to get commit SHA: %w", err)
}
return "", sha, nil
}
// initGitClient initializes a git client for the given repository and returns the client, the path to the directory where
// the repository is cloned, a cleanup function that should be called when the directory is no longer needed, and an error
// if one occurred.
func (s *Service) initGitClient(ctx context.Context, logCtx *log.Entry, r *apiclient.CommitHydratedManifestsRequest) (git.Client, string, func(), error) {
dirPath, err := files.CreateTempDir("/tmp/_commit-service")
if err != nil {
return nil, "", nil, fmt.Errorf("failed to create temp dir: %w", err)
}
// Call cleanupOrLog in this function if an error occurs to ensure the temp dir is cleaned up.
cleanupOrLog := func() {
err := os.RemoveAll(dirPath)
if err != nil {
logCtx.WithError(err).Error("failed to cleanup temp dir")
}
}
gitClient, err := s.repoClientFactory.NewClient(r.Repo, dirPath)
if err != nil {
cleanupOrLog()
return nil, "", nil, fmt.Errorf("failed to create git client: %w", err)
}
logCtx.Debugf("Initializing repo %s", r.Repo.Repo)
err = gitClient.Init()
if err != nil {
cleanupOrLog()
return nil, "", nil, fmt.Errorf("failed to init git client: %w", err)
}
logCtx.Debugf("Fetching repo %s", r.Repo.Repo)
err = gitClient.Fetch("")
if err != nil {
cleanupOrLog()
return nil, "", nil, fmt.Errorf("failed to clone repo: %w", err)
}
logCtx.Debugf("Getting user info for repo credentials")
gitCreds := r.Repo.GetGitCreds(s.gitCredsStore)
startTime := time.Now()
authorName, authorEmail, err := gitCreds.GetUserInfo(ctx)
s.metricsServer.ObserveUserInfoRequestDuration(r.Repo.Repo, getCredentialType(r.Repo), time.Since(startTime))
if err != nil {
cleanupOrLog()
return nil, "", nil, fmt.Errorf("failed to get github app info: %w", err)
}
if authorName == "" {
authorName = "Argo CD"
}
if authorEmail == "" {
logCtx.Warnf("Author email not available, using 'argo-cd@example.com'.")
authorEmail = "argo-cd@example.com"
}
logCtx.Debugf("Setting author %s <%s>", authorName, authorEmail)
_, err = gitClient.SetAuthor(authorName, authorEmail)
if err != nil {
cleanupOrLog()
return nil, "", nil, fmt.Errorf("failed to set author: %w", err)
}
return gitClient, dirPath, cleanupOrLog, nil
}
type hydratorMetadataFile struct {
RepoURL string `json:"repoURL"`
DrySHA string `json:"drySha"`
Commands []string `json:"commands"`
}
// TODO: make this configurable via ConfigMap.
var manifestHydrationReadmeTemplate = `
# Manifest Hydration
To hydrate the manifests in this repository, run the following commands:
` + "```shell\n" + `
git clone {{ .RepoURL }}
# cd into the cloned directory
git checkout {{ .DrySHA }}
{{ range $command := .Commands -}}
{{ $command }}
{{ end -}}` + "```"

View File

@@ -0,0 +1,50 @@
syntax = "proto3";
option go_package = "github.com/argoproj/argo-cd/v2/commitserver/apiclient";
import "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1/generated.proto";
// CommitHydratedManifestsRequest is the request to commit hydrated manifests to a repository.
message CommitHydratedManifestsRequest {
// Repo contains repository information including, at minimum, the URL of the repository. Generally it will contain
// repo credentials.
github.com.argoproj.argo_cd.v2.pkg.apis.application.v1alpha1.Repository repo = 1;
// SyncBranch is the branch Argo CD syncs from, i.e. the hydrated branch.
string syncBranch = 2;
// TargetBranch is the branch Argo CD is committing to, i.e. the branch that will be updated.
string targetBranch = 3;
// DrySha is the commit SHA from the dry branch, i.e. pre-rendered manifest branch.
string drySha = 4;
// CommitMessage is the commit message to use when committing changes.
string commitMessage = 5;
// Paths contains the paths to write hydrated manifests to, along with the manifests and commands to execute.
repeated PathDetails paths = 6;
}
// PathDetails holds information about hydrated manifests to be written to a particular path in the hydrated manifests
// commit.
message PathDetails {
// Path is the path to write the hydrated manifests to.
string path = 1;
// Manifests contains the manifests to write to the path.
repeated HydratedManifestDetails manifests = 2;
// Commands contains the commands executed when hydrating the manifests.
repeated string commands = 3;
}
// ManifestDetails contains the hydrated manifests.
message HydratedManifestDetails {
// ManifestJSON is the hydrated manifest as JSON.
string manifestJSON = 1;
}
// ManifestsResponse is the response to the ManifestsRequest.
message CommitHydratedManifestsResponse {
// HydratedSha is the commit SHA of the hydrated manifests commit.
string hydratedSha = 1;
}
// CommitService is the service for committing hydrated manifests to a repository.
service CommitService {
// Commit commits hydrated manifests to a repository.
rpc CommitHydratedManifests (CommitHydratedManifestsRequest) returns (CommitHydratedManifestsResponse);
}

View File

@@ -0,0 +1,123 @@
package commit
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/argoproj/argo-cd/v2/commitserver/apiclient"
"github.com/argoproj/argo-cd/v2/commitserver/commit/mocks"
"github.com/argoproj/argo-cd/v2/commitserver/metrics"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/git"
gitmocks "github.com/argoproj/argo-cd/v2/util/git/mocks"
)
func Test_CommitHydratedManifests(t *testing.T) {
t.Parallel()
validRequest := &apiclient.CommitHydratedManifestsRequest{
Repo: &v1alpha1.Repository{
Repo: "https://github.com/argoproj/argocd-example-apps.git",
},
TargetBranch: "main",
SyncBranch: "env/test",
CommitMessage: "test commit message",
}
t.Run("missing repo", func(t *testing.T) {
t.Parallel()
service, _ := newServiceWithMocks(t)
request := &apiclient.CommitHydratedManifestsRequest{}
_, err := service.CommitHydratedManifests(context.Background(), request)
require.Error(t, err)
assert.ErrorContains(t, err, "repo is required")
})
t.Run("missing repo URL", func(t *testing.T) {
t.Parallel()
service, _ := newServiceWithMocks(t)
request := &apiclient.CommitHydratedManifestsRequest{
Repo: &v1alpha1.Repository{},
}
_, err := service.CommitHydratedManifests(context.Background(), request)
require.Error(t, err)
assert.ErrorContains(t, err, "repo URL is required")
})
t.Run("missing target branch", func(t *testing.T) {
t.Parallel()
service, _ := newServiceWithMocks(t)
request := &apiclient.CommitHydratedManifestsRequest{
Repo: &v1alpha1.Repository{
Repo: "https://github.com/argoproj/argocd-example-apps.git",
},
}
_, err := service.CommitHydratedManifests(context.Background(), request)
require.Error(t, err)
assert.ErrorContains(t, err, "target branch is required")
})
t.Run("missing sync branch", func(t *testing.T) {
t.Parallel()
service, _ := newServiceWithMocks(t)
request := &apiclient.CommitHydratedManifestsRequest{
Repo: &v1alpha1.Repository{
Repo: "https://github.com/argoproj/argocd-example-apps.git",
},
TargetBranch: "main",
}
_, err := service.CommitHydratedManifests(context.Background(), request)
require.Error(t, err)
assert.ErrorContains(t, err, "sync branch is required")
})
t.Run("failed to create git client", func(t *testing.T) {
t.Parallel()
service, mockRepoClientFactory := newServiceWithMocks(t)
mockRepoClientFactory.On("NewClient", mock.Anything, mock.Anything).Return(nil, assert.AnError).Once()
_, err := service.CommitHydratedManifests(context.Background(), validRequest)
require.Error(t, err)
assert.ErrorIs(t, err, assert.AnError)
})
t.Run("happy path", func(t *testing.T) {
t.Parallel()
service, mockRepoClientFactory := newServiceWithMocks(t)
mockGitClient := gitmocks.NewClient(t)
mockGitClient.On("Init").Return(nil).Once()
mockGitClient.On("Fetch", mock.Anything).Return(nil).Once()
mockGitClient.On("SetAuthor", "Argo CD", "argo-cd@example.com").Return("", nil).Once()
mockGitClient.On("CheckoutOrOrphan", "env/test", false).Return("", nil).Once()
mockGitClient.On("CheckoutOrNew", "main", "env/test", false).Return("", nil).Once()
mockGitClient.On("RemoveContents").Return("", nil).Once()
mockGitClient.On("CommitAndPush", "main", "test commit message").Return("", nil).Once()
mockGitClient.On("CommitSHA").Return("it-worked!", nil).Once()
mockRepoClientFactory.On("NewClient", mock.Anything, mock.Anything).Return(mockGitClient, nil).Once()
resp, err := service.CommitHydratedManifests(context.Background(), validRequest)
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, "it-worked!", resp.HydratedSha)
})
}
func newServiceWithMocks(t *testing.T) (*Service, *mocks.RepoClientFactory) {
metricsServer := metrics.NewMetricsServer()
mockCredsStore := git.NoopCredsStore{}
service := NewService(mockCredsStore, metricsServer)
mockRepoClientFactory := mocks.NewRepoClientFactory(t)
service.repoClientFactory = mockRepoClientFactory
return service, mockRepoClientFactory
}

View File

@@ -0,0 +1,23 @@
package commit
import "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
// getCredentialType returns the type of credential used by the repository.
func getCredentialType(repo *v1alpha1.Repository) string {
if repo == nil {
return ""
}
if repo.Password != "" {
return "https"
}
if repo.SSHPrivateKey != "" {
return "ssh"
}
if repo.GithubAppPrivateKey != "" && repo.GithubAppId != 0 && repo.GithubAppInstallationId != 0 {
return "github-app"
}
if repo.GCPServiceAccountKey != "" {
return "cloud-source-repositories"
}
return ""
}

View File

@@ -0,0 +1,62 @@
package commit
import (
"testing"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func TestRepository_GetCredentialType(t *testing.T) {
tests := []struct {
name string
repo *v1alpha1.Repository
want string
}{
{
name: "Empty Repository",
repo: nil,
want: "",
},
{
name: "HTTPS Repository",
repo: &v1alpha1.Repository{
Repo: "foo",
Password: "some-password",
},
want: "https",
},
{
name: "SSH Repository",
repo: &v1alpha1.Repository{
Repo: "foo",
SSHPrivateKey: "some-key",
},
want: "ssh",
},
{
name: "GitHub App Repository",
repo: &v1alpha1.Repository{
Repo: "foo",
GithubAppPrivateKey: "some-key",
GithubAppId: 1,
GithubAppInstallationId: 1,
},
want: "github-app",
},
{
name: "Google Cloud Repository",
repo: &v1alpha1.Repository{
Repo: "foo",
GCPServiceAccountKey: "some-key",
},
want: "cloud-source-repositories",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := getCredentialType(tt.repo); got != tt.want {
t.Errorf("Repository.GetCredentialType() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -0,0 +1,145 @@
package commit
import (
"encoding/json"
"fmt"
"os"
"path"
"text/template"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/argoproj/argo-cd/v2/commitserver/apiclient"
"github.com/argoproj/argo-cd/v2/util/io/files"
)
// WriteForPaths writes the manifests, hydrator.metadata, and README.md files for each path in the provided paths. It
// also writes a root-level hydrator.metadata file containing the repo URL and dry SHA.
func WriteForPaths(rootPath string, repoUrl string, drySha string, paths []*apiclient.PathDetails) error {
// Write the top-level readme.
err := writeMetadata(rootPath, hydratorMetadataFile{DrySHA: drySha, RepoURL: repoUrl})
if err != nil {
return fmt.Errorf("failed to write top-level hydrator metadata: %w", err)
}
for _, p := range paths {
hydratePath := p.Path
if hydratePath == "." {
hydratePath = ""
}
var fullHydratePath string
fullHydratePath, err = files.SecureMkdirAll(rootPath, hydratePath, os.ModePerm)
if err != nil {
return fmt.Errorf("failed to create path: %w", err)
}
// Write the manifests
err = writeManifests(fullHydratePath, p.Manifests)
if err != nil {
return fmt.Errorf("failed to write manifests: %w", err)
}
// Write hydrator.metadata containing information about the hydration process.
hydratorMetadata := hydratorMetadataFile{
Commands: p.Commands,
DrySHA: drySha,
RepoURL: repoUrl,
}
err = writeMetadata(fullHydratePath, hydratorMetadata)
if err != nil {
return fmt.Errorf("failed to write hydrator metadata: %w", err)
}
// Write README
err = writeReadme(fullHydratePath, hydratorMetadata)
if err != nil {
return fmt.Errorf("failed to write readme: %w", err)
}
}
return nil
}
// writeMetadata writes the metadata to the hydrator.metadata file.
func writeMetadata(dirPath string, metadata hydratorMetadataFile) error {
hydratorMetadataJson, err := json.MarshalIndent(metadata, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal hydrator metadata: %w", err)
}
// No need to use SecureJoin here, as the path is already sanitized.
hydratorMetadataPath := path.Join(dirPath, "hydrator.metadata")
err = os.WriteFile(hydratorMetadataPath, hydratorMetadataJson, os.ModePerm)
if err != nil {
return fmt.Errorf("failed to write hydrator metadata: %w", err)
}
return nil
}
// writeReadme writes the readme to the README.md file.
func writeReadme(dirPath string, metadata hydratorMetadataFile) error {
readmeTemplate := template.New("readme")
readmeTemplate, err := readmeTemplate.Parse(manifestHydrationReadmeTemplate)
if err != nil {
return fmt.Errorf("failed to parse readme template: %w", err)
}
// Create writer to template into
// No need to use SecureJoin here, as the path is already sanitized.
readmePath := path.Join(dirPath, "README.md")
readmeFile, err := os.Create(readmePath)
if err != nil && !os.IsExist(err) {
return fmt.Errorf("failed to create README file: %w", err)
}
err = readmeTemplate.Execute(readmeFile, metadata)
closeErr := readmeFile.Close()
if closeErr != nil {
log.WithError(closeErr).Error("failed to close README file")
}
if err != nil {
return fmt.Errorf("failed to execute readme template: %w", err)
}
return nil
}
// writeManifests writes the manifests to the manifest.yaml file, truncating the file if it exists and appending the
// manifests in the order they are provided.
func writeManifests(dirPath string, manifests []*apiclient.HydratedManifestDetails) error {
// If the file exists, truncate it.
// No need to use SecureJoin here, as the path is already sanitized.
manifestPath := path.Join(dirPath, "manifest.yaml")
file, err := os.OpenFile(manifestPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm)
if err != nil {
return fmt.Errorf("failed to open manifest file: %w", err)
}
defer func() {
err := file.Close()
if err != nil {
log.WithError(err).Error("failed to close file")
}
}()
enc := yaml.NewEncoder(file)
defer func() {
err := enc.Close()
if err != nil {
log.WithError(err).Error("failed to close yaml encoder")
}
}()
enc.SetIndent(2)
for _, m := range manifests {
obj := &unstructured.Unstructured{}
err = json.Unmarshal([]byte(m.ManifestJSON), obj)
if err != nil {
return fmt.Errorf("failed to unmarshal manifest: %w", err)
}
err = enc.Encode(&obj.Object)
if err != nil {
return fmt.Errorf("failed to encode manifest: %w", err)
}
}
return nil
}

View File

@@ -0,0 +1,154 @@
package commit
import (
"encoding/json"
"os"
"path"
"testing"
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/argoproj/argo-cd/v2/commitserver/apiclient"
)
func TestWriteForPaths(t *testing.T) {
dir := t.TempDir()
repoUrl := "https://github.com/example/repo"
drySha := "abc123"
paths := []*apiclient.PathDetails{
{
Path: "path1",
Manifests: []*apiclient.HydratedManifestDetails{
{ManifestJSON: `{"kind":"Pod","apiVersion":"v1"}`},
},
Commands: []string{"command1", "command2"},
},
{
Path: "path2",
Manifests: []*apiclient.HydratedManifestDetails{
{ManifestJSON: `{"kind":"Service","apiVersion":"v1"}`},
},
Commands: []string{"command3"},
},
}
err := WriteForPaths(dir, repoUrl, drySha, paths)
require.NoError(t, err)
// Check if the top-level hydrator.metadata exists and contains the repo URL and dry SHA
topMetadataPath := path.Join(dir, "hydrator.metadata")
topMetadataBytes, err := os.ReadFile(topMetadataPath)
require.NoError(t, err)
var topMetadata hydratorMetadataFile
err = json.Unmarshal(topMetadataBytes, &topMetadata)
require.NoError(t, err)
assert.Equal(t, repoUrl, topMetadata.RepoURL)
assert.Equal(t, drySha, topMetadata.DrySHA)
for _, p := range paths {
fullHydratePath, err := securejoin.SecureJoin(dir, p.Path)
require.NoError(t, err)
// Check if each path directory exists
assert.DirExists(t, fullHydratePath)
// Check if each path contains a hydrator.metadata file and contains the repo URL
metadataPath := path.Join(fullHydratePath, "hydrator.metadata")
metadataBytes, err := os.ReadFile(metadataPath)
require.NoError(t, err)
var readMetadata hydratorMetadataFile
err = json.Unmarshal(metadataBytes, &readMetadata)
require.NoError(t, err)
assert.Equal(t, repoUrl, readMetadata.RepoURL)
// Check if each path contains a README.md file and contains the repo URL
readmePath := path.Join(fullHydratePath, "README.md")
readmeBytes, err := os.ReadFile(readmePath)
require.NoError(t, err)
assert.Contains(t, string(readmeBytes), repoUrl)
// Check if each path contains a manifest.yaml file and contains the word Pod
manifestPath := path.Join(fullHydratePath, "manifest.yaml")
manifestBytes, err := os.ReadFile(manifestPath)
require.NoError(t, err)
assert.Contains(t, string(manifestBytes), "kind")
}
}
func TestWriteForPaths_invalid_yaml(t *testing.T) {
dir := t.TempDir()
repoUrl := "https://github.com/example/repo"
drySha := "abc123"
paths := []*apiclient.PathDetails{
{
Path: "path1",
Manifests: []*apiclient.HydratedManifestDetails{
{ManifestJSON: `{`}, // Invalid YAML
},
Commands: []string{"command1", "command2"},
},
}
err := WriteForPaths(dir, repoUrl, drySha, paths)
require.Error(t, err)
}
func TestWriteMetadata(t *testing.T) {
dir := t.TempDir()
metadata := hydratorMetadataFile{
RepoURL: "https://github.com/example/repo",
DrySHA: "abc123",
}
err := writeMetadata(dir, metadata)
require.NoError(t, err)
metadataPath := path.Join(dir, "hydrator.metadata")
metadataBytes, err := os.ReadFile(metadataPath)
require.NoError(t, err)
var readMetadata hydratorMetadataFile
err = json.Unmarshal(metadataBytes, &readMetadata)
require.NoError(t, err)
assert.Equal(t, metadata, readMetadata)
}
func TestWriteReadme(t *testing.T) {
dir := t.TempDir()
metadata := hydratorMetadataFile{
RepoURL: "https://github.com/example/repo",
DrySHA: "abc123",
}
err := writeReadme(dir, metadata)
require.NoError(t, err)
readmePath := path.Join(dir, "README.md")
readmeBytes, err := os.ReadFile(readmePath)
require.NoError(t, err)
assert.Contains(t, string(readmeBytes), metadata.RepoURL)
}
func TestWriteManifests(t *testing.T) {
dir := t.TempDir()
manifests := []*apiclient.HydratedManifestDetails{
{ManifestJSON: `{"kind":"Pod","apiVersion":"v1"}`},
}
err := writeManifests(dir, manifests)
require.NoError(t, err)
manifestPath := path.Join(dir, "manifest.yaml")
manifestBytes, err := os.ReadFile(manifestPath)
require.NoError(t, err)
assert.Contains(t, string(manifestBytes), "kind")
}

View File

@@ -0,0 +1,59 @@
// Code generated by mockery v2.43.2. DO NOT EDIT.
package mocks
import (
git "github.com/argoproj/argo-cd/v2/util/git"
mock "github.com/stretchr/testify/mock"
v1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
// RepoClientFactory is an autogenerated mock type for the RepoClientFactory type
type RepoClientFactory struct {
mock.Mock
}
// NewClient provides a mock function with given fields: repo, rootPath
func (_m *RepoClientFactory) NewClient(repo *v1alpha1.Repository, rootPath string) (git.Client, error) {
ret := _m.Called(repo, rootPath)
if len(ret) == 0 {
panic("no return value specified for NewClient")
}
var r0 git.Client
var r1 error
if rf, ok := ret.Get(0).(func(*v1alpha1.Repository, string) (git.Client, error)); ok {
return rf(repo, rootPath)
}
if rf, ok := ret.Get(0).(func(*v1alpha1.Repository, string) git.Client); ok {
r0 = rf(repo, rootPath)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(git.Client)
}
}
if rf, ok := ret.Get(1).(func(*v1alpha1.Repository, string) error); ok {
r1 = rf(repo, rootPath)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewRepoClientFactory creates a new instance of RepoClientFactory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewRepoClientFactory(t interface {
mock.TestingT
Cleanup(func())
}) *RepoClientFactory {
mock := &RepoClientFactory{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@@ -0,0 +1,32 @@
package commit
import (
"github.com/argoproj/argo-cd/v2/commitserver/metrics"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/git"
)
// RepoClientFactory is a factory for creating git clients for a repository.
type RepoClientFactory interface {
NewClient(repo *v1alpha1.Repository, rootPath string) (git.Client, error)
}
type repoClientFactory struct {
gitCredsStore git.CredsStore
metricsServer *metrics.Server
}
// NewRepoClientFactory returns a new instance of the repo client factory.
func NewRepoClientFactory(gitCredsStore git.CredsStore, metricsServer *metrics.Server) RepoClientFactory {
return &repoClientFactory{
gitCredsStore: gitCredsStore,
metricsServer: metricsServer,
}
}
// NewClient creates a new git client for the repository.
func (r *repoClientFactory) NewClient(repo *v1alpha1.Repository, rootPath string) (git.Client, error) {
gitCreds := repo.GetGitCreds(r.gitCredsStore)
opts := git.WithEventHandlers(metrics.NewGitClientEventHandlers(r.metricsServer))
return git.NewClientExt(repo.Repo, rootPath, gitCreds, repo.IsInsecure(), repo.IsLFSEnabled(), repo.Proxy, opts)
}

View File

@@ -0,0 +1,22 @@
//go:build !linux
package commit
import (
"fmt"
"os"
securejoin "github.com/cyphar/filepath-securejoin"
)
func SecureMkdirAll(root, unsafePath string, mode os.FileMode) (string, error) {
fullPath, err := securejoin.SecureJoin(root, unsafePath)
if err != nil {
return "", fmt.Errorf("failed to construct secure path: %w", err)
}
err = os.MkdirAll(fullPath, mode)
if err != nil {
return "", fmt.Errorf("failed to create directory: %w", err)
}
return fullPath, nil
}

View File

@@ -0,0 +1,69 @@
//go:build !linux
package commit
import (
"os"
"path"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSecureMkdirAllDefault(t *testing.T) {
root := t.TempDir()
unsafePath := "test/dir"
fullPath, err := SecureMkdirAll(root, unsafePath, os.ModePerm)
require.NoError(t, err)
expectedPath := path.Join(root, unsafePath)
assert.Equal(t, expectedPath, fullPath)
}
func TestSecureMkdirAllWithExistingDir(t *testing.T) {
root := t.TempDir()
unsafePath := "existing/dir"
fullPath, err := SecureMkdirAll(root, unsafePath, os.ModePerm)
require.NoError(t, err)
newPath, err := SecureMkdirAll(root, unsafePath, os.ModePerm)
require.NoError(t, err)
assert.Equal(t, fullPath, newPath)
}
func TestSecureMkdirAllWithFile(t *testing.T) {
root := t.TempDir()
unsafePath := "file.txt"
filePath := filepath.Join(root, unsafePath)
err := os.WriteFile(filePath, []byte("test"), os.ModePerm)
require.NoError(t, err)
_, err = SecureMkdirAll(root, unsafePath, os.ModePerm)
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to create directory")
}
func TestSecureMkdirAllDotDotPath(t *testing.T) {
root := t.TempDir()
unsafePath := "../outside"
fullPath, err := SecureMkdirAll(root, unsafePath, os.ModePerm)
require.NoError(t, err)
expectedPath := filepath.Join(root, "outside")
assert.Equal(t, expectedPath, fullPath)
info, err := os.Stat(fullPath)
require.NoError(t, err)
assert.True(t, info.IsDir())
relPath, err := filepath.Rel(root, fullPath)
require.NoError(t, err)
assert.False(t, strings.HasPrefix(relPath, ".."))
}

View File

@@ -0,0 +1,22 @@
//go:build linux
package commit
import (
"fmt"
"os"
securejoin "github.com/cyphar/filepath-securejoin"
)
func SecureMkdirAll(root, unsafePath string, mode os.FileMode) (string, error) {
err := securejoin.MkdirAll(root, unsafePath, int(mode))
if err != nil {
return "", fmt.Errorf("failed to make directory: %w", err)
}
fullPath, err := securejoin.SecureJoin(root, unsafePath)
if err != nil {
return "", fmt.Errorf("failed to construct secure path: %w", err)
}
return fullPath, nil
}

View File

@@ -0,0 +1,22 @@
//go:build linux
package commit
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
func TestSecureMkdirAllLinux(t *testing.T) {
root := t.TempDir()
unsafePath := "test/dir"
fullPath, err := SecureMkdirAll(root, unsafePath, os.ModePerm)
require.NoError(t, err)
expectedPath := filepath.Join(root, unsafePath)
require.Equal(t, expectedPath, fullPath)
}

View File

@@ -0,0 +1,34 @@
package metrics
import (
"time"
"github.com/argoproj/argo-cd/v2/util/git"
)
// NewGitClientEventHandlers creates event handlers that update Git related metrics
func NewGitClientEventHandlers(metricsServer *Server) git.EventHandlers {
return git.EventHandlers{
OnFetch: func(repo string) func() {
startTime := time.Now()
metricsServer.IncGitRequest(repo, GitRequestTypeFetch)
return func() {
metricsServer.ObserveGitRequestDuration(repo, GitRequestTypeFetch, time.Since(startTime))
}
},
OnLsRemote: func(repo string) func() {
startTime := time.Now()
metricsServer.IncGitRequest(repo, GitRequestTypeLsRemote)
return func() {
metricsServer.ObserveGitRequestDuration(repo, GitRequestTypeLsRemote, time.Since(startTime))
}
},
OnPush: func(repo string) func() {
startTime := time.Now()
metricsServer.IncGitRequest(repo, GitRequestTypePush)
return func() {
metricsServer.ObserveGitRequestDuration(repo, GitRequestTypePush, time.Since(startTime))
}
},
}
}

View File

@@ -0,0 +1,157 @@
package metrics
import (
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// Server is a prometheus server which collects application metrics.
type Server struct {
handler http.Handler
commitPendingRequestsGauge *prometheus.GaugeVec
gitRequestCounter *prometheus.CounterVec
gitRequestHistogram *prometheus.HistogramVec
commitRequestHistogram *prometheus.HistogramVec
userInfoRequestHistogram *prometheus.HistogramVec
commitRequestCounter *prometheus.CounterVec
}
// GitRequestType is the type of git request
type GitRequestType string
const (
// GitRequestTypeLsRemote is a request to list remote refs
GitRequestTypeLsRemote = "ls-remote"
// GitRequestTypeFetch is a request to fetch from remote
GitRequestTypeFetch = "fetch"
// GitRequestTypePush is a request to push to remote
GitRequestTypePush = "push"
)
// CommitResponseType is the type of response for a commit request
type CommitResponseType string
const (
// CommitResponseTypeSuccess is a successful commit request
CommitResponseTypeSuccess = "success"
// CommitResponseTypeFailure is a failed commit request
CommitResponseTypeFailure = "failure"
)
// NewMetricsServer returns a new prometheus server which collects application metrics.
func NewMetricsServer() *Server {
registry := prometheus.NewRegistry()
registry.MustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))
registry.MustRegister(collectors.NewGoCollector())
commitPendingRequestsGauge := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "argocd_commitserver_commit_pending_request_total",
Help: "Number of pending commit requests",
},
[]string{"repo"},
)
registry.MustRegister(commitPendingRequestsGauge)
gitRequestCounter := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "argocd_commitserver_git_request_total",
Help: "Number of git requests performed by repo server",
},
[]string{"repo", "request_type"},
)
registry.MustRegister(gitRequestCounter)
gitRequestHistogram := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "argocd_commitserver_git_request_duration_seconds",
Help: "Git requests duration seconds.",
Buckets: []float64{0.1, 0.25, .5, 1, 2, 4, 10, 20},
},
[]string{"repo", "request_type"},
)
registry.MustRegister(gitRequestHistogram)
commitRequestHistogram := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "argocd_commitserver_commit_request_duration_seconds",
Help: "Commit request duration seconds.",
Buckets: []float64{0.1, 0.25, .5, 1, 2, 4, 10, 20},
},
[]string{"repo", "response_type"},
)
registry.MustRegister(commitRequestHistogram)
userInfoRequestHistogram := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "argocd_commitserver_userinfo_request_duration_seconds",
Help: "Userinfo request duration seconds.",
Buckets: []float64{0.1, 0.25, .5, 1, 2, 4, 10, 20},
},
[]string{"repo", "credential_type"},
)
registry.MustRegister(userInfoRequestHistogram)
commitRequestCounter := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "argocd_commitserver_commit_request_total",
Help: "Number of commit requests performed handled",
},
[]string{"repo", "response_type"},
)
registry.MustRegister(commitRequestCounter)
return &Server{
handler: promhttp.HandlerFor(registry, promhttp.HandlerOpts{}),
commitPendingRequestsGauge: commitPendingRequestsGauge,
gitRequestCounter: gitRequestCounter,
gitRequestHistogram: gitRequestHistogram,
commitRequestHistogram: commitRequestHistogram,
userInfoRequestHistogram: userInfoRequestHistogram,
commitRequestCounter: commitRequestCounter,
}
}
// GetHandler returns the http.Handler for the prometheus server
func (m *Server) GetHandler() http.Handler {
return m.handler
}
// IncPendingCommitRequest increments the pending commit requests gauge
func (m *Server) IncPendingCommitRequest(repo string) {
m.commitPendingRequestsGauge.WithLabelValues(repo).Inc()
}
// DecPendingCommitRequest decrements the pending commit requests gauge
func (m *Server) DecPendingCommitRequest(repo string) {
m.commitPendingRequestsGauge.WithLabelValues(repo).Dec()
}
// IncGitRequest increments the git requests counter
func (m *Server) IncGitRequest(repo string, requestType GitRequestType) {
m.gitRequestCounter.WithLabelValues(repo, string(requestType)).Inc()
}
// ObserveGitRequestDuration observes the duration of a git request
func (m *Server) ObserveGitRequestDuration(repo string, requestType GitRequestType, duration time.Duration) {
m.gitRequestHistogram.WithLabelValues(repo, string(requestType)).Observe(duration.Seconds())
}
// ObserveCommitRequestDuration observes the duration of a commit request
func (m *Server) ObserveCommitRequestDuration(repo string, rt CommitResponseType, duration time.Duration) {
m.commitRequestHistogram.WithLabelValues(repo, string(rt)).Observe(duration.Seconds())
}
// ObserveUserInfoRequestDuration observes the duration of a userinfo request
func (m *Server) ObserveUserInfoRequestDuration(repo string, credentialType string, duration time.Duration) {
m.userInfoRequestHistogram.WithLabelValues(repo, credentialType).Observe(duration.Seconds())
}
// IncCommitRequest increments the commit request counter
func (m *Server) IncCommitRequest(repo string, rt CommitResponseType) {
m.commitRequestCounter.WithLabelValues(repo, string(rt)).Inc()
}

29
commitserver/server.go Normal file
View File

@@ -0,0 +1,29 @@
package commitserver
import (
"google.golang.org/grpc"
"github.com/argoproj/argo-cd/v2/commitserver/metrics"
"github.com/argoproj/argo-cd/v2/util/git"
"github.com/argoproj/argo-cd/v2/commitserver/apiclient"
"github.com/argoproj/argo-cd/v2/commitserver/commit"
)
// ArgoCDCommitServer is the server that handles commit requests.
type ArgoCDCommitServer struct {
commitService *commit.Service
}
// NewServer returns a new instance of the commit server.
func NewServer(gitCredsStore git.CredsStore, metricsServer *metrics.Server) *ArgoCDCommitServer {
return &ArgoCDCommitServer{commitService: commit.NewService(gitCredsStore, metricsServer)}
}
// CreateGRPC creates a new gRPC server.
func (a *ArgoCDCommitServer) CreateGRPC() *grpc.Server {
server := grpc.NewServer()
apiclient.RegisterCommitServiceServer(server, a.commitService)
return server
}

View File

@@ -26,6 +26,8 @@ const (
const (
// DefaultRepoServerAddr is the gRPC address of the Argo CD repo server
DefaultRepoServerAddr = "argocd-repo-server:8081"
// DefaultCommitServerAddr is the gRPC address of the Argo CD commit server
DefaultCommitServerAddr = "argocd-commit-server:8086"
// DefaultDexServerAddr is the HTTP address of the Dex OIDC server, which we run a reverse proxy against
DefaultDexServerAddr = "argocd-dex-server:5556"
// DefaultRedisAddr is the default redis address
@@ -46,7 +48,6 @@ const (
ArgoCDGPGKeysConfigMapName = "argocd-gpg-keys-cm"
// ArgoCDAppControllerShardConfigMapName contains the application controller to shard mapping
ArgoCDAppControllerShardConfigMapName = "argocd-app-controller-shard-cm"
ArgoCDCmdParamsConfigMapName = "argocd-cmd-params-cm"
)
// Some default configurables
@@ -62,15 +63,19 @@ const (
DefaultPortArgoCDMetrics = 8082
DefaultPortArgoCDAPIServerMetrics = 8083
DefaultPortRepoServerMetrics = 8084
DefaultPortCommitServer = 8086
DefaultPortCommitServerMetrics = 8087
)
// DefaultAddressAPIServer for ArgoCD components
const (
DefaultAddressAdminDashboard = "localhost"
DefaultAddressAPIServer = "0.0.0.0"
DefaultAddressAPIServerMetrics = "0.0.0.0"
DefaultAddressRepoServer = "0.0.0.0"
DefaultAddressRepoServerMetrics = "0.0.0.0"
DefaultAddressAdminDashboard = "localhost"
DefaultAddressAPIServer = "0.0.0.0"
DefaultAddressAPIServerMetrics = "0.0.0.0"
DefaultAddressRepoServer = "0.0.0.0"
DefaultAddressRepoServerMetrics = "0.0.0.0"
DefaultAddressCommitServer = "0.0.0.0"
DefaultAddressCommitServerMetrics = "0.0.0.0"
)
// Default paths on the pod's file system
@@ -175,6 +180,8 @@ const (
LabelValueSecretTypeRepository = "repository"
// LabelValueSecretTypeRepoCreds indicates a secret type of repository credentials
LabelValueSecretTypeRepoCreds = "repo-creds"
// LabelValueSecretTypeRepositoryWrite indicates a secret type of repository credentials for writing
LabelValueSecretTypeRepositoryWrite = "repository-write"
// AnnotationKeyAppInstance is the Argo CD application name is used as the instance name
AnnotationKeyAppInstance = "argocd.argoproj.io/tracking-id"

View File

@@ -42,6 +42,8 @@ import (
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
commitclient "github.com/argoproj/argo-cd/v2/commitserver/apiclient"
"github.com/argoproj/argo-cd/v2/common"
statecache "github.com/argoproj/argo-cd/v2/controller/cache"
"github.com/argoproj/argo-cd/v2/controller/metrics"
@@ -121,6 +123,8 @@ type ApplicationController struct {
appComparisonTypeRefreshQueue workqueue.RateLimitingInterface
appOperationQueue workqueue.RateLimitingInterface
projectRefreshQueue workqueue.RateLimitingInterface
appHydrateQueue workqueue.RateLimitingInterface
hydrationQueue workqueue.RateLimitingInterface
appInformer cache.SharedIndexInformer
appLister applisters.ApplicationLister
projInformer cache.SharedIndexInformer
@@ -131,6 +135,7 @@ type ApplicationController struct {
statusRefreshJitter time.Duration
selfHealTimeout time.Duration
repoClientset apiclient.Clientset
commitClientset commitclient.Clientset
db db.ArgoDB
settingsMgr *settings_util.SettingsManager
refreshRequestedApps map[string]CompareWith
@@ -154,6 +159,7 @@ func NewApplicationController(
kubeClientset kubernetes.Interface,
applicationClientset appclientset.Interface,
repoClientset apiclient.Clientset,
commitClientset commitclient.Clientset,
argoCache *appstatecache.Cache,
kubectl kube.Kubectl,
appResyncPeriod time.Duration,
@@ -186,10 +192,13 @@ func NewApplicationController(
kubectl: kubectl,
applicationClientset: applicationClientset,
repoClientset: repoClientset,
commitClientset: commitClientset,
appRefreshQueue: workqueue.NewRateLimitingQueueWithConfig(ratelimiter.NewCustomAppControllerRateLimiter(rateLimiterConfig), workqueue.RateLimitingQueueConfig{Name: "app_reconciliation_queue"}),
appOperationQueue: workqueue.NewRateLimitingQueueWithConfig(ratelimiter.NewCustomAppControllerRateLimiter(rateLimiterConfig), workqueue.RateLimitingQueueConfig{Name: "app_operation_processing_queue"}),
projectRefreshQueue: workqueue.NewRateLimitingQueueWithConfig(ratelimiter.NewCustomAppControllerRateLimiter(rateLimiterConfig), workqueue.RateLimitingQueueConfig{Name: "project_reconciliation_queue"}),
appComparisonTypeRefreshQueue: workqueue.NewRateLimitingQueue(ratelimiter.NewCustomAppControllerRateLimiter(rateLimiterConfig)),
appHydrateQueue: workqueue.NewRateLimitingQueueWithConfig(ratelimiter.NewCustomAppControllerRateLimiter(rateLimiterConfig), workqueue.RateLimitingQueueConfig{Name: "app_hydration_queue"}),
hydrationQueue: workqueue.NewRateLimitingQueueWithConfig(ratelimiter.NewCustomAppControllerRateLimiter(rateLimiterConfig), workqueue.RateLimitingQueueConfig{Name: "manifest_hydration_queue"}),
db: db,
statusRefreshTimeout: appResyncPeriod,
statusHardRefreshTimeout: appHardResyncPeriod,
@@ -834,6 +843,8 @@ func (ctrl *ApplicationController) Run(ctx context.Context, statusProcessors int
defer ctrl.appComparisonTypeRefreshQueue.ShutDown()
defer ctrl.appOperationQueue.ShutDown()
defer ctrl.projectRefreshQueue.ShutDown()
defer ctrl.appHydrateQueue.ShutDown()
defer ctrl.hydrationQueue.ShutDown()
ctrl.metricsServer.RegisterClustersInfoSource(ctx, ctrl.stateCache)
ctrl.RegisterClusterSecretUpdater(ctx)
@@ -892,6 +903,17 @@ func (ctrl *ApplicationController) Run(ctx context.Context, statusProcessors int
for ctrl.processProjectQueueItem() {
}
}, time.Second, ctx.Done())
go wait.Until(func() {
for ctrl.processAppHydrateQueueItem() {
}
}, time.Second, ctx.Done())
go wait.Until(func() {
for ctrl.processHydrationQueueItem() {
}
}, time.Second, ctx.Done())
<-ctx.Done()
}
@@ -1533,6 +1555,12 @@ func (ctrl *ApplicationController) PatchAppWithWriteBack(ctx context.Context, na
return patchedApp, err
}
// processAppRefreshQueueItem does roughly these tasks:
// 1. If we're shutting down, it quits early and returns "false" to indicate we're done processing refreshes.
// 2. Checks whether the app needs to be refreshed. If not, quit early.
// 3. If we're "comparing with nothing," just update the app resource tree in Redis and the app status in k8s.
// 4. Checks that all AppProject restrictions are being followed. If not, clears the app resource tree and managed
// resources in Redis and sets failure conditions on the app status.
func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext bool) {
patchMs := time.Duration(0) // time spent in doing patch/update calls
setOpMs := time.Duration(0) // time spent in doing Operation patch calls in autosync
@@ -1742,6 +1770,329 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
return
}
func (ctrl *ApplicationController) processAppHydrateQueueItem() (processNext bool) {
appKey, shutdown := ctrl.appHydrateQueue.Get()
if shutdown {
processNext = false
return
}
processNext = true
defer func() {
if r := recover(); r != nil {
log.Errorf("Recovered from panic: %+v\n%s", r, debug.Stack())
}
ctrl.appHydrateQueue.Done(appKey)
}()
obj, exists, err := ctrl.appInformer.GetIndexer().GetByKey(appKey.(string))
if err != nil {
log.Errorf("Failed to get application '%s' from informer index: %+v", appKey, err)
return
}
if !exists {
// This happens after app was deleted, but the work queue still had an entry for it.
return
}
origApp, ok := obj.(*appv1.Application)
if !ok {
log.Warnf("Key '%s' in index is not an application", appKey)
return
}
origApp = origApp.DeepCopy()
app := origApp.DeepCopy()
if app.Spec.SourceHydrator == nil {
return
}
logCtx := getAppLog(app)
logCtx.Debug("Processing app hydrate queue item")
// If we're using a source hydrator, see if the dry source has changed.
latestRevision, err := ctrl.appStateManager.ResolveDryRevision(app.Spec.SourceHydrator.DrySource.RepoURL, app.Spec.SourceHydrator.DrySource.TargetRevision)
if err != nil {
logCtx.Errorf("Failed to check whether dry source has changed, skipping: %v", err)
return
}
// TODO: don't reuse statusRefreshTimeout. Create a new timeout for hydration.
reason := appNeedsHydration(origApp, ctrl.statusRefreshTimeout, latestRevision)
if reason == "" {
return
}
if latestRevision == "" {
logCtx.Errorf("Dry source has not been resolved, skipping")
return
}
logCtx.WithField("reason", reason).Info("Hydrating app")
app.Status.SourceHydrator.CurrentOperation = &appv1.HydrateOperation{
DrySHA: latestRevision,
StartedAt: metav1.Now(),
FinishedAt: nil,
Phase: appv1.HydrateOperationPhaseHydrating,
SourceHydrator: *app.Spec.SourceHydrator,
}
ctrl.persistAppStatus(origApp, &app.Status)
origApp.Status.SourceHydrator = app.Status.SourceHydrator
ctrl.hydrationQueue.Add(getHydrationQueueKey(app))
logCtx.Debug("Successfully processed app hydrate queue item")
return
}
func getHydrationQueueKey(app *appv1.Application) hydrationQueueKey {
destinationBranch := app.Spec.SourceHydrator.SyncSource.TargetBranch
if app.Spec.SourceHydrator.HydrateTo != nil {
destinationBranch = app.Spec.SourceHydrator.HydrateTo.TargetBranch
}
key := hydrationQueueKey{
sourceRepoURL: app.Spec.SourceHydrator.DrySource.RepoURL,
sourceTargetRevision: app.Spec.SourceHydrator.DrySource.TargetRevision,
destinationBranch: destinationBranch,
}
return key
}
type hydrationQueueKey struct {
sourceRepoURL string
sourceTargetRevision string
destinationBranch string
}
type uniqueHydrationDestination struct {
sourceRepoURL string
sourceTargetRevision string
destinationBranch string
destinationPath string
}
func (ctrl *ApplicationController) processHydrationQueueItem() (processNext bool) {
key, shutdown := ctrl.hydrationQueue.Get()
if shutdown {
processNext = false
return
}
hydrationKey, ok := key.(hydrationQueueKey)
if !ok {
log.Errorf("Failed to cast key to hydrationQueueKey")
processNext = true
return
}
logCtx := log.WithFields(log.Fields{
"sourceRepoURL": hydrationKey.sourceRepoURL,
"sourceTargetRevision": hydrationKey.sourceTargetRevision,
"destinationBranch": hydrationKey.destinationBranch,
})
processNext = true
defer func() {
if r := recover(); r != nil {
log.Errorf("Recovered from panic: %+v\n%s", r, debug.Stack())
}
ctrl.hydrationQueue.Done(key)
}()
logCtx.Debug("Processing hydration queue item")
relevantApps, drySHA, hydratedSHA, err := ctrl.hydrateAppsLatestCommit(logCtx, hydrationKey)
if err != nil {
logCtx.WithField("appCount", len(relevantApps)).WithError(err).Error("Failed to hydrate apps")
for _, app := range relevantApps {
origApp := app.DeepCopy()
app.Status.SourceHydrator.CurrentOperation.Phase = appv1.HydrateOperationPhaseFailed
failedAt := metav1.Now()
app.Status.SourceHydrator.CurrentOperation.FinishedAt = &failedAt
app.Status.SourceHydrator.CurrentOperation.Message = fmt.Sprintf("Failed to hydrated revision %s: %v", drySHA, err.Error())
ctrl.persistAppStatus(origApp, &app.Status)
logCtx.Errorf("Failed to hydrate app: %v", err)
}
return
}
logCtx.WithField("appCount", len(relevantApps)).Debug("Successfully hydrated apps")
finishedAt := metav1.Now()
for _, app := range relevantApps {
origApp := app.DeepCopy()
operation := &appv1.HydrateOperation{
StartedAt: app.Status.SourceHydrator.CurrentOperation.StartedAt,
FinishedAt: &finishedAt,
Phase: appv1.HydrateOperationPhaseHydrated,
Message: "",
DrySHA: drySHA,
HydratedSHA: hydratedSHA,
SourceHydrator: app.Status.SourceHydrator.CurrentOperation.SourceHydrator,
}
app.Status.SourceHydrator.CurrentOperation = operation
app.Status.SourceHydrator.LastSuccessfulOperation = &appv1.SuccessfulHydrateOperation{
DrySHA: drySHA,
HydratedSHA: hydratedSHA,
SourceHydrator: app.Status.SourceHydrator.CurrentOperation.SourceHydrator,
}
ctrl.persistAppStatus(origApp, &app.Status)
// Request a refresh since we pushed a new commit.
ctrl.requestAppRefresh(app.QualifiedName(), CompareWithLatest.Pointer(), nil)
}
return
}
func (ctrl *ApplicationController) hydrateAppsLatestCommit(logCtx *log.Entry, hydrationKey hydrationQueueKey) ([]*appv1.Application, string, string, error) {
relevantApps, err := ctrl.getRelevantAppsForHydration(logCtx, hydrationKey)
if err != nil {
return nil, "", "", fmt.Errorf("failed to get relevant apps for hydration: %w", err)
}
dryRevision, err := ctrl.appStateManager.ResolveDryRevision(hydrationKey.sourceRepoURL, hydrationKey.sourceTargetRevision)
if err != nil {
return relevantApps, "", "", fmt.Errorf("failed to resolve dry revision: %w", err)
}
hydratedRevision, err := ctrl.hydrate(relevantApps, dryRevision)
if err != nil {
return relevantApps, dryRevision, "", fmt.Errorf("failed to hydrate apps: %w", err)
}
return relevantApps, dryRevision, hydratedRevision, nil
}
func (ctrl *ApplicationController) getRelevantAppsForHydration(logCtx *log.Entry, hydrationKey hydrationQueueKey) ([]*appv1.Application, error) {
// Get all apps
apps, err := ctrl.appLister.List(labels.Everything())
if err != nil {
return nil, fmt.Errorf("failed to list apps: %w", err)
}
var relevantApps []*appv1.Application
uniqueDestinations := make(map[uniqueHydrationDestination]bool, len(apps))
for _, app := range apps {
// TODO: test that we're actually skipping un-processable apps.
if !ctrl.canProcessApp(app) {
continue
}
if app.Spec.SourceHydrator == nil {
continue
}
if app.Spec.SourceHydrator.DrySource.RepoURL != hydrationKey.sourceRepoURL ||
app.Spec.SourceHydrator.DrySource.TargetRevision != hydrationKey.sourceTargetRevision {
continue
}
destinationBranch := app.Spec.SourceHydrator.SyncSource.TargetBranch
if app.Spec.SourceHydrator.HydrateTo != nil {
destinationBranch = app.Spec.SourceHydrator.HydrateTo.TargetBranch
}
if destinationBranch != hydrationKey.destinationBranch {
continue
}
var proj *appv1.AppProject
proj, err = ctrl.getAppProj(app)
if err != nil {
return nil, fmt.Errorf("failed to get project %q for app %q: %w", app.Spec.Project, app.QualifiedName(), err)
}
permitted := proj.IsSourcePermitted(app.Spec.GetSource())
if !permitted {
// Log and skip. We don't want to fail the entire operation because of one app.
logCtx.Warnf("App %q is not permitted to use source %q", app.QualifiedName(), app.Spec.Source.String())
continue
}
uniqueDestinationKey := uniqueHydrationDestination{
sourceRepoURL: app.Spec.SourceHydrator.DrySource.RepoURL,
sourceTargetRevision: app.Spec.SourceHydrator.DrySource.TargetRevision,
destinationBranch: destinationBranch,
destinationPath: app.Spec.SourceHydrator.SyncSource.Path,
}
// TODO: test the dupe detection
if _, ok := uniqueDestinations[uniqueDestinationKey]; ok {
return nil, fmt.Errorf("multiple app hydrators use the same destination: %v", uniqueDestinationKey)
}
uniqueDestinations[uniqueDestinationKey] = true
relevantApps = append(relevantApps, app)
}
return relevantApps, nil
}
func (ctrl *ApplicationController) hydrate(apps []*appv1.Application, revision string) (string, error) {
if len(apps) == 0 {
return "", nil
}
repoURL := apps[0].Spec.SourceHydrator.DrySource.RepoURL
syncBranch := apps[0].Spec.SourceHydrator.SyncSource.TargetBranch
targetBranch := apps[0].Spec.GetHydrateToSource().TargetRevision
var paths []*commitclient.PathDetails
for _, app := range apps {
project, err := ctrl.getAppProj(app)
if err != nil {
return "", fmt.Errorf("failed to get project: %w", err)
}
drySource := appv1.ApplicationSource{
RepoURL: app.Spec.SourceHydrator.DrySource.RepoURL,
Path: app.Spec.SourceHydrator.DrySource.Path,
TargetRevision: app.Spec.SourceHydrator.DrySource.TargetRevision,
}
drySources := []appv1.ApplicationSource{drySource}
revisions := []string{app.Spec.SourceHydrator.DrySource.TargetRevision}
appLabelKey, err := ctrl.settingsMgr.GetAppInstanceLabelKey()
if err != nil {
return "", fmt.Errorf("failed to get app instance label key: %w", err)
}
// TODO: enable signature verification
objs, resp, err := ctrl.appStateManager.GetRepoObjs(app, drySources, appLabelKey, revisions, false, false, false, project, false, false)
if err != nil {
return "", fmt.Errorf("failed to get repo objects: %w", err)
}
// Set up a ManifestsRequest
manifestDetails := make([]*commitclient.HydratedManifestDetails, len(objs))
for i, obj := range objs {
objJson, err := json.Marshal(obj)
if err != nil {
return "", fmt.Errorf("failed to marshal object: %w", err)
}
manifestDetails[i] = &commitclient.HydratedManifestDetails{ManifestJSON: string(objJson)}
}
paths = append(paths, &commitclient.PathDetails{
Path: app.Spec.SourceHydrator.SyncSource.Path,
Manifests: manifestDetails,
Commands: resp[0].Commands,
})
}
repo, err := ctrl.db.GetHydratorCredentials(context.Background(), repoURL)
if err != nil {
return "", fmt.Errorf("failed to get hydrator credentials: %w", err)
}
if repo == nil {
// Try without credentials.
repo = &appv1.Repository{
Repo: repoURL,
}
}
manifestsRequest := commitclient.CommitHydratedManifestsRequest{
Repo: repo,
SyncBranch: syncBranch,
TargetBranch: targetBranch,
DrySha: revision,
CommitMessage: fmt.Sprintf("[Argo CD Bot] hydrate %s", revision),
Paths: paths,
}
closer, commitService, err := ctrl.commitClientset.NewCommitServerClient()
if err != nil {
return "", fmt.Errorf("failed to create commit service: %w", err)
}
defer closer.Close()
resp, err := commitService.CommitHydratedManifests(context.Background(), &manifestsRequest)
if err != nil {
return "", fmt.Errorf("failed to commit hydrated manifests: %w", err)
}
return resp.HydratedSha, nil
}
func resourceStatusKey(res appv1.ResourceStatus) string {
return strings.Join([]string{res.Group, res.Kind, res.Namespace, res.Name}, "/")
}
@@ -1810,6 +2161,36 @@ func (ctrl *ApplicationController) needRefreshAppStatus(app *appv1.Application,
return false, refreshType, compareWith
}
// appNeedsHydration answers if application needs manifests hydrated.
func appNeedsHydration(app *appv1.Application, statusHydrateTimeout time.Duration, latestRevision string) string {
if app.Spec.SourceHydrator == nil {
return ""
}
var hydratedAt *metav1.Time
if app.Status.SourceHydrator.CurrentOperation != nil {
hydratedAt = &app.Status.SourceHydrator.CurrentOperation.StartedAt
}
if app.IsHydrateRequested() {
return "hydrate requested"
} else if app.Status.SourceHydrator.CurrentOperation == nil {
return "no previous hydrate operation"
} else if !app.Spec.SourceHydrator.DeepEquals(app.Status.SourceHydrator.CurrentOperation.SourceHydrator) {
return "spec.sourceHydrator differs"
} else if app.Status.SourceHydrator.CurrentOperation.DrySHA != latestRevision {
return "revision differs"
} else if app.Status.SourceHydrator.CurrentOperation.Phase == appv1.HydrateOperationPhaseFailed && metav1.Now().Sub(app.Status.SourceHydrator.CurrentOperation.FinishedAt.Time) > 2*time.Minute {
return "previous hydrate operation failed more than 2 minutes ago"
} else if hydratedAt == nil || hydratedAt.Add(statusHydrateTimeout).Before(time.Now().UTC()) {
return "hydration expired"
}
return ""
}
// refreshAppConditions validates whether AppProject restrictions are being followed. If not, it adds error conditions
// to the app status.
func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application) (*appv1.AppProject, bool) {
errorConditions := make([]appv1.ApplicationCondition, 0)
proj, err := ctrl.getAppProj(app)
@@ -1957,18 +2338,11 @@ func (ctrl *ApplicationController) autoSync(app *appv1.Application, syncStatus *
}
}
selfHeal := app.Spec.SyncPolicy.Automated.SelfHeal
// Multi-Source Apps with selfHeal disabled should not trigger an autosync if
// the last sync revision and the new sync revision is the same.
if app.Spec.HasMultipleSources() && !selfHeal && reflect.DeepEqual(app.Status.Sync.Revisions, syncStatus.Revisions) {
logCtx.Infof("Skipping auto-sync: selfHeal disabled and sync caused by object update")
return nil, 0
}
desiredCommitSHA := syncStatus.Revision
desiredCommitSHAsMS := syncStatus.Revisions
alreadyAttempted, attemptPhase := alreadyAttemptedSync(app, desiredCommitSHA, desiredCommitSHAsMS, app.Spec.HasMultipleSources())
ts.AddCheckpoint("already_attempted_sync_ms")
selfHeal := app.Spec.SyncPolicy.Automated.SelfHeal
op := appv1.Operation{
Sync: &appv1.SyncOperation{
Revision: desiredCommitSHA,
@@ -2091,7 +2465,7 @@ func alreadyAttemptedSync(app *appv1.Application, commitSHA string, commitSHAsMS
} else {
// Ignore differences in target revision, since we already just verified commitSHAs are equal,
// and we do not want to trigger auto-sync due to things like HEAD != master
specSource := app.Spec.Source.DeepCopy()
specSource := app.Spec.GetSource()
specSource.TargetRevision = ""
syncResSource := app.Status.OperationState.SyncResult.Source.DeepCopy()
syncResSource.TargetRevision = ""
@@ -2116,7 +2490,7 @@ func (ctrl *ApplicationController) shouldSelfHeal(app *appv1.Application) (bool,
// isAppNamespaceAllowed returns whether the application is allowed in the
// namespace it's residing in.
func (ctrl *ApplicationController) isAppNamespaceAllowed(app *appv1.Application) bool {
return app.Namespace == ctrl.namespace || glob.MatchStringInList(ctrl.applicationNamespaces, app.Namespace, glob.REGEXP)
return app.Namespace == ctrl.namespace || glob.MatchStringInList(ctrl.applicationNamespaces, app.Namespace, false)
}
func (ctrl *ApplicationController) canProcessApp(obj interface{}) bool {
@@ -2282,6 +2656,7 @@ func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.Shar
if !newOK || (delay != nil && *delay != time.Duration(0)) {
ctrl.appOperationQueue.AddRateLimited(key)
}
ctrl.appHydrateQueue.AddRateLimited(newApp.QualifiedName())
ctrl.clusterSharding.UpdateApp(newApp)
},
DeleteFunc: func(obj interface{}) {

View File

@@ -37,6 +37,7 @@ import (
dbmocks "github.com/argoproj/argo-cd/v2/util/db/mocks"
mockcommitclient "github.com/argoproj/argo-cd/v2/commitserver/apiclient/mocks"
mockstatecache "github.com/argoproj/argo-cd/v2/controller/cache/mocks"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned/fake"
@@ -113,6 +114,8 @@ func newFakeController(data *fakeData, repoErr error) *ApplicationController {
mockRepoClientset := mockrepoclient.Clientset{RepoServerServiceClient: &mockRepoClient}
mockCommitClientset := mockcommitclient.Clientset{}
secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
@@ -142,6 +145,7 @@ func newFakeController(data *fakeData, repoErr error) *ApplicationController {
kubeClient,
appclientset.NewSimpleClientset(data.apps...),
&mockRepoClientset,
&mockCommitClientset,
appstatecache.NewCache(
cacheutil.NewCache(cacheutil.NewInMemoryCache(1*time.Minute)),
1*time.Minute,
@@ -563,42 +567,6 @@ func TestAutoSync(t *testing.T) {
assert.False(t, app.Operation.Sync.Prune)
}
func TestMultiSourceSelfHeal(t *testing.T) {
// Simulate OutOfSync caused by object change in cluster
// So our Sync Revisions and SyncStatus Revisions should deep equal
t.Run("ClusterObjectChangeShouldNotTriggerAutoSync", func(t *testing.T) {
app := newFakeMultiSourceApp()
app.Spec.SyncPolicy.Automated.SelfHeal = false
app.Status.Sync.Revisions = []string{"z", "x", "v"}
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app}}, nil)
syncStatus := v1alpha1.SyncStatus{
Status: v1alpha1.SyncStatusCodeOutOfSync,
Revisions: []string{"z", "x", "v"},
}
cond, _ := ctrl.autoSync(app, &syncStatus, []v1alpha1.ResourceStatus{{Name: "guestbook-1", Kind: kube.DeploymentKind, Status: v1alpha1.SyncStatusCodeOutOfSync}})
assert.Nil(t, cond)
app, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(test.FakeArgoCDNamespace).Get(context.Background(), "my-app", metav1.GetOptions{})
require.NoError(t, err)
assert.Nil(t, app.Operation)
})
t.Run("NewRevisionChangeShouldTriggerAutoSync", func(t *testing.T) {
app := newFakeMultiSourceApp()
app.Spec.SyncPolicy.Automated.SelfHeal = false
app.Status.Sync.Revisions = []string{"a", "b", "c"}
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app}}, nil)
syncStatus := v1alpha1.SyncStatus{
Status: v1alpha1.SyncStatusCodeOutOfSync,
Revisions: []string{"z", "x", "v"},
}
cond, _ := ctrl.autoSync(app, &syncStatus, []v1alpha1.ResourceStatus{{Name: "guestbook-1", Kind: kube.DeploymentKind, Status: v1alpha1.SyncStatusCodeOutOfSync}})
assert.Nil(t, cond)
app, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(test.FakeArgoCDNamespace).Get(context.Background(), "my-app", metav1.GetOptions{})
require.NoError(t, err)
assert.NotNil(t, app.Operation)
})
}
func TestAutoSyncNotAllowEmpty(t *testing.T) {
app := newFakeApp()
app.Spec.SyncPolicy.Automated.Prune = true
@@ -2170,3 +2138,88 @@ func TestAppStatusIsReplaced(t *testing.T) {
require.True(t, has)
require.Nil(t, val)
}
func Test_appNeedsHydration(t *testing.T) {
t.Parallel()
now := time.Now()
oneHourAgo := metav1.NewTime(now.Add(-1 * time.Hour))
testCases := []struct {
name string
app *v1alpha1.Application
timeout time.Duration
latestRevision string
expected string
}{
{
name: "source hydrator not configured",
app: &v1alpha1.Application{},
expected: "source hydrator not configured",
},
{
name: "hydrate requested",
app: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{v1alpha1.AnnotationKeyHydrate: "normal"}}},
timeout: 1 * time.Hour,
latestRevision: "abc123",
expected: "hydrate requested",
},
{
name: "no previous hydrate operation",
app: &v1alpha1.Application{},
timeout: 1 * time.Hour,
latestRevision: "abc123",
expected: "no previous hydrate operation",
},
{
name: "spec.sourceHydrator differs",
app: &v1alpha1.Application{
Spec: v1alpha1.ApplicationSpec{
SourceHydrator: &v1alpha1.SourceHydrator{},
},
Status: v1alpha1.ApplicationStatus{SourceHydrator: v1alpha1.SourceHydratorStatus{CurrentOperation: &v1alpha1.HydrateOperation{
SourceHydrator: v1alpha1.SourceHydrator{DrySource: v1alpha1.DrySource{RepoURL: "something new"}},
}}},
},
timeout: 1 * time.Hour,
latestRevision: "abc123",
expected: "spec.sourceHydrator differs",
},
{
name: "dry SHA has changed",
app: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{SourceHydrator: v1alpha1.SourceHydratorStatus{CurrentOperation: &v1alpha1.HydrateOperation{DrySHA: "xyz123"}}}},
timeout: 1 * time.Hour,
latestRevision: "abc123",
expected: "revision differs",
},
{
name: "hydration failed more than two minutes ago",
app: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{SourceHydrator: v1alpha1.SourceHydratorStatus{CurrentOperation: &v1alpha1.HydrateOperation{DrySHA: "abc123", FinishedAt: &oneHourAgo, Phase: v1alpha1.HydrateOperationPhaseFailed}}}},
timeout: 1 * time.Hour,
latestRevision: "abc123",
expected: "previous hydrate operation failed more than 2 minutes ago",
},
{
name: "timeout reached",
app: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{SourceHydrator: v1alpha1.SourceHydratorStatus{CurrentOperation: &v1alpha1.HydrateOperation{StartedAt: oneHourAgo}}}},
timeout: 1 * time.Minute,
latestRevision: "abc123",
expected: "hydration expired",
},
{
name: "hydrate not needed",
app: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{SourceHydrator: v1alpha1.SourceHydratorStatus{CurrentOperation: &v1alpha1.HydrateOperation{DrySHA: "abc123", FinishedAt: &oneHourAgo, Phase: v1alpha1.HydrateOperationPhaseFailed}}}},
timeout: 1 * time.Hour,
latestRevision: "abc123",
expected: "",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
result := appNeedsHydration(tc.app, tc.timeout, tc.latestRevision)
assert.Equal(t, tc.expected, result)
})
}
}

View File

@@ -51,7 +51,7 @@ func (ctrl *ApplicationController) executePostDeleteHooks(app *v1alpha1.Applicat
revisions = append(revisions, src.TargetRevision)
}
targets, _, err := ctrl.appStateManager.GetRepoObjs(app, app.Spec.GetSources(), appLabelKey, revisions, false, false, false, proj, false)
targets, _, err := ctrl.appStateManager.GetRepoObjs(app, app.Spec.GetSources(), appLabelKey, revisions, false, false, false, proj, false, true)
if err != nil {
return false, err
}

View File

@@ -70,7 +70,8 @@ type managedResource struct {
type AppStateManager interface {
CompareAppState(app *v1alpha1.Application, project *v1alpha1.AppProject, revisions []string, sources []v1alpha1.ApplicationSource, noCache bool, noRevisionCache bool, localObjects []string, hasMultipleSources bool, rollback bool) (*comparisonResult, error)
SyncAppState(app *v1alpha1.Application, state *v1alpha1.OperationState)
GetRepoObjs(app *v1alpha1.Application, sources []v1alpha1.ApplicationSource, appLabelKey string, revisions []string, noCache, noRevisionCache, verifySignature bool, proj *v1alpha1.AppProject, rollback bool) ([]*unstructured.Unstructured, []*apiclient.ManifestResponse, error)
GetRepoObjs(app *v1alpha1.Application, sources []v1alpha1.ApplicationSource, appLabelKey string, revisions []string, noCache, noRevisionCache, verifySignature bool, proj *v1alpha1.AppProject, rollback bool, sendAppName bool) ([]*unstructured.Unstructured, []*apiclient.ManifestResponse, error)
ResolveDryRevision(repoURL string, revision string) (string, error)
}
// comparisonResult holds the state of an application after the reconciliation
@@ -123,7 +124,7 @@ type appStateManager struct {
// task to the repo-server. It returns the list of generated manifests as unstructured
// objects. It also returns the full response from all calls to the repo server as the
// second argument.
func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alpha1.ApplicationSource, appLabelKey string, revisions []string, noCache, noRevisionCache, verifySignature bool, proj *v1alpha1.AppProject, rollback bool) ([]*unstructured.Unstructured, []*apiclient.ManifestResponse, error) {
func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alpha1.ApplicationSource, appLabelKey string, revisions []string, noCache, noRevisionCache, verifySignature bool, proj *v1alpha1.AppProject, rollback bool, sendRuntimeState bool) ([]*unstructured.Unstructured, []*apiclient.ManifestResponse, error) {
ts := stats.NewTimingStats()
helmRepos, err := m.db.ListHelmRepositories(context.Background())
if err != nil {
@@ -205,6 +206,14 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp
}
}
appNamespace := app.Spec.Destination.Namespace
apiVersions := argo.APIResourcesToStrings(apiResources, true)
if !sendRuntimeState {
appNamespace = ""
apiVersions = nil
serverVersion = ""
}
val, ok := app.Annotations[v1alpha1.AnnotationKeyManifestGeneratePaths]
if !source.IsHelm() && syncedRevision != "" && ok && val != "" {
// Validate the manifest-generate-path annotation to avoid generating manifests if it has not changed.
@@ -215,10 +224,10 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp
Paths: path.GetAppRefreshPaths(app),
AppLabelKey: appLabelKey,
AppName: app.InstanceName(m.namespace),
Namespace: app.Spec.Destination.Namespace,
Namespace: appNamespace,
ApplicationSource: &source,
KubeVersion: serverVersion,
ApiVersions: argo.APIResourcesToStrings(apiResources, true),
ApiVersions: apiVersions,
TrackingMethod: string(argo.GetTrackingMethod(m.settingsMgr)),
RefSources: refSources,
HasMultipleSources: app.Spec.HasMultipleSources(),
@@ -238,11 +247,11 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp
NoRevisionCache: noRevisionCache,
AppLabelKey: appLabelKey,
AppName: app.InstanceName(m.namespace),
Namespace: app.Spec.Destination.Namespace,
Namespace: appNamespace,
ApplicationSource: &source,
KustomizeOptions: kustomizeOptions,
KubeVersion: serverVersion,
ApiVersions: argo.APIResourcesToStrings(apiResources, true),
ApiVersions: apiVersions,
VerifySignature: verifySignature,
HelmRepoCreds: permittedHelmCredentials,
TrackingMethod: string(argo.GetTrackingMethod(m.settingsMgr)),
@@ -275,6 +284,38 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp
return targetObjs, manifestInfos, nil
}
func (m *appStateManager) ResolveDryRevision(repoURL string, revision string) (string, error) {
conn, repoClient, err := m.repoClientset.NewRepoServerClient()
if err != nil {
return "", fmt.Errorf("failed to connect to repo server: %w", err)
}
defer io.Close(conn)
repo, err := m.db.GetRepository(context.Background(), repoURL, "")
if err != nil {
return "", fmt.Errorf("failed to get repo %q: %w", repoURL, err)
}
// Mock the app. The repo-server only needs to know whether the "chart" field is populated.
app := &v1alpha1.Application{
Spec: v1alpha1.ApplicationSpec{
Source: &v1alpha1.ApplicationSource{
RepoURL: repoURL,
TargetRevision: revision,
},
},
}
resp, err := repoClient.ResolveRevision(context.Background(), &apiclient.ResolveRevisionRequest{
Repo: repo,
App: app,
AmbiguousRevision: revision,
})
if err != nil {
return "", fmt.Errorf("failed to determine whether the dry source has changed: %w", err)
}
return resp.Revision, nil
}
func unmarshalManifests(manifests []string) ([]*unstructured.Unstructured, error) {
targetObjs := make([]*unstructured.Unstructured, 0)
for _, manifest := range manifests {
@@ -449,7 +490,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1
}
}
targetObjs, manifestInfos, err = m.GetRepoObjs(app, sources, appLabelKey, revisions, noCache, noRevisionCache, verifySignature, project, rollback)
targetObjs, manifestInfos, err = m.GetRepoObjs(app, sources, appLabelKey, revisions, noCache, noRevisionCache, verifySignature, project, rollback, true)
if err != nil {
targetObjs = make([]*unstructured.Unstructured, 0)
msg := fmt.Sprintf("Failed to load target state: %s", err.Error())
@@ -591,7 +632,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1
}
// No need to care about the return value here, we just want the modified managedNs
_, err = syncNamespace(app.Spec.SyncPolicy)(managedNs, liveObj)
_, err = syncNamespace(m.resourceTracking, appLabelKey, trackingMethod, app.Name, app.Spec.SyncPolicy)(managedNs, liveObj)
if err != nil {
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now})
failedToLoadObjs = true

View File

@@ -324,7 +324,7 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
}
if syncOp.SyncOptions.HasOption("CreateNamespace=true") {
opts = append(opts, sync.WithNamespaceModifier(syncNamespace(app.Spec.SyncPolicy)))
opts = append(opts, sync.WithNamespaceModifier(syncNamespace(m.resourceTracking, appLabelKey, trackingMethod, app.Name, app.Spec.SyncPolicy)))
}
syncCtx, cleanup, err := sync.NewSyncContext(

View File

@@ -5,11 +5,12 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/argo"
)
// syncNamespace determine if Argo CD should create and/or manage the namespace
// where the application will be deployed.
func syncNamespace(syncPolicy *v1alpha1.SyncPolicy) func(m *unstructured.Unstructured, l *unstructured.Unstructured) (bool, error) {
func syncNamespace(resourceTracking argo.ResourceTracking, appLabelKey string, trackingMethod v1alpha1.TrackingMethod, appName string, syncPolicy *v1alpha1.SyncPolicy) func(m, l *unstructured.Unstructured) (bool, error) {
// This function must return true for the managed namespace to be synced.
return func(managedNs, liveNs *unstructured.Unstructured) (bool, error) {
if managedNs == nil {

View File

@@ -8,7 +8,9 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"github.com/argoproj/argo-cd/v2/common"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/argo"
)
func createFakeNamespace(uid string, resourceVersion string, labels map[string]string, annotations map[string]string) *unstructured.Unstructured {
@@ -248,7 +250,7 @@ func Test_shouldNamespaceSync(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual, err := syncNamespace(tt.syncPolicy)(tt.managedNs, tt.liveNs)
actual, err := syncNamespace(argo.NewResourceTracking(), common.LabelKeyAppInstance, argo.TrackingMethodAnnotation, "some-app", tt.syncPolicy)(tt.managedNs, tt.liveNs)
require.NoError(t, err)
if tt.managedNs != nil {

View File

@@ -15,7 +15,7 @@ These are the upcoming releases dates:
| v2.10 | Monday, Dec. 18, 2023 | Monday, Feb. 5, 2024 | [Katie Lamkin](https://github.com/kmlamkin9) | | [checklist](https://github.com/argoproj/argo-cd/issues/16339) |
| v2.11 | Friday, Apr. 5, 2024 | Monday, May 6, 2024 | [Pavel Kostohrys](https://github.com/pasha-codefresh) | [Pavel Kostohrys](https://github.com/pasha-codefresh) | [checklist](https://github.com/argoproj/argo-cd/issues/17726) |
| v2.12 | Monday, Jun. 17, 2024 | Monday, Aug. 5, 2024 | [Ishita Sequeira](https://github.com/ishitasequeira) | [Pavel Kostohrys](https://github.com/pasha-codefresh) | [checklist](https://github.com/argoproj/argo-cd/issues/19063) |
| v2.13 | Monday, Sep. 16, 2024 | Monday, Nov. 4, 2024 | [Regina Voloshin](https://github.com/reggie-k) | [Pavel Kostohrys](https://github.com/pasha-codefresh) | [checklist](https://github.com/argoproj/argo-cd/issues/19513) |
| v2.13 | Monday, Sep. 16, 2024 | Monday, Nov. 4, 2024 | | | |
| v2.14 | Monday, Dec. 16, 2024 | Monday, Feb. 3, 2025 | | | |
| v2.15 | Monday, Mar. 17, 2025 | Monday, May 5, 2025 | | | |

View File

@@ -42,11 +42,8 @@ In order for an application to be managed and reconciled outside the Argo CD's c
In order to enable this feature, the Argo CD administrator must reconfigure the `argocd-server` and `argocd-application-controller` workloads to add the `--application-namespaces` parameter to the container's startup command.
The `--application-namespaces` parameter takes a comma-separated list of namespaces where `Applications` are to be allowed in. Each entry of the list supports:
The `--application-namespaces` parameter takes a comma-separated list of namespaces where `Applications` are to be allowed in. Each entry of the list supports shell-style wildcards such as `*`, so for example the entry `app-team-*` would match `app-team-one` and `app-team-two`. To enable all namespaces on the cluster where Argo CD is running on, you can just specify `*`, i.e. `--application-namespaces=*`.
- shell-style wildcards such as `*`, so for example the entry `app-team-*` would match `app-team-one` and `app-team-two`. To enable all namespaces on the cluster where Argo CD is running on, you can just specify `*`, i.e. `--application-namespaces=*`.
- regex, requires wrapping the string in ```/```, example to allow all namespaces except a particular one: ```/^((?!not-allowed).)*$/```.
The startup parameters for both, the `argocd-server` and the `argocd-application-controller` can also be conveniently set up and kept in sync by specifying the `application.namespaces` settings in the `argocd-cmd-params-cm` ConfigMap _instead_ of changing the manifests for the respective workloads. For example:
```yaml

View File

@@ -92,10 +92,14 @@ spec:
# You can specify the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD uses
# the Kubernetes version of the target cluster. The value must be semver formatted. Do not prefix with `v`.
# This field is useful primarily when using sourceHydrator, which requires the Kubernetes version to be set
# explicitly in .argocd-source.yaml.
kubeVersion: 1.30.0
# You can specify the Kubernetes resource API versions to pass to Helm when templating manifests. By default, Argo
# CD uses the API versions of the target cluster. The format is [group/]version/kind.
# This field is useful primarily when using sourceHydrator, which requires the API versions to be set explicitly
# in .argocd-source.yaml.
apiVersions:
- traefik.io/v1alpha1/TLSOption
- v1/Service
@@ -139,10 +143,14 @@ spec:
# You can specify the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD uses
# the Kubernetes version of the target cluster. The value must be semver formatted. Do not prefix with `v`.
# This field is useful primarily when using sourceHydrator, which requires the Kubernetes version to be set
# explicitly in .argocd-source.yaml.
kubeVersion: 1.30.0
# You can specify the Kubernetes resource API versions to pass to Helm when templating manifests. By default, Argo
# CD uses the API versions of the target cluster. The format is [group/]version/kind.
# This field is useful primarily when using sourceHydrator, which requires the API versions to be set explicitly
# in .argocd-source.yaml.
apiVersions:
- traefik.io/v1alpha1/TLSOption
- v1/Service
@@ -199,6 +207,29 @@ spec:
path: guestbook # This has no meaning for Helm charts pulled directly from a Helm repo instead of git.
ref: my-repo # For Helm, acts as a reference to this source for fetching values files from this source. Has no meaning when under `source` field
# Hydrates manifests and pushes them to the configured hydrateTo or syncSource branch.
sourceHydrator:
# drySource is the source of "don't repeat yourself" manifests to hydrate - i.e. Kustomize, Helm, etc. manifests.
drySource:
# repoURL is the URL of the git repository containing the manifests to hydrate. It must be git, Helm is not
# a supported dry source.
repoURL: https://github.com/argoproj/argocd-example-apps.git
path: helm-guestbook
targetRevision: HEAD
# syncSource is the branch/path from which Argo CD will sync. If hydrateTo is not set, this is also the branch to
# which Argo CD will push hydrated manifests.
syncSource:
targetBranch: env/dev
# You can set path to '.', but it is recommended to set it to a directory. This will make it easier to add other
# applications to hydrate to the same branch.
path: guestbook
# hydrateTo is the branch to which Argo CD will push hydrated manifests. If not set, Argo CD will push to the branch
# specified in syncSource.
# Setting hydrateTo allows you to "stage" changes in a branch which Argo CD does not sync from. You can then use an
# external tool to merge the changes into the syncSource branch.
hydrateTo:
targetBranch: env/dev-next
# Destination cluster and namespace to deploy the application
destination:
# cluster API URL

View File

@@ -7,8 +7,6 @@ The Git generator contains two subtypes: the Git directory generator, and Git fi
If the `project` field in your ApplicationSet is templated, developers may be able to create Applications under Projects with excessive permissions.
For ApplicationSets with a templated `project` field, [the source of truth _must_ be controlled by admins](./Security.md#templated-project-field)
- in the case of git generators, PRs must require admin approval.
- Git generator does not support Signature Verification For ApplicationSets with a templated `project` field.
## Git Generator: Directories

View File

@@ -100,17 +100,6 @@ possible with Go text templates:
- name: throw-away
value: "{{end}}"
- Signature verification is not supported for the templated `project` field when using the Git generator.
::yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
spec:
goTemplate: true
template:
spec:
project: {{.project}}
## Migration guide

View File

@@ -43,7 +43,6 @@ Note:
- Referenced clusters must already be defined in Argo CD, for the ApplicationSet controller to use them
- Only **one** of `name` or `server` may be specified: if both are specified, an error is returned.
- Signature Verification does not work with the templated `project` field when using git generator.
The `metadata` field of template may also be used to set an Application `name`, or to add labels or annotations to the Application.

View File

@@ -9,6 +9,9 @@ data:
# Repo server address. (default "argocd-repo-server:8081")
repo.server: "argocd-repo-server:8081"
# Commit server address. (default "argocd-commit-server:8086")
commit.server: "argocd-commit-server:8086"
# Redis server hostname and port (e.g. argocd-redis:6379)
redis.server: "argocd-redis:6379"
# Enable compression for data sent to Redis with the required compression algorithm. (default 'gzip')
@@ -188,6 +191,16 @@ data:
# Include hidden directories from Git
reposerver.include.hidden.directories: "false"
## Commit-server properties
# Listen on given address for incoming connections (default "0.0.0.0")
commitserver.listen.address: "0.0.0.0"
# Set the logging format. One of: text|json (default "text")
commitserver.log.format: "text"
# Set the logging level. One of: debug|info|warn|error (default "info")
commitserver.log.level: "info"
# Listen on given address for metrics (default "0.0.0.0")
commitserver.metrics.listen.address: "0.0.0.0"
# Disable TLS on the HTTP endpoint
dexserver.disable.tls: "false"

View File

@@ -86,6 +86,20 @@ Scraped at the `argocd-repo-server:8084/metrics` endpoint.
| `argocd_redis_request_total` | counter | Number of Kubernetes requests executed during application reconciliation. |
| `argocd_repo_pending_request_total` | gauge | Number of pending requests requiring repository lock |
## Commit Server Metrics
Metrics about the Commit Server.
Scraped at the `argocd-commit-server:8087/metrics` endpoint.
| Metric | Type | Description |
|---------------------------------------------------------|:---------:|------------------------------------------------------|
| `argocd_commitserver_commit_pending_request_total` | guage | Number of pending commit requests. |
| `argocd_commitserver_git_request_duration_seconds` | histogram | Git requests duration seconds. |
| `argocd_commitserver_git_request_total` | counter | Number of git requests performed by commit server |
| `argocd_commitserver_commit_request_duration_seconds` | histogram | Commit requests duration seconds. |
| `argocd_commitserver_userinfo_request_duration_seconds` | histogram | Userinfo requests duration seconds. |
| `argocd_commitserver_commit_request_total` | counter | Number of commit requests performed by commit server |
## Prometheus Operator
If using Prometheus Operator, the following ServiceMonitor example manifests can be used.

View File

@@ -33,6 +33,7 @@
- [notification.toolkit.fluxcd.io/Receiver/reconcile](https://github.com/argoproj/argo-cd/blob/master/resource_customizations/notification.toolkit.fluxcd.io/Receiver/actions/reconcile/action.lua)
- [notification.toolkit.fluxcd.io/Receiver/resume](https://github.com/argoproj/argo-cd/blob/master/resource_customizations/notification.toolkit.fluxcd.io/Receiver/actions/resume/action.lua)
- [notification.toolkit.fluxcd.io/Receiver/suspend](https://github.com/argoproj/argo-cd/blob/master/resource_customizations/notification.toolkit.fluxcd.io/Receiver/actions/suspend/action.lua)
- [promoter.argoproj.io/PullRequest/merge](https://github.com/argoproj/argo-cd/blob/master/resource_customizations/promoter.argoproj.io/PullRequest/actions/merge/action.lua)
- [source.toolkit.fluxcd.io/Bucket/reconcile](https://github.com/argoproj/argo-cd/blob/master/resource_customizations/source.toolkit.fluxcd.io/Bucket/actions/reconcile/action.lua)
- [source.toolkit.fluxcd.io/Bucket/resume](https://github.com/argoproj/argo-cd/blob/master/resource_customizations/source.toolkit.fluxcd.io/Bucket/actions/resume/action.lua)
- [source.toolkit.fluxcd.io/Bucket/suspend](https://github.com/argoproj/argo-cd/blob/master/resource_customizations/source.toolkit.fluxcd.io/Bucket/actions/suspend/action.lua)

View File

@@ -27,6 +27,7 @@ argocd-application-controller [flags]
--client-certificate string Path to a client certificate file for TLS
--client-key string Path to a client key file for TLS
--cluster string The name of the kubeconfig cluster to use
--commit-server string Commit server address. (default "argocd-commit-server:8086")
--context string The name of the kubeconfig context to use
--default-cache-expiration duration Cache expiration default (default 24h0m0s)
--disable-compression If true, opt-out of response compression for all requests to the server

View File

@@ -9,6 +9,9 @@ Argo CD supports several different ways in which Kubernetes manifests can be def
* A directory of YAML/JSON/Jsonnet manifests, including [Jsonnet](jsonnet.md).
* Any [custom config management tool](../operator-manual/config-management-plugins.md) configured as a config management plugin
Argo CD also supports the "rendered manifest" pattern, i.e. pushing the hydrated manifests to git before syncing them to
the cluster. See the [source hydrator](source-hydrator.md) page for more information.
## Development
Argo CD also supports uploading local manifests directly. Since this is an anti-pattern of the
GitOps paradigm, this should only be done for development purposes. A user with an `override` permission is required

View File

@@ -11,7 +11,6 @@ argocd [flags]
### Options
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -52,7 +52,6 @@ argocd account [flags]
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -25,7 +25,6 @@ argocd account bcrypt --password YOUR_PASSWORD
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -35,7 +35,6 @@ Resources: [clusters projects applications applicationsets repositories certific
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -28,7 +28,6 @@ argocd account delete-token --account <account-name> ID
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -30,7 +30,6 @@ argocd account generate-token --account <account-name>
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -28,7 +28,6 @@ argocd account get-user-info [flags]
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -29,7 +29,6 @@ argocd account get --account <account-name>
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -24,7 +24,6 @@ argocd account list
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -40,7 +40,6 @@ argocd account update-password [flags]
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -30,7 +30,6 @@ $ argocd admin initial-password reset
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -32,7 +32,6 @@ argocd admin app get-reconcile-results APPNAME
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -17,7 +17,6 @@ argocd admin app diff-reconcile-results PATH1 PATH2 [flags]
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -45,6 +45,9 @@ argocd admin app generate-spec APPNAME [flags]
--directory-exclude string Set glob expression used to exclude files from application source path
--directory-include string Set glob expression used to include files from application source path
--directory-recurse Recurse directory
--dry-source-path string Path in repository to the app directory for the dry source
--dry-source-repo string Repository URL of the app dry source
--dry-source-revision string Revision of the app dry source
--env string Application environment to monitor
-f, --file string Filename or URL to Kubernetes manifests for the app
--helm-api-versions stringArray Helm api-versions (in format [group/]version/kind) to use when running helm template (Can be repeated to set several values: --helm-api-versions traefik.io/v1alpha1/TLSOption --helm-api-versions v1/Service). If not set, use the api-versions from the destination cluster
@@ -58,6 +61,7 @@ argocd admin app generate-spec APPNAME [flags]
--helm-skip-crds Skip helm crd installation step
--helm-version string Helm version
-h, --help help for generate-spec
--hydrate-to-branch string The branch to hydrate the app to
--ignore-missing-value-files Ignore locally missing valueFiles when setting helm template --values
-i, --inline If set then generated resource is written back to the file specified in --file flag
--jsonnet-ext-var-code stringArray Jsonnet ext var
@@ -98,6 +102,8 @@ argocd admin app generate-spec APPNAME [flags]
--sync-retry-backoff-factor int Factor multiplies the base duration after each failed sync retry (default 2)
--sync-retry-backoff-max-duration duration Max sync retry backoff duration. Input needs to be a duration (e.g. 2m, 1h) (default 3m0s)
--sync-retry-limit int Max number of allowed sync retries
--sync-source-branch string The branch from which the app will sync
--sync-source-path string The path in the repository from which the app will sync
--validate Validation of repo and cluster (default true)
--values stringArray Helm values file(s) to use
--values-literal-file string Filename or URL to import as a literal Helm values block
@@ -106,7 +112,6 @@ argocd admin app generate-spec APPNAME [flags]
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -43,7 +43,6 @@ argocd admin app get-reconcile-results PATH [flags]
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -31,7 +31,6 @@ argocd admin cluster namespaces my-cluster
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -40,7 +40,6 @@ argocd admin cluster generate-spec CONTEXT [flags]
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -54,7 +54,6 @@ argocd admin cluster kubeconfig https://cluster-api-url:6443 /path/to/output/kub
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -37,7 +37,6 @@ argocd admin cluster namespaces [flags]
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -38,7 +38,6 @@ argocd admin cluster namespaces disable-namespaced-mode PATTERN [flags]
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -40,7 +40,6 @@ argocd admin cluster namespaces enable-namespaced-mode PATTERN [flags]
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -53,7 +53,6 @@ argocd admin cluster shards [flags]
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -67,7 +67,6 @@ argocd admin cluster stats target-cluster
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -54,7 +54,6 @@ $ argocd admin dashboard --redis-compress gzip
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -11,36 +11,33 @@ argocd admin export [flags]
### Options
```
--application-namespaces strings Comma separated list of namespace globs to export applications from. If not provided value from 'application.namespaces' in argocd-cmd-params-cm will be used,if it's not defined only applications from Argo CD namespace will be exported
--applicationset-namespaces strings Comma separated list of namespace globs to export applicationsets from. If not provided value from 'applicationsetcontroller.namespaces' in argocd-cmd-params-cm will be used,if it's not defined only applicationsets from Argo CD namespace will be exported
--as string Username to impersonate for the operation
--as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
--as-uid string UID to impersonate for the operation
--certificate-authority string Path to a cert file for the certificate authority
--client-certificate string Path to a client certificate file for TLS
--client-key string Path to a client key file for TLS
--cluster string The name of the kubeconfig cluster to use
--context string The name of the kubeconfig context to use
--disable-compression If true, opt-out of response compression for all requests to the server
-h, --help help for export
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
--kubeconfig string Path to a kube config. Only required if out-of-cluster
-n, --namespace string If present, the namespace scope for this CLI request
-o, --out string Output to the specified file instead of stdout (default "-")
--password string Password for basic authentication to the API server
--proxy-url string If provided, this URL will be used to connect via proxy
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
--server string The address and port of the Kubernetes API server
--tls-server-name string If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used.
--token string Bearer token for authentication to the API server
--user string The name of the kubeconfig user to use
--username string Username for basic authentication to the API server
--as string Username to impersonate for the operation
--as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
--as-uid string UID to impersonate for the operation
--certificate-authority string Path to a cert file for the certificate authority
--client-certificate string Path to a client certificate file for TLS
--client-key string Path to a client key file for TLS
--cluster string The name of the kubeconfig cluster to use
--context string The name of the kubeconfig context to use
--disable-compression If true, opt-out of response compression for all requests to the server
-h, --help help for export
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
--kubeconfig string Path to a kube config. Only required if out-of-cluster
-n, --namespace string If present, the namespace scope for this CLI request
-o, --out string Output to the specified file instead of stdout (default "-")
--password string Password for basic authentication to the API server
--proxy-url string If provided, this URL will be used to connect via proxy
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
--server string The address and port of the Kubernetes API server
--tls-server-name string If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used.
--token string Bearer token for authentication to the API server
--user string The name of the kubeconfig user to use
--username string Username for basic authentication to the API server
```
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -11,39 +11,36 @@ argocd admin import SOURCE [flags]
### Options
```
--application-namespaces strings Comma separated list of namespace globs to which import of applications is allowed. If not provided value from 'application.namespaces' in argocd-cmd-params-cm will be used,if it's not defined only applications without an explicit namespace will be imported to the Argo CD namespace
--applicationset-namespaces strings Comma separated list of namespace globs which import of applicationsets is allowed. If not provided value from 'applicationsetcontroller.namespaces' in argocd-cmd-params-cm will be used,if it's not defined only applicationsets without an explicit namespace will be imported to the Argo CD namespace
--as string Username to impersonate for the operation
--as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
--as-uid string UID to impersonate for the operation
--certificate-authority string Path to a cert file for the certificate authority
--client-certificate string Path to a client certificate file for TLS
--client-key string Path to a client key file for TLS
--cluster string The name of the kubeconfig cluster to use
--context string The name of the kubeconfig context to use
--disable-compression If true, opt-out of response compression for all requests to the server
--dry-run Print what will be performed
-h, --help help for import
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
--kubeconfig string Path to a kube config. Only required if out-of-cluster
-n, --namespace string If present, the namespace scope for this CLI request
--password string Password for basic authentication to the API server
--proxy-url string If provided, this URL will be used to connect via proxy
--prune Prune secrets, applications and projects which do not appear in the backup
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
--server string The address and port of the Kubernetes API server
--stop-operation Stop any existing operations
--tls-server-name string If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used.
--token string Bearer token for authentication to the API server
--user string The name of the kubeconfig user to use
--username string Username for basic authentication to the API server
--verbose Verbose output (versus only changed output)
--as string Username to impersonate for the operation
--as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
--as-uid string UID to impersonate for the operation
--certificate-authority string Path to a cert file for the certificate authority
--client-certificate string Path to a client certificate file for TLS
--client-key string Path to a client key file for TLS
--cluster string The name of the kubeconfig cluster to use
--context string The name of the kubeconfig context to use
--disable-compression If true, opt-out of response compression for all requests to the server
--dry-run Print what will be performed
-h, --help help for import
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
--kubeconfig string Path to a kube config. Only required if out-of-cluster
-n, --namespace string If present, the namespace scope for this CLI request
--password string Password for basic authentication to the API server
--proxy-url string If provided, this URL will be used to connect via proxy
--prune Prune secrets, applications and projects which do not appear in the backup
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
--server string The address and port of the Kubernetes API server
--stop-operation Stop any existing operations
--tls-server-name string If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used.
--token string Bearer token for authentication to the API server
--user string The name of the kubeconfig user to use
--username string Username for basic authentication to the API server
--verbose Verbose output (versus only changed output)
```
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -37,7 +37,6 @@ argocd admin initial-password [flags]
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -42,7 +42,6 @@ argocd admin notifications [flags]
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--auth-token string Authentication token
--client-crt string Client certificate file
--client-crt-key string Client certificate key file

View File

@@ -17,7 +17,6 @@ argocd admin notifications template [flags]
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--argocd-repo-server string Argo CD repo server address (default "argocd-repo-server:8081")
--argocd-repo-server-plaintext Use a plaintext client (non-TLS) to connect to repository server
--argocd-repo-server-strict-tls Perform strict validation of TLS certificates when connecting to repo server

View File

@@ -29,7 +29,6 @@ argocd admin notifications template get app-sync-succeeded -o=yaml
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--argocd-repo-server string Argo CD repo server address (default "argocd-repo-server:8081")
--argocd-repo-server-plaintext Use a plaintext client (non-TLS) to connect to repository server
--argocd-repo-server-strict-tls Perform strict validation of TLS certificates when connecting to repo server

View File

@@ -30,7 +30,6 @@ argocd admin notifications template notify app-sync-succeeded guestbook
### Options inherited from parent commands
```
--argocd-context string The name of the Argo-CD server context to use
--argocd-repo-server string Argo CD repo server address (default "argocd-repo-server:8081")
--argocd-repo-server-plaintext Use a plaintext client (non-TLS) to connect to repository server
--argocd-repo-server-strict-tls Perform strict validation of TLS certificates when connecting to repo server

Some files were not shown because too many files have changed in this diff Show More