Compare commits

...

218 Commits

Author SHA1 Message Date
Alexander Matyushentsev
24834112d6 Update manifests to v1.4.3 2020-04-15 09:35:12 -07:00
Alexander Matyushentsev
48cced9d92 Update manifests to v1.4.2 2020-01-23 16:48:25 -08:00
Alexander Matyushentsev
bc77ea70c4 fix: correctly replace cache in namespace isolation mode (#3023) 2020-01-23 16:20:40 -08:00
Alexander Matyushentsev
f8721a7360 Update manifests to v1.4.1 2020-01-22 14:44:13 -08:00
Alexander Matyushentsev
8ada07e0f5 fix: run dep ensure (#3018) 2020-01-22 14:43:38 -08:00
Alexander Matyushentsev
de8ae9c617 fix: impossible to config RBAC if group name includes ',' (#3013)
* fix:  impossible to config RBAC if group name includes ','

* apply reviewer notes
2020-01-22 11:47:31 -08:00
Alexander Matyushentsev
2d029488ab Update manifests to v1.4.0 2020-01-17 21:32:51 -08:00
Alexander Matyushentsev
3771486c14 feat: upgrade dex to v2.21.0 (#2985) 2020-01-15 14:07:44 -08:00
Alexander Matyushentsev
97922f0439 fix: sync apps panel fails with 'No App Selected' message if name contains '.' (#2983) 2020-01-15 14:07:39 -08:00
Alexander Matyushentsev
141ef96a44 docs: add notifications.md with recommandation about notifications (#2979)
* docs: add demo argocd/grafana links

* docs: add notifications.md with recommandation about notifications
2020-01-15 14:07:34 -08:00
Paul Brit
92824215f8 docs: Fix a broken link to Helm Hooks (#2970) 2020-01-15 14:07:30 -08:00
Alexander Matyushentsev
f74640d95c fix: fix nil pointer dereference in CreateRepositoryCredentials method (#2975) 2020-01-15 14:07:27 -08:00
Alexander Matyushentsev
5c0ebb59d2 fix: remove 'total' suffix from gauge prom metric (#2976) 2020-01-15 14:07:23 -08:00
Alexander Matyushentsev
6d65d01757 fix: fix rendering CRD acronym (#2978) 2020-01-15 14:07:20 -08:00
Alexander Matyushentsev
36bbc29891 docs: v1.3 and v1.4 changelog (#2952) 2020-01-15 14:07:17 -08:00
Alexander Matyushentsev
5af52f6698 Update manifests to v1.4.0-rc1 2020-01-13 09:03:55 -08:00
Alexander Matyushentsev
a6cb9987a9 chore: sort imports in controller/sync_hooks.go and run 'dep ensure' (#2972) 2020-01-13 09:02:19 -08:00
Alexander Matyushentsev
802edf3202 chore: remove unnecessary gh-pages filter from circle config (#2966) 2020-01-10 16:37:47 -08:00
Alexander Matyushentsev
6c1e6b1e72 chore: add github action that publish docs changes (#2965) 2020-01-10 14:12:19 -08:00
Alexander Matyushentsev
3396a604fe fix: rename cluster prometheus metrics according to the naming convention (#2964) 2020-01-10 13:27:03 -08:00
Alexander Matyushentsev
574446b3c0 fix: app create --upsert should take finalizers into account (#2963) 2020-01-10 12:06:04 -08:00
Alexander Matyushentsev
07e35b1839 fix: application upsert should retry on concurrent modification error (#2959) 2020-01-09 16:00:35 -08:00
Alexander Matyushentsev
0989faf4bd fix: sort conditions to avoid reconciliation loop (#2955) 2020-01-09 00:00:45 -08:00
Alexander Matyushentsev
85bda0f793 fix: self-heal should retry syncing an application after specified delay (#2950)
* fix: self-heal should retry syncing an application after specified delay
2020-01-08 14:07:36 -08:00
Michael Goodness
ac4191ff8e Add MLB to users list! (#2951)
🎉 🎉 🎉
2020-01-08 07:21:24 -08:00
Alexander Matyushentsev
08e50d4eb3 fix: use resource health for hook status evaluation (#2938) 2020-01-07 15:01:40 -08:00
Manatsawin Hanmongkolchai
9c0db45331 feat: Add support for ssh-with-port repo url (#2866) (#2948)
* Add gitlab test

* webhook: Add support for ssh with port url
2020-01-06 23:10:35 -08:00
Alexander Matyushentsev
76da1529d9 fix: limit number of parallel kubectl apply (#2944)
* fix: limit number of parallel kubectl apply
2020-01-06 22:28:42 -08:00
Alexander Matyushentsev
da6da2a229 fix: argocd diff should use NormalizedLiveState (#2943) 2020-01-06 15:39:54 -08:00
Alexander Matyushentsev
3cbe3483ca fix: property retry clusters secret watch (#2940) 2020-01-03 18:59:50 -08:00
Alexander Matyushentsev
bc33f19333 fix: diff local ignore kustomize build options (#2942) 2020-01-03 18:56:55 -08:00
Jesse Suen
189eaf2705 fix: update argocd-util import was not working properly (#2939) 2020-01-03 17:19:07 -08:00
Alexander Matyushentsev
cd27e55711 Simplify using Argo CD without users/SSO/UI (#2688) 2020-01-02 17:54:06 -08:00
Alexander Matyushentsev
6d612b47f6 fix: stop logging dex config secrets #(2904) (#2937) 2020-01-02 15:39:57 -08:00
Alexander Matyushentsev
e07953bf74 fix: remove unnecessary scroll from filter panel (#2935) 2020-01-01 20:43:32 -08:00
Alexander Matyushentsev
af212ce6b9 fix: status badge should display 'Not Found' if application does not exist (#2927) 2019-12-30 13:37:35 -08:00
Alexander Matyushentsev
3911cd48ca fix: the 'repo add' command incorrectly requires upsert flag (#2926) 2019-12-30 12:02:47 -08:00
Alexander Matyushentsev
d4c0ee80ee fix: collect cluster metrics in background; remove obsolete metrics (#2923) 2019-12-27 21:06:37 -08:00
Alexander Matyushentsev
6a0bb821cc fix: stop using jsondiffpatch on clientside to render resource difference (#2869)
* fix: stop using jsondiffpatch on clientside to render resource difference (#2863)

* Apply reviewer notes
2019-12-26 14:42:56 -08:00
Alexander Matyushentsev
9a23823f32 fix: removes redundant mutex usage in controller cache and adds cluster cache metrics (#2898) 2019-12-26 14:08:31 -08:00
Alexander Matyushentsev
aea81373eb fix: prevent user from seeing/deleting resources not permitted in project (#2908) (#2910) 2019-12-26 14:08:14 -08:00
David J. M. Karlsen
2354965b6f chore: a few lint fixes (#2911) 2019-12-26 13:36:58 -08:00
David J. M. Karlsen
c7b48e3f7c fix: cmd line docs (#2912)
Signed-off-by: David J. M. Karlsen <david@davidkarlsen.com>
2019-12-25 15:34:20 -08:00
shlo
4e579da8e9 correct wordings (#2916) 2019-12-25 15:32:48 -08:00
Alexander Matyushentsev
04097a1383 fix: add missing networking.k8s.io/Ingress health check (#2908) (#2909) 2019-12-20 10:49:18 -08:00
John Girvan
dbcfccbfc0 Add documentation for Azure AD SSO configuration (#2905) 2019-12-19 09:40:23 -08:00
Laura Barber
7e8723d664 Add peloton as user to readme. (#2901) 2019-12-18 15:12:15 -08:00
Alexander Matyushentsev
316cf5c61c fix: commit is rendered as undefinied is app revision is not specified explicitly (#2899) 2019-12-18 11:20:48 -08:00
Simon Behar
b1b443543b refactor: Export autocomplete to argo-ui (#2897)
* refactor: Export autocomplete to argo-ui
2019-12-18 11:20:32 -08:00
David J. M. Karlsen
90769d47ed improve docs. Fixes #2884 (#2895)
Signed-off-by: David J. M. Karlsen <david@davidkarlsen.com>
2019-12-18 08:25:14 -08:00
Alexander Matyushentsev
f253fcfa86 fix: UI should re-trigger SSO login if SSO JWT token expires (#2891) 2019-12-17 18:31:30 -08:00
Alexander Matyushentsev
aea2a51eeb fix: expand/collapse annotations on application details page (#2894) 2019-12-17 18:30:56 -08:00
Alexander Matyushentsev
d0530a6c39 fix: UI should cache version info to avoid having loading indicator on every navigation (#2889) 2019-12-17 15:31:06 -08:00
Alexander Matyushentsev
21f1c6c977 fix: add missing git checkout in GetRevisionMetadata method (#2893) 2019-12-17 15:30:08 -08:00
Alex Collins
e1c65a0190 feat: Displays controllerrevsion's revision in the UI. Closes #2306 (#2702) 2019-12-17 14:12:13 -08:00
Alex Collins
9783e7e2bf Updates UI icons. Closes #2625 and #2757 (#2653)
* Updates icons. Closes #2625
2019-12-17 10:50:33 -08:00
Alexander Matyushentsev
e9ae87ae67 feat: namespace isolation (#2176) (#2839)
feat: namespace isolation (#2176) (#2839)
2019-12-17 07:50:11 -08:00
Omer Kahani
cda1be72c0 Fix links (#2888) 2019-12-17 07:18:48 -08:00
Alexander Matyushentsev
66169ba982 Upgrade argo ui (#2887) 2019-12-16 16:38:22 -08:00
Alexander Matyushentsev
35adaf04bf Add tooltip to version info (#2882) 2019-12-16 13:54:09 -08:00
Alexander Matyushentsev
fca87b7884 Prevent loading commit metadata is it is missing in sync results (#2886) 2019-12-16 13:45:56 -08:00
Alexander Matyushentsev
54b3786f32 fix: clarify cluster cli command arguments to avoid confusion (#2879) 2019-12-13 19:26:12 -08:00
Alexander Matyushentsev
899abd37b5 fix: stop loading history metadata for the whole deployment history (#2878) (#2880) 2019-12-13 19:25:33 -08:00
Jesse Suen
73f0437ef3 feat: Use kubectl apply library instead of forking binary (#2861) 2019-12-13 17:29:01 -08:00
Alexander Matyushentsev
f815facf71 fix: git contention leads applications into Unknown state (#2877) 2019-12-13 12:00:39 -08:00
Alexander Matyushentsev
7ab781d309 fix: Fix flaky TestAutoSyncSelfHealEnabled test (#2876) 2019-12-13 12:00:27 -08:00
Qingbo Zhou
566a690813 docs: fix typo in webhook doc (#2870) 2019-12-13 11:38:53 -08:00
Alex Collins
2707008c1a Adds revision history limit. Closes #2790 (#2818)
* Adds revision history limit. Closes #2790
2019-12-13 11:14:43 -08:00
Torsten Walter
c4eeb8d3a6 chore: Add Syncier as user of Argo CD (#2872)
Signed-off-by: Torsten Walter <torsten.walter@syncier.com>
2019-12-13 09:34:13 -08:00
vdinesh2461990
3c24681907 Update README.md (#2868)
Added Tiger Analytics
2019-12-12 08:14:52 -08:00
jkleinlercher
20bdfeca81 Add ARZ to Argo CD users (#2867) 2019-12-12 00:07:29 -08:00
Alexander Matyushentsev
0a5023d08e Issue #2853 - Improve application env variables/labels editing (#2856) 2019-12-11 14:01:36 -08:00
Alexander Matyushentsev
0bf38bb93f Fix TestAutoSyncSelfHealEnabled test (#2857) 2019-12-11 13:26:32 -08:00
Scott Cabrinha
9670f406a3 Issue #2833: use editor arguments in InteractiveEditor (#2850) 2019-12-11 10:06:54 -08:00
Abhishek Jaisingh
575f7604d6 feat: Template Out Data Source in Grafana Dashboard (#2859)
* Template Out Hardcoded Prometheus Data Source
* This allows monitoring Argo CD deployed on different Promethei

Change-Id: I89a326865c30515716c78f9485fa91769dc9fe4c
2019-12-10 23:53:47 -08:00
Aoi Motomura
97b45c7a74 Add cybozu to the list of users (#2858) 2019-12-10 23:41:05 -08:00
Jesse Suen
0ea0404044 Update k8s dependency to v1.16 (#2828) 2019-12-10 18:33:58 -08:00
Alex Collins
0fa02b0a55 Updates releasing guide. (#2844) 2019-12-10 15:05:55 -08:00
David Hong
7c787a3bb0 Add version information for git submodule support (#2855) 2019-12-10 15:05:40 -08:00
Simon Behar
ef2501f4b1 Add support for hidden directories with directory enforcer (#2821)
* Add support for hidden directories with directory enforcer

* Refactor

* Lint

* Rework done, still needs tests

* WIP

* Should be done

* Fix test

* Helm Charts
2019-12-10 13:50:20 -08:00
Alexander Matyushentsev
f56e084bb9 Issue #2683 - Make sure app update don't fail due to concurrent modification (#2852) 2019-12-10 13:09:39 -08:00
dthomson25
c3ed032c79 Update rollout healthcheck to use pause conditons (#2842) 2019-12-10 11:00:46 -08:00
Julian Mazzitelli
14d0e76f12 who uses: biobox analytics (#2851)
* Update README.md

* who uses: biobox alphabetical ordering
2019-12-10 09:37:25 -08:00
Alexander Matyushentsev
cc2b7b149e Issue 2848 - Application Deployment history panel shows incorrect info for recent releases (#2849) 2019-12-09 15:36:39 -08:00
Devin Stein
9ffba90a73 [doc] Add KSOPS to Secret Management List (#2846) 2019-12-09 12:55:21 -08:00
Devin Stein
659b16f5f8 doc: Add Viaduct to "Who Uses" List (#2841) 2019-12-09 11:07:09 -08:00
Alex Collins
adc2e9ba54 Improves the "Sync Apps" panel UI. Closes #2698 (#2699) 2019-12-09 11:04:31 -08:00
Yros Aguiar
3a7f6d06b1 doc: Add Pipefy to list of current companies using Argocd (#2838)
* Add pipefy to list of current company using argocd
2019-12-09 10:38:22 -08:00
jannfis
4fa47ce6cd Only delete resources during app delete cascade if permitted to (fixes #2693) (#2695)
* Only delete resources during app delete if we're permitted to

* Permission checks need to move to a different place

* Add unit tests

* Return map of actually deleted object, so we can test against it

* Better error handling

* Move logic into shouldBeDeleted()
2019-12-09 08:39:20 -08:00
Daniel Helfand
fb0aef3d73 More Detailed argocd CLI Install Instructions for Mac and Linux (#2807) 2019-12-09 08:36:33 -08:00
Julien Poissonnier
8cd34503a1 doc: add elium to list of users (#2834) 2019-12-09 08:35:37 -08:00
Dominik Münch
e40a046504 doc: add celonis to list of users (#2836) 2019-12-09 08:30:09 -08:00
Anton Lindholm
6710ac15f4 Add walkbase to the list of users (#2835) 2019-12-09 08:28:45 -08:00
Enrico Stahn
026cfc5efd doc: add hipages to list of users (#2832) 2019-12-08 22:25:47 -08:00
Jacob O'Farrell
880ee7fbaf Add Max Kelsen to Who Uses Argo CD (#2831) 2019-12-08 22:24:23 -08:00
Nikolas Philips
6bf943c156 Add Baloise as Argo CD user (#2830) 2019-12-08 22:22:38 -08:00
Jaime
78dfbd37b4 Add Adevinta in README.md (#2829)
Add Adevinta.
https://www.adevinta.com/
2019-12-08 18:00:10 -08:00
Nick Kampe
03acca8ef7 Added EDF Renewables to list of organizations (#2823)
* Added EDF Renewables to list of organizations
2019-12-08 17:41:33 -08:00
Alex Collins
2032688f36 Make ConvertToVersion maybe 1090% faster on average (#2820) 2019-12-06 17:31:54 -08:00
Alex Collins
3ad462a112 Fixes error in docs that prevents publishing them (#2817) 2019-12-05 22:56:42 -08:00
Alex Collins
9b679ffa75 Fixes logging of tracing option in CLI (#2819) 2019-12-05 15:19:36 -08:00
Alex Collins
cb4898acb5 Adds tracing to key external invocations. (#2811) 2019-12-05 13:35:20 -08:00
Alexander Matyushentsev
5077be9482 argocd-util should allow editing project policies in bulk (#2615)
* Implement 'argocd-util projects update-role-policy' command which allows to update multiple project policies
2019-12-05 13:35:12 -08:00
Alexander Matyushentsev
89a5bc87b9 Issue #2721 Optimize helm repo querying (#2816) 2019-12-05 11:13:08 -08:00
Guido Maria Serra
a5f22d3841 SAML/Azure integration (#2815) 2019-12-05 09:41:21 -08:00
Byungjin Park
50ac3fd6c5 Fix typo in docs (#2813) 2019-12-04 22:18:11 -08:00
Alexander Matyushentsev
91b0cd0a47 Issue #2721 - cache parsed repositories, repo credentials to avoid unnecessary yaml parsing (#2809) 2019-12-04 15:23:18 -08:00
Daniel Helfand
9fa1886c02 updates to getting started tutorial (#2801) 2019-12-03 17:33:11 -08:00
Alex Collins
148b90b5b5 Revert "Use Kustomize 3 to generate manifetsts. Closes #2487 (#2510)" (#2696) 2019-12-03 10:52:29 -08:00
Christine Banek
0715d05733 Fix 'Open application' link when using basehref (#2729) 2019-12-03 10:26:37 -08:00
INOUE BANJI
3258f2deee Add custom healthchecks for cert-manager v0.11.0 (#2689) 2019-12-03 09:44:20 -08:00
Simon Behar
72b90f6890 Fix directory traversal edge case and enhance tests (#2797) 2019-12-02 18:26:58 -08:00
Devan Goodwin
addf397b53 Fix a bug with cluster add when token secret is not first in list. (#2744) 2019-12-02 15:34:57 -08:00
Alex Collins
aeb48b0a69 Fix bug where manifests are not cached. Fixes #2770 (#2771) 2019-12-02 12:08:06 -08:00
Alex Collins
a6ccf924b5 Fixes bug whereby retry does not work for CLI. Fixes #2767 (#2768) 2019-12-02 10:09:41 -08:00
Alex Collins
e416547192 Make BeforeHookCreation the default. Fixes #2754 (#2759) 2019-12-02 09:44:42 -08:00
Alex Collins
60aa7fb71e Adds support for /api/v1/account* via HTTP. Fixes #2664 (#2701) 2019-12-02 09:43:58 -08:00
Alex Collins
cbe94440df Allow dot in project policy. Closes #2724 (#2755) 2019-12-01 19:14:32 -08:00
Alexander Matyushentsev
bfe05e4755 Update README.md (#2788) 2019-11-30 12:26:43 -08:00
nitinpatil1992
848a576a05 Usage of Argo-cd at Saloodo! GmbH (#2786)
* Usage of Argo-cd at Saloodo! GmbH

Argocd for CD for saloodo platform deploying almost 24 webservices

* Re-order argocd organization list for saloodo

* Reorder organisations list for saloodo
2019-11-28 18:20:04 -08:00
Alex Collins
a6da0ca65b Ensures that Helm charts are correctly resolved before sync. Fixes #2758 (#2760) 2019-11-28 12:55:22 -08:00
Alex Collins
af195f36f0 Update README.md to add KubeCon workshop (#2766) 2019-11-28 12:54:53 -08:00
Konstantin
31e30fbf6e fixed docs with proper keys for secret data (#2777) 2019-11-27 09:54:51 -08:00
Gregor Krmelj
f994926487 docs: fix label for ServiceMonitor CRD for API server metrics (#2775) 2019-11-27 09:53:16 -08:00
Simon Behar
c8ae89f953 Make directory enforcer more lenient (#2716)
* Make directory enforcer more lenient and add flag

* Fixes

* Lint fixes

* Lint fixes

* Fixed test

* Minor

* Removed enforcer option

* Move directory traversal check higher up

* Go fmt

* Allow URLs

* Added test
2019-11-27 07:00:46 -08:00
Alex Collins
257c27677a Removes log warning regarding indexer and may improve performance. Closes #1345 (#2761) 2019-11-24 14:12:47 -08:00
Alex Collins
73a1a7ce76 Correct version of Argo CD that build env is available in. See #2749 (#2750) 2019-11-21 14:37:45 -08:00
Alex Collins
c2ff8e856b Allow you to sync local Helm apps. Fixes #2741 (#2747) 2019-11-20 18:20:12 -08:00
Ilir Bekteshi
5980b604a6 Add values file block example (#2745) 2019-11-20 08:28:48 -08:00
Alex Collins
d3b670937a Shows chart name in apps tiles and apps table pages. Closes #2726 (#2728) 2019-11-19 07:48:48 -08:00
Alex Collins
9593f8d3b3 Update CONTRIBUTING.md (#2727) 2019-11-18 15:19:52 -08:00
jannfis
0cfe1cdedf Set X-Frame-Options on serving static assets (#2706) (#2711)
* Add some test data for testing static assets

* Optional send X-Frame-Options header for static assets

* Allow fake server some time to settle in tests

* Retrigger CI
2019-11-18 15:12:35 -08:00
Alex Collins
39ea6444f9 Disables TestAutoSyncSelfHealEnabled. (#2703) 2019-11-18 15:12:11 -08:00
Alex Collins
620d956038 Returns a clearer error on invalid Helm version. Closes #2665 and #2736 (#2666) 2019-11-18 10:08:52 -08:00
Naoki Oketani
be77f468a3 modify pre-requisites kustomize version (#2725) 2019-11-15 11:19:33 -08:00
David Maciel
24eb0b2409 Clarify the need for namespace during export (#2722) 2019-11-15 10:10:49 -08:00
Simon Behar
b233563e29 Fixed KustomizeBuildOptions not getting synced (#2714) 2019-11-13 09:31:37 -08:00
Olivier Lemasle
455837f3e8 Remove references to argocd-ui image in manifests generation (#2710)
Docker image argocd-ui is no longer used and is not present
in YAML manifests; however, there was still references to it
in the manifest generation workflow.
2019-11-13 09:12:50 -08:00
Alex Collins
09808b5016 Fixes the failing CI master build (#2697) 2019-11-12 16:10:35 -08:00
Alexander Matyushentsev
f01df4e686 Restore 'argocd app run action' backward compatibility (#2700) 2019-11-12 16:08:51 -08:00
Naoki Oketani
a6db07ff72 Rename deprecated deadline option to timeout (#2686) 2019-11-12 15:54:20 -08:00
Masayuki Ishii
048e787668 Issue #2668 - Delete a specified context (#2669)
* Issue #2668 - Delete a specified context
2019-11-12 14:38:48 -08:00
Alexander Matyushentsev
3d6c77e3a8 Issue #2691 - Remove annoying toolbar flickering (#2692) 2019-11-12 08:39:19 -08:00
Naoki Oketani
11f00c88b5 modified make target (#2685) 2019-11-11 09:37:46 -08:00
Alan Tang
6cbc43e2ae Modify docs for ingress ssl passthrough and SSO (#2649) 2019-11-11 09:33:06 -08:00
Alexander Matyushentsev
84f24cdb6c Issue #2673 - Application controller flag is broken (#2674) 2019-11-08 16:20:31 -08:00
Alexander Matyushentsev
0d8011da8e Issue #2670 - API server does not allow creating role with action 'action/*' (#2671) 2019-11-08 11:02:47 -08:00
Alexander Matyushentsev
0ff2533ba0 Issue #2559 - Add gauge Prometheus metric which represents the number of pending manifest requests. (#2658) 2019-11-07 18:28:13 -08:00
dthomson25
7982a19966 Add AnalysisRun and Experiment HealthCheck (#2579) 2019-11-07 15:08:32 -08:00
Alexander Matyushentsev
f4400b9493 Issue #2659 - Fix 1.3 login regressions (#2660)
* Issue #2659 - Fix 1.3 login regressions

* Add server.go tests
2019-11-07 14:52:17 -08:00
Alexander Matyushentsev
4facca0ae7 Issue #2662 - Don't parse kustomize version outout (#2663) 2019-11-07 14:51:13 -08:00
Alex Collins
8ac09c9ca9 Adds support for testing .tsx files. Closes #2610 (#2661) 2019-11-07 14:50:52 -08:00
Alexander Matyushentsev
cb6d7eaad2 Issue #2645 - /api/version should not fail if unable to load tool version (#2654) 2019-11-06 16:17:54 -08:00
Alexander Matyushentsev
4ccb02375f Issue #2655 - Application list page is not updated automatically (#2656) 2019-11-06 16:16:31 -08:00
Alex Collins
329b845f55 Adds note to docs that build enviroment is available in v1.3. Fixes 2651 (#2652) 2019-11-06 14:36:09 -08:00
David Hong
de29c9d0f5 chore: Upgrade kustomize to 3.2.1 (#2607) 2019-11-06 00:25:45 -08:00
Yujun Zhang
7065229a45 Revert "Fix Typo: Filter Label To Get Server Pod Name (#2597)" (#2637)
This reverts commit 424f1e9a3d.
2019-11-05 07:15:54 -08:00
Alexander Matyushentsev
68a81854c1 Issue #2635 - Custom actions are disabled in Argo CD UI (#2636) 2019-11-04 14:28:38 -08:00
Alexander Matyushentsev
d06260ebbb Issue #2592 - Remove transitive dependency on packr (#2631) 2019-11-04 10:41:43 -08:00
Alexander Matyushentsev
cc7b83adf3 Issue #2633 - Application list page incorrectly filter apps by label selector (#2634) 2019-11-04 10:41:10 -08:00
jannfis
45270ec03f Assume git as default repository type (fixes #2622) (#2628)
* Assume git as default repository type

* Add helm repo with name in E2E tests
2019-11-03 20:15:28 -08:00
Saradhi Sreegiriraju
42804dbbac Update README.md (#2630) 2019-11-03 11:18:04 -08:00
Alexander Matyushentsev
00eebce95a Issue #2626 - Repo server executes unnecessary ls-remotes (#2627) 2019-11-02 21:56:51 -07:00
Gene Liverman
e00607b722 Certmanager docs (#2629)
* Add example of SSL-Passthrough with cert-manager

The certificates in this example are from Let's Ecnrypt

* Callout the secret name as being provided by Argo CD directly
2019-11-02 17:12:22 -07:00
Alex Collins
8575e3942c Adds support for Helm charts to be a semver range. Closes #2552 (#2606) 2019-11-01 13:41:35 -07:00
Alexander Matyushentsev
da04075120 Issue #2620 - Cluster list page fails if any cluster is not reachable (#2621) 2019-11-01 13:22:51 -07:00
Alexander Matyushentsev
0b684db148 Issue #2616 - argocd app diff prints only first difference (#2617) 2019-11-01 11:49:01 -07:00
Alexander Matyushentsev
1d5e6a1b90 Bump min client cache version (#2619) 2019-11-01 11:35:49 -07:00
jannfis
0fab3707cc Allow '@'-character in SSH usernames when connecting a repository (#2612) 2019-11-01 11:03:28 -07:00
Alex Collins
212ca9e37e Adds argocd auth can-i command. Close #2255 (#2521) 2019-10-31 16:06:29 -07:00
Alexander Matyushentsev
89b33a1442 Execute application label filtering on client side (#2605) 2019-10-30 18:42:04 -07:00
Alex Collins
6930ecc947 Adds timeout to Helm commands. (#2570) 2019-10-30 16:41:39 -07:00
Alex Collins
430b933869 UI fixes for "Sync Apps" panel. (#2604) 2019-10-30 16:39:28 -07:00
Alex Collins
e328a5d144 Adds a status icon for the op to the UI. Closes #2596 (#2601) 2019-10-30 15:59:43 -07:00
Alex Collins
70ec0d8b29 Upgrade Helm to v2.15.2. Closes #2587 (#2590) 2019-10-30 15:32:23 -07:00
jannfis
fdea6e2edf Adds the option to output in YAML and JSON to several CLI commands. Closes #2534 (#2551) 2019-10-30 10:23:28 -07:00
niqdev
c096341772 [doc] update secret-management (#2598) 2019-10-30 10:16:46 -07:00
Abhishek Jaisingh
424f1e9a3d Fix Typo: Filter Label To Get Server Pod Name (#2597) 2019-10-30 10:16:14 -07:00
Abhishek Jaisingh
4d795ac381 Fix Typo in Docs (#2595) 2019-10-30 10:15:48 -07:00
dherman
7610a8b2dd correct the spelling of hashicorp (#2599) 2019-10-30 10:15:29 -07:00
Miguel
b8bac1e688 Update README.md added Universidad Mesoamericana to organization list (#2514) 2019-10-29 13:34:13 -07:00
Isaac Gaskin
afda10bc8f docs(app cmd): updating --local text to include helm and kustomize (#2582) 2019-10-29 13:31:10 -07:00
Alex Collins
a9a28b7e42 Sets app status to unknown if there is an error. Closes #2577 (#2578) 2019-10-29 11:44:34 -07:00
Mohammed Naser
9d784e7e3f Fix date in CHANGELOG.md (#2584) 2019-10-29 10:14:54 -07:00
Alexander Matyushentsev
a53950e5a8 Issue #2339 - Don't update 'status.reconciledAt' unless compared with latest git version (#2581) 2019-10-28 16:44:23 -07:00
Devan Goodwin
134469c5f0 Display app conditions timestamp in CLI and UI. (#737) (#2565)
* Display app conditions timestamp in CLI and UI.

* Add Red Hat to README.
2019-10-28 11:13:02 -07:00
Alex Collins
aeb5223169 Allows Helm charts to have arbitrary file names. Fixes #2549 (#2569) 2019-10-28 10:53:02 -07:00
Masayuki Ishii
73590e1a39 Fixes blocking SSO login via CLI (#2563) (#2564) 2019-10-27 01:43:24 -07:00
Alex Collins
a0f3903418 Fixes panic when creating repo (#2568) 2019-10-25 12:35:17 -07:00
Alex Collins
21cc1ec89b Reduces total build time from 20m to 14m (#2560) 2019-10-25 12:33:54 -07:00
Simon Behar
0675ff2fb2 Done (#2529) 2019-10-23 17:19:59 -07:00
Lev Aminov
c72160f681 Upgrade casbin/casbin to 1.9.1 (#2517) 2019-10-23 17:00:45 -07:00
Alex Collins
513b0eb51b Adds doc on secrets, updates changelog, updates Github templates (#2550) 2019-10-23 15:16:26 -07:00
Alex Collins
4169697302 Enable prettier on UI source code (#2524) 2019-10-22 15:45:33 -07:00
Alexander Matyushentsev
9c3c2f3f14 Issue #2339 - Controller should compare with latest git revision if app has changed (#2543) 2019-10-22 15:23:15 -07:00
Alex Collins
99426ce659 Tidies up naming of variables in code. See #2389 (#2541) 2019-10-22 14:25:02 -07:00
Alexander Matyushentsev
7e4cb92fb8 Unknown child app should not affect app health (#2544) 2019-10-22 13:56:55 -07:00
Alex Collins
a55087b6fd Shows version in UI. (#2502) 2019-10-22 11:11:54 -07:00
Simon Behar
2d73fea0a5 Redact secrets in dex logs (#2538)
* Done

* Pre-commit

* Added test

* Pre-commit

* Goimports
2019-10-22 10:11:34 -07:00
Alex Collins
5706a17155 Adds support for ARGO_CD_[TARGET_REVISION|REVISION] and pass to Custom Tool/Helm/Jsonnet (#2415) 2019-10-21 16:54:23 -07:00
Adam Johnson
bbfb96cb01 add git submodule support (#2495) 2019-10-21 15:17:07 -07:00
Eltahir Eltahir
500730ef6c Add CARFAX to list of Argo CD users (#2527) 2019-10-19 09:00:38 -07:00
Simon Behar
078f5ccccf Done (#2526) 2019-10-18 15:34:46 -07:00
Alex Collins
cf5d9db5bb Allows Helm parameters that contains arrays or maps. (#2525) 2019-10-18 15:30:46 -07:00
Alex Collins
6c93047367 Use the same tools for make image to make dev-tools-image. Closes #2488 (#2511) 2019-10-18 13:21:36 -07:00
Alex Collins
e7b5007361 Use Kustomize 3 to generate manifetsts. Closes #2487 (#2510) 2019-10-18 13:21:09 -07:00
Alex Collins
4cb84b37ce changes on 2pc to .codecov.yml, (#2513) 2019-10-18 07:40:16 -07:00
Alex Collins
572d376dab Adds the ability to work with groups of apps using labels (#2463) 2019-10-17 14:48:08 -07:00
Alex Collins
941ccda32e Update CHANGELOG.md to list v1.3.0-rc1 (#2504) 2019-10-17 14:15:39 -07:00
jannfis
8d5939f128 Set cookie policy to SameSite=lax and httpOnly (#2498) 2019-10-17 11:29:06 -07:00
jannfis
e8c21ab010 Fix typo: grcp -> grpc (#2509) 2019-10-17 10:35:46 -07:00
stgarf
0f2a88102d Added SSO and RBAC (#2503) 2019-10-16 19:58:42 -07:00
Devan Goodwin
e3edd2ced3 Add Time to ApplicationCondition. (#2417) 2019-10-16 19:29:52 -07:00
jannfis
37641cf2d0 Add repository credential management API and CLI (addresses #2136) (#2207) 2019-10-16 17:17:47 -07:00
Jeff Hastings
2148b593ee add namesuffix for kustomize applications (#2473) 2019-10-16 16:34:52 -07:00
Imran Ismail
5ec5aeb002 Use vars for service name reference in commands (#2408)
=
2019-10-16 16:33:48 -07:00
Alex Collins
bbdbe364b0 Makes cache timeouts configurable (#2412) 2019-10-16 15:46:45 -07:00
Alex Collins
8df3bad4c8 Fixes a bug where app kind was not show in UI YAML editor (#2501) 2019-10-16 13:52:49 -07:00
513 changed files with 36336 additions and 19744 deletions

View File

@@ -11,16 +11,15 @@ commands:
git config --global user.name "Your Name"
echo "export PATH=/home/circleci/.go_workspace/src/github.com/argoproj/argo-cd/hack:\$PATH" | tee -a $BASH_ENV
echo "export GIT_ASKPASS=git-ask-pass.sh" | tee -a $BASH_ENV
dep_ensure:
restore_vendor:
steps:
- restore_cache:
keys:
- vendor-v4-{{ checksum "Gopkg.lock" }}
- run:
name: Run dep ensure
command: dep ensure -v
- vendor-v1-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_JOB }}
save_vendor:
steps:
- save_cache:
key: vendor-v4-{{ checksum "Gopkg.lock" }}
key: vendor-v1-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_JOB }}
paths:
- vendor
install_golang:
@@ -30,24 +29,23 @@ commands:
command: |
go get golang.org/dl/go1.12.6
[ -e /home/circleci/sdk/go1.12.6 ] || go1.12.6 download
go env
echo "export GOPATH=/home/circleci/.go_workspace" | tee -a $BASH_ENV
echo "export PATH=/home/circleci/sdk/go1.12.6/bin:\$PATH" | tee -a $BASH_ENV
save_go_cache:
steps:
- save_cache:
key: go-v18-{{ .Branch }}
key: go-v1-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }}
# https://circleci.com/docs/2.0/language-go/
paths:
- /home/circleci/.go_workspace
- /home/circleci/.cache/go-build
- /home/circleci/sdk/go1.12.6
restore_go_cache:
steps:
- restore_cache:
keys:
- go-v18-{{ .Branch }}
- go-v18-master
- go-v17-{{ .Branch }}
- go-v17-master
- go-v1-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }}
- go-v1-master-{{ .Environment.CIRCLE_JOB }}
jobs:
codegen:
docker:
@@ -56,12 +54,13 @@ jobs:
steps:
- checkout
- restore_cache:
keys: [codegen-v2]
keys:
- codegen-v1-{{ checksum "Gopkg.lock" }}-{{ checksum "hack/installers/install-codegen-go-tools.sh" }}
- run: ./hack/install.sh codegen-go-tools
- run: sudo ./hack/install.sh codegen-tools
- run: dep ensure
- run: dep ensure -v
- save_cache:
key: codegen-v2
key: codegen-v1-{{ checksum "Gopkg.lock" }}-{{ checksum "hack/installers/install-codegen-go-tools.sh" }}
paths: [vendor, /tmp/dl, /go/pkg]
- run: helm init --client-only
- run: make codegen-local
@@ -93,9 +92,11 @@ jobs:
paths: [/tmp/dl]
- configure_git
- run: go get github.com/jstemmer/go-junit-report
- dep_ensure
- save_go_cache
- restore_vendor
- run: dep ensure -v
- run: make test
- save_vendor
- save_go_cache
- run:
name: Uploading code coverage
command: bash <(curl -s https://codecov.io/bash) -f coverage.out
@@ -133,7 +134,8 @@ jobs:
- save_cache:
key: e2e-dl-v10
paths: [/tmp/dl]
- dep_ensure
- restore_vendor
- run: dep ensure -v
- configure_git
- run: make cli
- run:
@@ -177,8 +179,9 @@ jobs:
command: PATH=dist:$PATH make test-e2e
environment:
ARGOCD_OPTS: "--server localhost:8080 --plaintext"
ARGOCD_E2E_EXPECT_TIMEOUT: "30"
ARGOCD_E2E_K3S: "true"
- save_vendor
- save_go_cache
- store_test_results:
path: test-results
- store_artifacts:
@@ -199,16 +202,15 @@ jobs:
key: yarn-packages-v4-{{ checksum "yarn.lock" }}
paths: [~/.cache/yarn, node_modules]
- run: yarn test
- run: yarn build
- run: ./node_modules/.bin/codecov -p ..
- run: NODE_ENV='production' yarn build
- run: yarn lint
workflows:
version: 2
workflow:
jobs:
- test
- codegen:
requires:
- test
- codegen
- ui:
requires:
- codegen

View File

@@ -13,5 +13,5 @@ coverage:
patch: off
project:
default:
# allow test coverage to drop by 1%, assume that it's typically due to CI problems
threshold: 1
# allow test coverage to drop by 2%, assume that it's typically due to CI problems
threshold: 2

View File

@@ -7,6 +7,7 @@ assignees: ''
---
Checklist:
* [ ] I've searched in the docs and FAQ for my answer: http://bit.ly/argocd-faq.
* [ ] I've included steps to reproduce the bug.
* [ ] I've pasted the output of `argocd version`.

View File

@@ -1,5 +1,7 @@
Checklist:
* [ ] I've created an [enhancement proposal](https://github.com/argoproj/argo-cd/issues/new/choose) and I feel I've gotten a green light from the community.
* [ ] My build is green ([troubleshooting builds](https://argoproj.github.io/argo-cd/developer-guide/ci/)).
* [ ] Optional. My organisation is added to the README.
* [ ] Either (a) I've created an [enhancement proposal](https://github.com/argoproj/argo-cd/issues/new/choose) and discussed it with the community, (b) this is a bug fix, or (c) this does not need to be in the release notes.
* [ ] The title of the PR states what changed and the related issues number (used for the release note).
* [ ] I've updated both the CLI and UI to expose my feature, or I plan to submit a second PR with them.
* [ ] Optional. My organization is added to the README.
* [ ] I've signed the CLA and my build is green ([troubleshooting builds](https://argoproj.github.io/argo-cd/developer-guide/ci/)).

27
.github/workflows/gh-pages.yaml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Deploy
on:
push:
branches:
- master
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Setup Python
uses: actions/setup-python@v1
with:
python-version: 3.x
- name: build
run: |
pip install mkdocs mkdocs_material
mkdocs build
mkdir ./site/.circleci && echo '{version: 2, jobs: {build: {branches: {ignore: gh-pages}}}}' > ./site/.circleci/config.yml
- name: deploy
uses: peaceiris/actions-gh-pages@v2.5.0
env:
PERSONAL_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
PUBLISH_BRANCH: gh-pages
PUBLISH_DIR: ./site

View File

@@ -1,5 +1,5 @@
run:
deadline: 2m
timeout: 2m
skip-files:
- ".*\\.pb\\.go"
skip-dirs:

View File

@@ -1,5 +1,290 @@
# Changelog
## v1.4.0 (Not Released)
The v1.4.0 is a stability release that brings multiple bug fixes, security, performance enhancements, and multiple usability improvements.
#### New Features
#### Security
A number of security enhancements and features have been implemented (thanks to [@jannfis](https://github.com/jannfis) for driving it! ):
* **Repository Credential Templates Management UI/CLI**. Now you can use Argo CD CLI or UI to configure
[credentials template](https://argoproj.github.io/argo-cd/user-guide/private-repositories/#credential-templates) for multiple repositories!
* **X-Frame-Options header on serving static assets**. The X-Frame-Options prevents third party sites to trick users into interacting with the application.
* **Tighten AppProject RBAC enforcement**. We've improved the enforcement of access rules specified in the
[application project](https://argoproj.github.io/argo-cd/operator-manual/declarative-setup/#projects) configuration.
#### Namespace Isolation
With the namespace isolation feature, you are no longer have to give full read-only cluster access to the Argo CD. Instead, you can give access only to selected namespaces with-in
the cluster:
```bash
argocd cluster add <mycluster> --namespace <mynamespace1> --namespace <mynamespace2>
```
This feature is useful if you don't have full cluster access but still want to use Argo CD to manage some cluster namespaces. The feature also improves performance if Argo CD is
used to manage a few namespaces of a large cluster.
#### Reconciliation Performance
The Argo CD no longer fork/exec `kubectl` to apply resource changes in the target cluster or convert resource manifest to the required manifest version. This reduces
CPU and Memory usage of large Argo CD instances.
#### Resources Health based Hook Status
The existing Argo CD [resource hooks](https://argoproj.github.io/argo-cd/user-guide/resource_hooks/) feature allows running custom logic during the syncing process. You can mark
any Kubernetes resource as a hook and Argo CD assess hook status if resource is a `Pod`, `Job` or `Argo Workflow`. In the v1.4.0 release Argo CD is going to leverage resource
[health assessment](https://argoproj.github.io/argo-cd/operator-manual/health/) to get sync hook status. This allows using any custom CRD as a sync hook and leverage custom health
check logic.
#### Manifest Generation
* **Track Helm Charts By Semantic Version**. You've been able to track charts hosted in Git repositories using branches to tags. This is now possible for Helm charts. You no longer
need to choose the exact version, such as v1.4.0 ,instead you can use a semantic version constraint such as v1.4.* and the latest version that matches will be installed.
* **Build Environment Variables**. Feature allows config management tool to get access to app details during manifest generation via
[environment variables](https://argoproj.github.io/argo-cd/user-guide/build-environment/).
* **Git submodules**. Argo CD is going to automatically fetch sub-modules if your repository has `.gitmodules` directory.
#### UI and CLI
* **Improved Resource Tree View**. The Application details page got even prettier. The resource view was tuned to fit more resources into the screen, include more information about
each resource and don't lose usability at the same time.
* **New Account Management CLI Command**. The CLI allows to check which actions are allowed for your account: `argocd account can-i sync applications '*'`
#### Maintenance Tools
The team put more effort into building tools that help to maintain Argo CD itself:
* **Bulk Project Editing**. The `argocd-util` allows to add and remove permissions defined in multiple project roles using one command.
* **More Prometheus Metrics**. A set of additional metrics that contains useful information managed clusters is exposed by application controller.
More documentation and tools are coming in patch releases.
#### Breaking Changes
The Argo CD deletes all **in-flight** hooks if you terminate running sync operation. The hook state assessment change implemented in this release the Argo CD enables detection of
an in-flight state for all Kubernetes resources including `Deployment`, `PVC`, `StatefulSet`, `ReplicaSet` etc. So if you terminate the sync operation that has, for example,
`StatefulSet` hook that is `Progressing` it will be deleted. The long-running jobs are not supposed to be used as a sync hook and you should consider using
[Sync Waves](https://argoproj.github.io/argo-cd/user-guide/sync-waves/) instead.
#### Enhancements
* feat: Add custom healthchecks for cert-manager v0.11.0 (#2689)
* feat: add git submodule support (#2495)
* feat: Add repository credential management API and CLI (addresses #2136) (#2207)
* feat: add support for --additional-headers cli flag (#2467)
* feat: Add support for ssh-with-port repo url (#2866) (#2948)
* feat: Add Time to ApplicationCondition. (#2417)
* feat: Adds `argocd auth can-i` command. Close #2255
* feat: Adds revision history limit. Closes #2790 (#2818)
* feat: Adds support for ARGO_CD_[TARGET_REVISION|REVISION] and pass to Custom Tool/Helm/Jsonnet
* feat: Adds support for Helm charts to be a semver range. Closes #2552 (#2606)
* feat: Adds tracing to key external invocations. (#2811)
* feat: argocd-util should allow editing project policies in bulk (#2615)
* feat: Displays controllerrevsion's revision in the UI. Closes #2306 (#2702)
* feat: Issue #2559 - Add gauge Prometheus metric which represents the number of pending manifest requests. (#2658)
* feat: Make ConvertToVersion maybe 1090% faster on average (#2820)
* feat: namespace isolation (#2839)
* feat: removes redundant mutex usage in controller cache and adds cluster cache metrics (#2898)
* feat: Set X-Frame-Options on serving static assets (#2706) (#2711)
* feat: Simplify using Argo CD without users/SSO/UI (#2688)
* feat: Template Out Data Source in Grafana Dashboard (#2859)
* feat: Updates UI icons. Closes #2625 and #2757 (#2653)
* feat: use editor arguments in InteractiveEditor (#2833)
* feat: Use kubectl apply library instead of forking binary (#2861)
* feat: use resource health for hook status evaluation (#2938)
#### Bug Fixes
- fix: Adds support for /api/v1/account* via HTTP. Fixes #2664 (#2701)
- fix: Allow '@'-character in SSH usernames when connecting a repository (#2612)
- fix: Allow dot in project policy. Closes #2724 (#2755)
- fix: Allow you to sync local Helm apps. Fixes #2741 (#2747)
- fix: Allows Helm parameters that contains arrays or maps. (#2525)
- fix: application-controller doesn't deal with rm/add same cluster gracefully (x509 unknown) (#2389)
- fix: diff local ignore kustomize build options (#2942)
- fix: Ensures that Helm charts are correctly resolved before sync. Fixes #2758 (#2760)
- fix: Fix 'Open application' link when using basehref (#2729)
- fix: fix a bug with cluster add when token secret is not first in list. (#2744)
- fix: fix bug where manifests are not cached. Fixes #2770 (#2771)
- fix: Fixes bug whereby retry does not work for CLI. Fixes #2767 (#2768)
- fix: git contention leads applications into Unknown state (#2877)
- fix: Issue #1944 - Gracefully handle missing cached app state (#2464)
- fix: Issue #2668 - Delete a specified context (#2669)
- fix: Issue #2683 - Make sure app update don't fail due to concurrent modification (#2852)
- fix: Issue #2721 Optimize helm repo querying (#2816)
- fix: Issue #2853 - Improve application env variables/labels editing (#2856)
- fix: Issue 2848 - Application Deployment history panel shows incorrect info for recent releases (#2849)
- fix: Make BeforeHookCreation the default. Fixes #2754 (#2759)
- fix: No error on `argocd app create` in CLI if `--revision` is omitted #2665
- fix: Only delete resources during app delete cascade if permitted to (fixes #2693) (#2695)
- fix: prevent user from seeing/deleting resources not permitted in project (#2908) (#2910)
- fix: self-heal should retry syncing an application after specified delay
- fix: stop logging dex config secrets #(2904) (#2937)
- fix: stop using jsondiffpatch on clientside to render resource difference (#2869)
- fix: Target Revision truncated #2736
- fix: UI should re-trigger SSO login if SSO JWT token expires (#2891)
- fix: update argocd-util import was not working properly (#2939)
#### Contributors
* [@abhishekjiitr](https://github.com/abhishekjiitr)
* [@adamjohnson01](https://github.com/adamjohnson01)
* [@alexec](https://github.com/alexec)
* [@alexmt](https://github.com/alexmt)
* [@binoue](https://github.com/binoue)
* [@cabrinha](https://github.com/cabrinha)
* [@cbanek](https://github.com/cbanek)
* [@dgoodwin](https://github.com/dgoodwin)
* [@jannfis](https://github.com/jannfis)
* [@jessesuen](https://github.com/jessesuen)
* [@masa213f](https://github.com/masa213f)
* [@whs](https://github.com/whs)
## v1.3.4 (2019-12-05)
- #2819 Fixes logging of tracing option in CLI
## v1.3.3 (2019-12-05)
- #2721 High CPU utilisation (5 cores) and spammy logs
## v1.3.2 (2019-12-03)
- #2797 Fix directory traversal edge case and enhance tests
## v1.3.1 (2019-12-02)
- #2664 update account password from API resulted 404
- #2724 Can't use `DNS-1123` compliant app name when creating project role
- #2726 App list does not show chart for Helm app
- #2741 argocd local sync cannot parse kubernetes version
- #2754 BeforeHookCreation should be the default hook
- #2767 Fix bug whereby retry does not work for CLI
- #2770 Always cache miss for manifests
- #1345 argocd-application-controller: can not retrieve list of objects using index : Index with name namespace does not exist
## v1.3.0 (2019-11-13)
#### New Features
##### Helm 1st-Class Support
We know that for many of our users, they want to deploy existing Helm charts using Argo CD. Up until now that has required you to create an Argo CD app in a Git repo that does nothing but point to that chart. Now you can use a Helm chart repository is the same way as a Git repository.
On top of that, we've improved support for Helm apps. The most common types of Helm hooks such as `pre-install` and `post-install` are supported as well as a the delete policy `before-hook-creation` which makes it easier to work with hooks.
https://youtu.be/GP7xtrnNznw
##### Orphan Resources
Some users would like to make sure that resources in a namespace are managed only by Argo CD. So we've introduced the concept of an "orphan resource" - any resource that is in namespace associated with an app, but not managed by Argo CD. This is enabled in the project settings. Once enabled, Argo CD will show in the app view any resources in the app's namepspace that is not mananged by Argo CD.
https://youtu.be/9ZoTevVQf5I
##### Sync Windows
There may be instances when you want to control the times during which an Argo CD app can sync. Sync Windows now gives you the capability to create windows of time in which apps are either allowed or denied the ability to sync. This can apply to both manual and auto-sync, or just auto-sync. The windows are configured at the project level and assigned to apps using app name, namespace or cluster. Wildcards are supported for all fields.
#### Enhancements
* [UI] Add application labels to Applications list and Applications details page (#1099)
* Helm repository as first class Argo CD Application source (#1145)
* Ability to generate a warn/alert when a namespace deviates from the expected state (#1167)
* Improve diff support for resource requests/limits (#1615)
* HTTP API should allow JWT to be passed via Authorization header (#1642)
* Ability to create & upsert projects from spec (#1852)
* Support for in-line block from helm chart values (#1930)
* Request OIDC groups claim if groups scope is not supported (#1956)
* Add a maintenance window for Applications with automated syncing (#1995)
* Support `argocd.argoproj.io/hook-delete-policy: BeforeHookCreation` (#2036)
* Support setting Helm string parameters using CLI/UI (#2078)
* Config management plugin environment variable UI/CLI support (#2203)
* Helm: auto-detect URLs (#2260)
* Helm: UI improvements (#2261)
* Support `helm template --kube-version ` (#2275)
* Use community icons for resources (#2277)
* Make `group` optional for `ignoreDifferences` config (#2298)
* Update Helm docs (#2315)
* Add cluster information into Splunk (#2354)
* argocd list command should have filter options like by project (#2396)
* Add target/current revision to status badge (#2445)
* Update tooling to use Kustomize v3 (#2487)
* Update root `Dockerfile` to use the `hack/install.sh` (#2488)
* Support and document using HPA for repo-server (#2559)
* Upgrade Helm (#2587)
* UI fixes for "Sync Apps" panel. (#2604)
* Upgrade kustomize from v3.1.0 to v3.2.1 (#2609)
* Map helm lifecycle hooks to ArgoCD pre/post/sync hooks (#355)
* [UI] Enhance app creation page with Helm parameters overrides (#1059)
#### Bug Fixes
- failed parsing on parameters with comma (#1660)
- Statefuleset with OnDelete Update Strategy stuck progressing (#1881)
- Warning during secret diffing (#1923)
- Error message "Unable to load data: key is missing" is confusing (#1944)
- OIDC group bindings are truncated (#2006)
- Multiple parallel app syncs causes OOM (#2022)
- Unknown error when setting params with argocd app set on helm app (#2046)
- Endpoint is no longer shown as a child of services (#2060)
- SSH known hosts entry cannot be deleted if contains shell pattern in name (#2099)
- Application 404s on names with periods (#2114)
- Adding certs for hostnames ending with a dot (.) is not possible (#2116)
- Fix `TestHookDeleteBeforeCreation` (#2141)
- v1.2.0-rc1 nil pointer dereference when syncing (#2146)
- Replacing services failure (#2150)
- 1.2.0-rc1 - Authentication Required error in Repo Server (#2152)
- v1.2.0-rc1 Applications List View doesn't work (#2174)
- Manual sync does not trigger Presync hooks (#2185)
- SyncError app condition disappears during app reconciliation (#2192)
- argocd app wait\sync prints 'Unknown' for resources without health (#2198)
- 1.2.0-rc2 Warning during secret diffing (#2206)
- SSO redirect url is incorrect if configured Argo CD URL has trailing slash (#2212)
- Application summary diff page shows hooks (#2215)
- An app with a single resource and Sync hook remains progressing (#2216)
- CONTRIBUTING documentation outdated (#2231)
- v1.2.0-rc2 does not retrieve http(s) based git repository behind the proxy (#2243)
- Intermittent "git ls-remote" request failures should not fail app reconciliation (#2245)
- Result of ListApps operation for Git repo is cached incorrectly (#2263)
- ListApps does not utilize cache (#2287)
- Controller panics due to nil pointer error (#2290)
- The Helm --kube-version support does not work on GKE: (#2303)
- Fixes bug that prevents you creating repos via UI/CLI. (#2308)
- The 'helm.repositories' settings is dropped without migration path (#2316)
- Badge response does not contain cache control header (#2317)
- Inconsistent sync result from UI and CLI (#2321)
- Failed edit application with plugin type requiring environment (#2330)
- AutoSync doesn't work anymore (#2339)
- End-to-End tests not working with Kubernetes v1.16 (#2371)
- Creating an application from Helm repository should select "Helm" as source type (#2378)
- The parameters of ValidateAccess GRPC method should not be logged (#2386)
- Maintenance window meaning is confusing (#2398)
- UI bug when targetRevision is ommited (#2407)
- Too many vulnerabilities in Docker image (#2425)
- proj windows commands not consistent with other commands (#2443)
- Custom resource actions cannot be executed from the UI (#2448)
- Application controller sometimes accidentally removes duplicated/excluded resource warning condition (#2453)
- Logic that checks sync windows state in the cli is incorrect (#2455)
- UI don't allow to create window with `* * * * *` schedule (#2475)
- Helm Hook is executed twice if annotated with both pre-install and pre-upgrade annotations (#2480)
- Impossible to edit chart name using App details page (#2484)
- ArgoCD does not provide CSRF protection (#2496)
- ArgoCD failing to install CRDs in master from Helm Charts (#2497)
- Timestamp in Helm package file name causes error in Application with Helm source (#2549)
- Attempting to create a repo with password but not username panics (#2567)
- UI incorrectly mark resources as `Required Pruning` (#2577)
- argocd app diff prints only first difference (#2616)
- Bump min client cache version (#2619)
- Cluster list page fails if any cluster is not reachable (#2620)
- Repository type should be mandatory for repo add command in CLI (#2622)
- Repo server executes unnecessary ls-remotes (#2626)
- Application list page incorrectly filter apps by label selector (#2633)
- Custom actions are disabled in Argo CD UI (#2635)
- Failure of `argocd version` in the self-building container image (#2645)
- Application list page is not updated automatically anymore (#2655)
- Login regression issues (#2659)
- Regression: Cannot return Kustomize version for 3.1.0 (#2662)
- API server does not allow creating role with action `action/*` (#2670)
- Application controller `kubectl-parallelism-limit` flag is broken (#2673)
- Annoying toolbar flickering (#2691)
## v1.2.4 (2019-10-23)
- Issue #2185 - Manual sync don't trigger hooks (#2477)
- Issue #2339 - Controller should compare with latest git revision if app has changed (#2543)
- Unknown child app should not affect app health (#2544)
- Redact secrets in dex logs (#2538)
## v1.2.3 (2019-10-1)
* Make argo-cd docker images openshift friendly (#2362) (@duboisf)
* Add dest-server and dest-namespace field to reconciliation logs (#2354)

View File

@@ -23,47 +23,16 @@ RUN apt-get update && apt-get install -y \
WORKDIR /tmp
# Install dep
ENV DEP_VERSION=0.5.0
RUN wget https://github.com/golang/dep/releases/download/v${DEP_VERSION}/dep-linux-amd64 -O /usr/local/bin/dep && \
chmod +x /usr/local/bin/dep
ADD hack/install.sh .
ADD hack/installers installers
# Install packr
ENV PACKR_VERSION=1.21.9
RUN wget https://github.com/gobuffalo/packr/releases/download/v${PACKR_VERSION}/packr_${PACKR_VERSION}_linux_amd64.tar.gz && \
tar -vxf packr*.tar.gz -C /tmp/ && \
mv /tmp/packr /usr/local/bin/packr
# Install kubectl
# NOTE: keep the version synced with https://storage.googleapis.com/kubernetes-release/release/stable.txt
ENV KUBECTL_VERSION=1.14.0
RUN curl -L -o /usr/local/bin/kubectl -LO https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl && \
chmod +x /usr/local/bin/kubectl && \
kubectl version --client
# Install ksonnet
ENV KSONNET_VERSION=0.13.1
RUN wget https://github.com/ksonnet/ksonnet/releases/download/v${KSONNET_VERSION}/ks_${KSONNET_VERSION}_linux_amd64.tar.gz && \
tar -C /tmp/ -xf ks_${KSONNET_VERSION}_linux_amd64.tar.gz && \
mv /tmp/ks_${KSONNET_VERSION}_linux_amd64/ks /usr/local/bin/ks && \
ks version
# Install helm
ENV HELM_VERSION=2.12.1
RUN wget https://storage.googleapis.com/kubernetes-helm/helm-v${HELM_VERSION}-linux-amd64.tar.gz && \
tar -C /tmp/ -xf helm-v${HELM_VERSION}-linux-amd64.tar.gz && \
mv /tmp/linux-amd64/helm /usr/local/bin/helm && \
helm version --client
ENV KUSTOMIZE_VERSION=3.1.0
RUN curl -L -o /usr/local/bin/kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/v${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64 && \
chmod +x /usr/local/bin/kustomize && \
kustomize version
# Install AWS IAM Authenticator
ENV AWS_IAM_AUTHENTICATOR_VERSION=0.4.0-alpha.1
RUN curl -L -o /usr/local/bin/aws-iam-authenticator https://github.com/kubernetes-sigs/aws-iam-authenticator/releases/download/${AWS_IAM_AUTHENTICATOR_VERSION}/aws-iam-authenticator_${AWS_IAM_AUTHENTICATOR_VERSION}_linux_amd64 && \
chmod +x /usr/local/bin/aws-iam-authenticator
RUN ./install.sh dep-linux
RUN ./install.sh packr-linux
RUN ./install.sh kubectl-linux
RUN ./install.sh ksonnet-linux
RUN ./install.sh helm-linux
RUN ./install.sh kustomize-linux
RUN ./install.sh aws-iam-authenticator-linux
####################################################################################################
# Argo CD Base - used as the base for both the release and dev argocd images

472
Gopkg.lock generated
View File

@@ -25,6 +25,14 @@
revision = "d216395917cc49052c7c7094cf57f09657ca08a8"
version = "v3.0.0"
[[projects]]
digest = "1:63e57618d792cccb87ad7cb8a0602e6205732beb3b01b0ea858fc4a5fd3ce8f1"
name = "github.com/MakeNowJust/heredoc"
packages = ["."]
pruneopts = ""
revision = "efb6ca8de9d5385c3963279701760e37637cf238"
version = "v2.0.1"
[[projects]]
digest = "1:b856d8248663c39265a764561c1a1a149783f6cc815feb54a1f3a591b91f6eca"
name = "github.com/Masterminds/semver"
@@ -58,15 +66,7 @@
[[projects]]
branch = "master"
digest = "1:0caf9208419fa5db5a0ca7112affaa9550c54291dda8e2abac0c0e76181c959e"
name = "github.com/argoproj/argo"
packages = ["util"]
pruneopts = ""
revision = "7ef1cea68c94f7f0e1e2f8bd75bedc5a7df8af90"
[[projects]]
branch = "master"
digest = "1:4f6afcf4ebe041b3d4aa7926d09344b48d2f588e1f957526bbbe54f9cbb366a1"
digest = "1:52905b00a73cda93a2ce8c5fa35185daed673d59e39576e81ad6ab6fb7076b3c"
name = "github.com/argoproj/pkg"
packages = [
"errors",
@@ -75,7 +75,7 @@
"time",
]
pruneopts = ""
revision = "38dba6e98495680ff1f8225642b63db10a96bb06"
revision = "02a6aac40ac4cd23de448afe7a1ec0ba4b6d2b96"
[[projects]]
digest = "1:d8a2bb36a048d1571bcc1aee208b61f39dc16c6c53823feffd37449dde162507"
@@ -94,12 +94,14 @@
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
[[projects]]
digest = "1:e04162bd6a6d4950541bae744c968108e14913b1cebccf29f7650b573f44adb3"
digest = "1:6e2b0748ea11cffebe87b4a671a44ecfb243141cdd5df54cb44b7e8e93cb7ea3"
name = "github.com/casbin/casbin"
packages = [
".",
"config",
"effect",
"errors",
"log",
"model",
"persist",
"persist/file-adapter",
@@ -108,8 +110,21 @@
"util",
]
pruneopts = ""
revision = "d71629e497929858300c38cd442098c178121c30"
version = "v1.5.0"
revision = "aaed1b7a7eac65d37ec4e15e308429fdf0bd6a9e"
version = "v1.9.1"
[[projects]]
branch = "master"
digest = "1:9c19f8c33e635e0439c8afc167d6d02e3aa6eea5b69d64880244fd354a99edc4"
name = "github.com/chai2010/gettext-go"
packages = [
"gettext",
"gettext/mo",
"gettext/plural",
"gettext/po",
]
pruneopts = ""
revision = "bf70f2a70fb1b1f36d90d671a72795984eab0fcb"
[[projects]]
branch = "v2"
@@ -135,6 +150,17 @@
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
version = "v3.2.0"
[[projects]]
digest = "1:c05f1899f086e3b4613d94d9e6f7ba6f4b6587498a1aa6037c5c294b22f5a743"
name = "github.com/docker/distribution"
packages = [
"digestset",
"reference",
]
pruneopts = ""
revision = "2461543d988979529609e8cb6fca9ca190dc48da"
version = "v2.7.1"
[[projects]]
digest = "1:b021ef379356343bdc13ec101e546b756fcef4b1186d08163bef7d3bc8c1e07f"
name = "github.com/docker/docker"
@@ -192,12 +218,28 @@
version = "v1.9.0"
[[projects]]
digest = "1:4216202f4088a73e2982df875e2f0d1401137bbc248e57391e70547af167a18a"
digest = "1:46ddeb9dd35d875ac7568c4dc1fc96ce424e034bdbb984239d8ffc151398ec01"
name = "github.com/evanphx/json-patch"
packages = ["."]
pruneopts = ""
revision = "72bf35d0ff611848c1dc9df0f976c81192392fa5"
version = "v4.1.0"
revision = "026c730a0dcc5d11f93f1cf1cc65b01247ea7b6f"
version = "v4.5.0"
[[projects]]
branch = "master"
digest = "1:549f95037fea25e00a5341ac6a169a5b3e5306be107f45260440107b779b74f9"
name = "github.com/exponent-io/jsonpath"
packages = ["."]
pruneopts = ""
revision = "d6023ce2651d8eafb5c75bb0c7167536102ec9f5"
[[projects]]
digest = "1:23a5efa4b272df86a8ebffc942f5e0c1aac4b750836037394cc450b6d91e241a"
name = "github.com/fatih/camelcase"
packages = ["."]
pruneopts = ""
revision = "44e46d280b43ec1531bb25252440e34f1b800b65"
version = "v1.0.0"
[[projects]]
digest = "1:b13707423743d41665fd23f0c36b2f37bb49c30e94adb813319c44188a51ba22"
@@ -205,6 +247,7 @@
packages = ["."]
pruneopts = ""
revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
version = "v1.0.0"
[[projects]]
branch = "master"
@@ -356,7 +399,7 @@
revision = "5a05380e4bc2440e0ec12f54f6f45648dbdd5e55"
[[projects]]
digest = "1:6e73003ecd35f4487a5e88270d3ca0a81bc80dc88053ac7e4dcfec5fba30d918"
digest = "1:d69d2ba23955582a64e367ff2b0808cdbd048458c178cea48f11ab8c40bd7aea"
name = "github.com/gogo/protobuf"
packages = [
"gogoproto",
@@ -389,8 +432,8 @@
"vanity/command",
]
pruneopts = ""
revision = "636bf0302bc95575d69441b25a2603156ffdddf1"
version = "v1.1.1"
revision = "5628607bb4c51c3157aacc3a50f0ab707582b805"
version = "v1.3.1"
[[projects]]
branch = "master"
@@ -423,6 +466,28 @@
revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5"
version = "v1.2.0"
[[projects]]
digest = "1:1e5b1e14524ed08301977b7b8e10c719ed853cbf3f24ecb66fae783a46f207a6"
name = "github.com/google/btree"
packages = ["."]
pruneopts = ""
revision = "4030bb1f1f0c35b30ca7009e9ebd06849dd45306"
version = "v1.0.0"
[[projects]]
digest = "1:9fcb267c272bc5054564b392e3ff7e65e35400fd9914afb1d169f92b95e7dbc9"
name = "github.com/google/go-cmp"
packages = [
"cmp",
"cmp/internal/diff",
"cmp/internal/flags",
"cmp/internal/function",
"cmp/internal/value",
]
pruneopts = ""
revision = "2d0692c2e9617365a95b295612ac0d4415ba4627"
version = "v0.3.1"
[[projects]]
digest = "1:14d826ee25139b4674e9768ac287a135f4e7c14e1134a5b15e4e152edfd49f41"
name = "github.com/google/go-jsonnet"
@@ -470,6 +535,17 @@
revision = "66b9c49e59c6c48f0ffce28c2d8b8a5678502c6d"
version = "v1.4.0"
[[projects]]
branch = "master"
digest = "1:e1fd67b5695fb12f54f979606c5d650a5aa72ef242f8e71072bfd4f7b5a141a0"
name = "github.com/gregjones/httpcache"
packages = [
".",
"diskcache",
]
pruneopts = ""
revision = "901d90724c7919163f472a9812253fb26761123d"
[[projects]]
branch = "master"
digest = "1:9dca8c981b8aed7448d94e78bc68a76784867a38b3036d5aabc0b32d92ffd1f4"
@@ -560,6 +636,14 @@
pruneopts = ""
revision = "d14ea06fba99483203c19d92cfcd13ebe73135f4"
[[projects]]
digest = "1:302ad9379eb146668760df4d779a95379acab43ce5f9a28f27f3273f98232020"
name = "github.com/jonboulle/clockwork"
packages = ["."]
pruneopts = ""
revision = "2eee05ed794112d45db504eb05aa693efd2b8b09"
version = "v0.1.0"
[[projects]]
digest = "1:31c6f3c4f1e15fcc24fcfc9f5f24603ff3963c56d6fa162116493b4025fb6acc"
name = "github.com/json-iterator/go"
@@ -591,6 +675,14 @@
pruneopts = ""
revision = "b729f2633dfe35f4d1d8a32385f6685610ce1cb5"
[[projects]]
branch = "master"
digest = "1:93018a4331df9925058905133cb997aec8f54d5303f4536a23e49b5648632d06"
name = "github.com/liggitt/tabwriter"
packages = ["."]
pruneopts = ""
revision = "89fcab3d43de07060e4fd4c1547430ed57e87f24"
[[projects]]
branch = "master"
digest = "1:ccc20cacf54eb16464dad02efa1c14fa7c0b9e124639b0d2a51dcc87b0154e4c"
@@ -651,6 +743,14 @@
revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd"
version = "1.0.1"
[[projects]]
digest = "1:5d9b668b0b4581a978f07e7d2e3314af18eb27b3fb5d19b70185b7c575723d11"
name = "github.com/opencontainers/go-digest"
packages = ["."]
pruneopts = ""
revision = "279bed98673dd5bef374d3b6e4b09e2af76183bf"
version = "v1.0.0-rc1"
[[projects]]
digest = "1:4c0404dc03d974acd5fcd8b8d3ce687b13bd169db032b89275e8b9d77b98ce8c"
name = "github.com/patrickmn/go-cache"
@@ -667,6 +767,22 @@
revision = "c37440a7cf42ac63b919c752ca73a85067e05992"
version = "v0.2.0"
[[projects]]
branch = "master"
digest = "1:5f0faa008e8ff4221b55a1a5057c8b02cb2fd68da6a65c9e31c82b72cbc836d0"
name = "github.com/petar/GoLLRB"
packages = ["llrb"]
pruneopts = ""
revision = "33fb24c13b99c46c93183c291836c573ac382536"
[[projects]]
digest = "1:4709c61d984ef9ba99b037b047546d8a576ae984fb49486e48d99658aa750cd5"
name = "github.com/peterbourgon/diskv"
packages = ["."]
pruneopts = ""
revision = "0be1b92a6df0e4f5cb0a5d15fb7f643d0ad93ce6"
version = "v3.0.0"
[[projects]]
digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca"
name = "github.com/pkg/errors"
@@ -754,6 +870,14 @@
revision = "9a47f48565a795472d43519dd49aac781f3034fb"
version = "v1.6.0"
[[projects]]
digest = "1:2761e287c811d0948d47d0252b82281eca3801eb3c9d5f9530956643d5b9f430"
name = "github.com/russross/blackfriday"
packages = ["."]
pruneopts = ""
revision = "05f3235734ad95d0016f6a23902f06461fcf567a"
version = "v1.5.2"
[[projects]]
digest = "1:3962f553b77bf6c03fc07cd687a22dd3b00fe11aa14d31194f5505f5bb65cdc8"
name = "github.com/sergi/go-diff"
@@ -790,11 +914,11 @@
version = "v0.1.4"
[[projects]]
digest = "1:9ba49264cef4386aded205f9cb5b1f2d30f983d7dc37a21c780d9db3edfac9a7"
digest = "1:0c63b3c7ad6d825a898f28cb854252a3b29d37700c68a117a977263f5ec94efe"
name = "github.com/spf13/cobra"
packages = ["."]
pruneopts = ""
revision = "fe5e611709b0c57fa4a89136deaa8e1d4004d053"
revision = "0.0.5"
[[projects]]
digest = "1:8e243c568f36b09031ec18dff5f7d2769dcf5ca4d624ea511c8e3197dc3d352d"
@@ -1241,11 +1365,13 @@
version = "v2.2.2"
[[projects]]
branch = "release-1.14"
digest = "1:d8a6f1ec98713e685346a2e4b46c6ec4a1792a5535f8b0dffe3b1c08c9d69b12"
branch = "release-1.16"
digest = "1:5e5cfbab57ea5444c1eb295a39fdc403f097f5ace592c829db7b3e0e3ea66903"
name = "k8s.io/api"
packages = [
"admission/v1",
"admission/v1beta1",
"admissionregistration/v1",
"admissionregistration/v1beta1",
"apps/v1",
"apps/v1beta1",
@@ -1265,6 +1391,7 @@
"coordination/v1",
"coordination/v1beta1",
"core/v1",
"discovery/v1alpha1",
"events/v1beta1",
"extensions/v1beta1",
"imagepolicy/v1alpha1",
@@ -1285,34 +1412,41 @@
"storage/v1beta1",
]
pruneopts = ""
revision = "40a48860b5abbba9aa891b02b32da429b08d96a0"
revision = "195af9ec35214c6d98662c5791364285bf2e2cf2"
[[projects]]
branch = "master"
digest = "1:49e0fcdcaeaf937c6c608d1da19eb80de74fe990021278d49d46e10288659be6"
branch = "release-1.16"
digest = "1:7f29d62c07c68767171cf2ed8598e0cb862b99584bb8beb93189e2ed00ac520e"
name = "k8s.io/apiextensions-apiserver"
packages = [
"pkg/apis/apiextensions",
"pkg/apis/apiextensions/v1",
"pkg/apis/apiextensions/v1beta1",
"pkg/client/clientset/clientset",
"pkg/client/clientset/clientset/scheme",
"pkg/client/clientset/clientset/typed/apiextensions/v1",
"pkg/client/clientset/clientset/typed/apiextensions/v1beta1",
"pkg/features",
]
pruneopts = ""
revision = "7f7d2b94eca3a7a1c49840e119a8bc03c3afb1e3"
revision = "07afe84a85e43cf2503133660c424a0b594b21db"
[[projects]]
branch = "release-1.14"
digest = "1:a802c91b189a31200cfb66744441fe62dac961ec7c5c58c47716570de7da195c"
branch = "release-1.16"
digest = "1:36db89a45a8cb3d565f7ebfd67dafd42c9c0bbb80d6bbd4991629b39b02a4c64"
name = "k8s.io/apimachinery"
packages = [
"pkg/api/equality",
"pkg/api/errors",
"pkg/api/meta",
"pkg/api/resource",
"pkg/api/validation",
"pkg/api/validation/path",
"pkg/apis/meta/internalversion",
"pkg/apis/meta/v1",
"pkg/apis/meta/v1/unstructured",
"pkg/apis/meta/v1/unstructured/unstructuredscheme",
"pkg/apis/meta/v1/validation",
"pkg/apis/meta/v1beta1",
"pkg/conversion",
"pkg/conversion/queryparams",
@@ -1331,12 +1465,14 @@
"pkg/util/cache",
"pkg/util/clock",
"pkg/util/diff",
"pkg/util/duration",
"pkg/util/errors",
"pkg/util/framer",
"pkg/util/httpstream",
"pkg/util/httpstream/spdy",
"pkg/util/intstr",
"pkg/util/json",
"pkg/util/jsonmergepatch",
"pkg/util/mergepatch",
"pkg/util/naming",
"pkg/util/net",
@@ -1355,14 +1491,51 @@
"third_party/forked/golang/reflect",
]
pruneopts = ""
revision = "6a84e37a896db9780c75367af8d2ed2bb944022e"
revision = "72ed19daf4bb788ae595ae4103c404cb0fa09c84"
[[projects]]
branch = "release-11.0"
digest = "1:794140b3ac07405646ea3d4a57e1f6155186e672aed8aa0c996779381cd92fe6"
branch = "release-1.16"
digest = "1:4e236f3f94cfc5f005ceb143948ad39a4b2ad10373f394b232838f797bddd6ef"
name = "k8s.io/apiserver"
packages = [
"pkg/apis/audit",
"pkg/authentication/serviceaccount",
"pkg/authentication/user",
"pkg/endpoints/request",
"pkg/features",
"pkg/util/feature",
]
pruneopts = ""
revision = "ebfe712c1fff40c4800d779470515e6025eda218"
[[projects]]
branch = "release-1.16"
digest = "1:b46a88b317c3187b6fa7c5351eca48b35aad182eee371168677747430ff955bb"
name = "k8s.io/cli-runtime"
packages = [
"pkg/genericclioptions",
"pkg/kustomize",
"pkg/kustomize/k8sdeps",
"pkg/kustomize/k8sdeps/configmapandsecret",
"pkg/kustomize/k8sdeps/kunstruct",
"pkg/kustomize/k8sdeps/kv",
"pkg/kustomize/k8sdeps/transformer",
"pkg/kustomize/k8sdeps/transformer/hash",
"pkg/kustomize/k8sdeps/transformer/patch",
"pkg/kustomize/k8sdeps/validator",
"pkg/printers",
"pkg/resource",
]
pruneopts = ""
revision = "6bff60de437070d7e8644b7a930837d5de512240"
[[projects]]
branch = "release-13.0"
digest = "1:84f90f6a3b5b16f2c57164c5281d302b2647da8f77aa9cb14d5ebeb17fccc25e"
name = "k8s.io/client-go"
packages = [
"discovery",
"discovery/cached/disk",
"discovery/fake",
"dynamic",
"dynamic/fake",
@@ -1371,6 +1544,8 @@
"kubernetes",
"kubernetes/fake",
"kubernetes/scheme",
"kubernetes/typed/admissionregistration/v1",
"kubernetes/typed/admissionregistration/v1/fake",
"kubernetes/typed/admissionregistration/v1beta1",
"kubernetes/typed/admissionregistration/v1beta1/fake",
"kubernetes/typed/apps/v1",
@@ -1409,6 +1584,8 @@
"kubernetes/typed/coordination/v1beta1/fake",
"kubernetes/typed/core/v1",
"kubernetes/typed/core/v1/fake",
"kubernetes/typed/discovery/v1alpha1",
"kubernetes/typed/discovery/v1alpha1/fake",
"kubernetes/typed/events/v1beta1",
"kubernetes/typed/events/v1beta1/fake",
"kubernetes/typed/extensions/v1beta1",
@@ -1453,6 +1630,15 @@
"plugin/pkg/client/auth/oidc",
"rest",
"rest/watch",
"restmapper",
"scale",
"scale/scheme",
"scale/scheme/appsint",
"scale/scheme/appsv1beta1",
"scale/scheme/appsv1beta2",
"scale/scheme/autoscalingv1",
"scale/scheme/extensionsint",
"scale/scheme/extensionsv1beta1",
"testing",
"third_party/forked/golang/template",
"tools/auth",
@@ -1463,8 +1649,10 @@
"tools/clientcmd/api/v1",
"tools/metrics",
"tools/pager",
"tools/portforward",
"tools/reference",
"tools/remotecommand",
"tools/watch",
"transport",
"transport/spdy",
"util/cert",
@@ -1478,11 +1666,11 @@
"util/workqueue",
]
pruneopts = ""
revision = "11646d1007e006f6f24995cb905c68bc62901c81"
revision = "85029d69edeae82e97dd1a0de3b24668cee9a15d"
[[projects]]
branch = "release-1.14"
digest = "1:742ce70d2c6de0f02b5331a25d4d549f55de6b214af22044455fd6e6b451cad9"
branch = "release-1.16"
digest = "1:254da4cb69b3776686b730a206e081e6f8898bb64760619d1895c25c407e718f"
name = "k8s.io/code-generator"
packages = [
"cmd/go-to-protobuf",
@@ -1491,7 +1679,15 @@
"third_party/forked/golang/reflect",
]
pruneopts = ""
revision = "50b561225d70b3eb79a1faafd3dfe7b1a62cbe73"
revision = "8e001e5d18949be7e823ccb9cfe9b60026e7bda0"
[[projects]]
branch = "master"
digest = "1:06c18e328063f3612dfda3c4c5e5b8becda1eabceca689335c8d98704dffe70a"
name = "k8s.io/component-base"
packages = ["featuregate"]
pruneopts = ""
revision = "435ce712a6949916fa293dc4d3d49429962043d8"
[[projects]]
branch = "master"
@@ -1529,7 +1725,7 @@
revision = "e80910364765199a4baebd4dec54c885fe52b680"
[[projects]]
digest = "1:42ea993b351fdd39b9aad3c9ebe71f2fdb5d1f8d12eed24e71c3dff1a31b2a43"
digest = "1:16a343bd9d820ae320de4d1eaa8acc7a214aac4b38fb21d03255d3a457d861df"
name = "k8s.io/kube-openapi"
packages = [
"cmd/openapi-gen",
@@ -1538,41 +1734,145 @@
"pkg/generators",
"pkg/generators/rules",
"pkg/util/proto",
"pkg/util/proto/validation",
"pkg/util/sets",
]
pruneopts = ""
revision = "411b2483e5034420675ebcdd4a55fc76fe5e55cf"
revision = "30be4d16710ac61bce31eb28a01054596fe6a9f1"
[[projects]]
branch = "release-1.14"
digest = "1:78aa6079e011ece0d28513c7fe1bd64284fa9eb5d671760803a839ffdf0e9e38"
name = "k8s.io/kubernetes"
branch = "release-1.16"
digest = "1:687af22932f9b53ff2e6755b2eefe160f076d522794abb980f0ddb187bcefacd"
name = "k8s.io/kubectl"
packages = [
"pkg/api/v1/pod",
"pkg/apis/apps",
"pkg/apis/autoscaling",
"pkg/apis/batch",
"pkg/apis/core",
"pkg/kubectl/scheme",
"pkg/kubectl/util/term",
"pkg/cmd/apply",
"pkg/cmd/delete",
"pkg/cmd/util",
"pkg/cmd/util/editor",
"pkg/cmd/util/editor/crlf",
"pkg/cmd/wait",
"pkg/describe",
"pkg/describe/versioned",
"pkg/generated",
"pkg/rawhttp",
"pkg/scheme",
"pkg/util",
"pkg/util/certificate",
"pkg/util/deployment",
"pkg/util/event",
"pkg/util/fieldpath",
"pkg/util/i18n",
"pkg/util/interrupt",
"pkg/util/node",
"pkg/util/openapi",
"pkg/util/openapi/validation",
"pkg/util/printers",
"pkg/util/qos",
"pkg/util/rbac",
"pkg/util/resource",
"pkg/util/slice",
"pkg/util/storage",
"pkg/util/templates",
"pkg/util/term",
"pkg/validation",
"pkg/version",
]
pruneopts = ""
revision = "2d20b5759406ded89f8b25cf085ff4733b144ba5"
revision = "14647fd13a8b4cffc5a8f327b0018e037f72e4e8"
[[projects]]
branch = "release-1.16"
digest = "1:02241e5570c239d31e52955b1a8e6d603a35fd6542d14e98882fb6c3c4ef3d56"
name = "k8s.io/kubernetes"
packages = [
"pkg/api/legacyscheme",
"pkg/api/v1/pod",
"pkg/apis/apps",
"pkg/apis/apps/install",
"pkg/apis/apps/v1",
"pkg/apis/apps/v1beta1",
"pkg/apis/apps/v1beta2",
"pkg/apis/authentication",
"pkg/apis/authentication/install",
"pkg/apis/authentication/v1",
"pkg/apis/authentication/v1beta1",
"pkg/apis/authorization",
"pkg/apis/authorization/install",
"pkg/apis/authorization/v1",
"pkg/apis/authorization/v1beta1",
"pkg/apis/autoscaling",
"pkg/apis/autoscaling/install",
"pkg/apis/autoscaling/v1",
"pkg/apis/autoscaling/v2beta1",
"pkg/apis/autoscaling/v2beta2",
"pkg/apis/batch",
"pkg/apis/batch/install",
"pkg/apis/batch/v1",
"pkg/apis/batch/v1beta1",
"pkg/apis/batch/v2alpha1",
"pkg/apis/certificates",
"pkg/apis/certificates/install",
"pkg/apis/certificates/v1beta1",
"pkg/apis/coordination",
"pkg/apis/coordination/install",
"pkg/apis/coordination/v1",
"pkg/apis/coordination/v1beta1",
"pkg/apis/core",
"pkg/apis/core/install",
"pkg/apis/core/v1",
"pkg/apis/events",
"pkg/apis/events/install",
"pkg/apis/events/v1beta1",
"pkg/apis/extensions",
"pkg/apis/extensions/install",
"pkg/apis/extensions/v1beta1",
"pkg/apis/networking",
"pkg/apis/policy",
"pkg/apis/policy/install",
"pkg/apis/policy/v1beta1",
"pkg/apis/rbac",
"pkg/apis/rbac/install",
"pkg/apis/rbac/v1",
"pkg/apis/rbac/v1alpha1",
"pkg/apis/rbac/v1beta1",
"pkg/apis/scheduling",
"pkg/apis/scheduling/install",
"pkg/apis/scheduling/v1",
"pkg/apis/scheduling/v1alpha1",
"pkg/apis/scheduling/v1beta1",
"pkg/apis/settings",
"pkg/apis/settings/install",
"pkg/apis/settings/v1alpha1",
"pkg/apis/storage",
"pkg/apis/storage/install",
"pkg/apis/storage/v1",
"pkg/apis/storage/v1alpha1",
"pkg/apis/storage/v1beta1",
"pkg/features",
"pkg/kubectl/cmd/auth",
"pkg/registry/rbac/reconciliation",
"pkg/registry/rbac/validation",
"pkg/util/node",
"pkg/util/parsers",
"pkg/util/slice",
"pkg/util/workqueue/prometheus",
]
pruneopts = ""
revision = "bfafae8f1c2fdf3c3cfef04674db028531a7c098"
[[projects]]
branch = "master"
digest = "1:4c5d39f7ca1c940d7e74dbc62d2221e2c59b3d35c54f1fa9c77f3fd3113bbcb1"
digest = "1:a8a2e6bbef691323b833d0eb11bb0e570e7eb9619ac76f7b11265530e1cac922"
name = "k8s.io/utils"
packages = [
"buffer",
"exec",
"integer",
"net",
"pointer",
"trace",
]
pruneopts = ""
revision = "c55fbcfc754a5b2ec2fbae8fb9dcac36bdba6a12"
revision = "6ca3b61696b65b0e81f1a39b4937fc2d2994ed6a"
[[projects]]
branch = "master"
@@ -1582,6 +1882,37 @@
pruneopts = ""
revision = "97fed8db84274c421dbfffbb28ec859901556b97"
[[projects]]
digest = "1:0b2daace3dcced8712072529b621360cf520f3c2ead92d755f35a0ec8dca2714"
name = "sigs.k8s.io/kustomize"
packages = [
"pkg/commands/build",
"pkg/constants",
"pkg/expansion",
"pkg/factory",
"pkg/fs",
"pkg/git",
"pkg/gvk",
"pkg/ifc",
"pkg/ifc/transformer",
"pkg/image",
"pkg/internal/error",
"pkg/loader",
"pkg/patch",
"pkg/patch/transformer",
"pkg/resid",
"pkg/resmap",
"pkg/resource",
"pkg/target",
"pkg/transformers",
"pkg/transformers/config",
"pkg/transformers/config/defaultconfig",
"pkg/types",
]
pruneopts = ""
revision = "a6f65144121d1955266b0cd836ce954c04122dc8"
version = "v2.0.3"
[[projects]]
digest = "1:321081b4a44256715f2b68411d8eda9a17f17ebfe6f0cc61d2cc52d11c08acfa"
name = "sigs.k8s.io/yaml"
@@ -1597,13 +1928,11 @@
"bou.ke/monkey",
"github.com/Masterminds/semver",
"github.com/TomOnTime/utfutil",
"github.com/argoproj/argo/util",
"github.com/argoproj/pkg/errors",
"github.com/argoproj/pkg/exec",
"github.com/argoproj/pkg/time",
"github.com/casbin/casbin",
"github.com/casbin/casbin/model",
"github.com/casbin/casbin/persist",
"github.com/coreos/go-oidc",
"github.com/dgrijalva/jwt-go",
"github.com/dustin/go-humanize",
@@ -1692,6 +2021,7 @@
"k8s.io/api/batch/v1",
"k8s.io/api/core/v1",
"k8s.io/api/extensions/v1beta1",
"k8s.io/api/networking/v1beta1",
"k8s.io/api/rbac/v1",
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1",
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset",
@@ -1706,10 +2036,14 @@
"k8s.io/apimachinery/pkg/runtime/serializer",
"k8s.io/apimachinery/pkg/selection",
"k8s.io/apimachinery/pkg/types",
"k8s.io/apimachinery/pkg/util/intstr",
"k8s.io/apimachinery/pkg/util/jsonmergepatch",
"k8s.io/apimachinery/pkg/util/runtime",
"k8s.io/apimachinery/pkg/util/strategicpatch",
"k8s.io/apimachinery/pkg/util/wait",
"k8s.io/apimachinery/pkg/watch",
"k8s.io/cli-runtime/pkg/genericclioptions",
"k8s.io/cli-runtime/pkg/printers",
"k8s.io/client-go/discovery",
"k8s.io/client-go/discovery/fake",
"k8s.io/client-go/dynamic",
@@ -1717,6 +2051,7 @@
"k8s.io/client-go/informers/core/v1",
"k8s.io/client-go/kubernetes",
"k8s.io/client-go/kubernetes/fake",
"k8s.io/client-go/kubernetes/scheme",
"k8s.io/client-go/listers/core/v1",
"k8s.io/client-go/plugin/pkg/client/auth/gcp",
"k8s.io/client-go/plugin/pkg/client/auth/oidc",
@@ -1725,6 +2060,8 @@
"k8s.io/client-go/tools/cache",
"k8s.io/client-go/tools/clientcmd",
"k8s.io/client-go/tools/clientcmd/api",
"k8s.io/client-go/tools/portforward",
"k8s.io/client-go/transport/spdy",
"k8s.io/client-go/util/flowcontrol",
"k8s.io/client-go/util/workqueue",
"k8s.io/code-generator/cmd/go-to-protobuf",
@@ -1733,13 +2070,32 @@
"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1",
"k8s.io/kube-openapi/cmd/openapi-gen",
"k8s.io/kube-openapi/pkg/common",
"k8s.io/kubectl/pkg/cmd/apply",
"k8s.io/kubectl/pkg/cmd/util",
"k8s.io/kubectl/pkg/scheme",
"k8s.io/kubectl/pkg/util/term",
"k8s.io/kubernetes/pkg/api/legacyscheme",
"k8s.io/kubernetes/pkg/api/v1/pod",
"k8s.io/kubernetes/pkg/apis/apps",
"k8s.io/kubernetes/pkg/apis/batch",
"k8s.io/kubernetes/pkg/apis/apps/install",
"k8s.io/kubernetes/pkg/apis/authentication/install",
"k8s.io/kubernetes/pkg/apis/authorization/install",
"k8s.io/kubernetes/pkg/apis/autoscaling/install",
"k8s.io/kubernetes/pkg/apis/batch/install",
"k8s.io/kubernetes/pkg/apis/certificates/install",
"k8s.io/kubernetes/pkg/apis/coordination/install",
"k8s.io/kubernetes/pkg/apis/core",
"k8s.io/kubernetes/pkg/kubectl/scheme",
"k8s.io/kubernetes/pkg/kubectl/util/term",
"k8s.io/kubernetes/pkg/apis/core/install",
"k8s.io/kubernetes/pkg/apis/events/install",
"k8s.io/kubernetes/pkg/apis/extensions/install",
"k8s.io/kubernetes/pkg/apis/policy/install",
"k8s.io/kubernetes/pkg/apis/rbac/install",
"k8s.io/kubernetes/pkg/apis/scheduling/install",
"k8s.io/kubernetes/pkg/apis/settings/install",
"k8s.io/kubernetes/pkg/apis/storage/install",
"k8s.io/kubernetes/pkg/kubectl/cmd/auth",
"k8s.io/kubernetes/pkg/util/node",
"k8s.io/kubernetes/pkg/util/slice",
"k8s.io/kubernetes/pkg/util/workqueue/prometheus",
"k8s.io/utils/pointer",
"layeh.com/gopher-json",
]

View File

@@ -19,7 +19,7 @@ required = [
[[constraint]]
name = "github.com/gogo/protobuf"
version = "1.1.1"
version = "1.3.1"
# override github.com/grpc-ecosystem/go-grpc-middleware's constraint on master
[[override]]
@@ -36,25 +36,54 @@ required = [
revision = "7858729281ec582767b20e0d696b6041d995d5e0"
[[override]]
branch = "release-1.14"
branch = "release-1.16"
name = "k8s.io/api"
[[override]]
branch = "release-1.14"
branch = "release-1.16"
name = "k8s.io/kubernetes"
[[override]]
branch = "release-1.14"
branch = "release-1.16"
name = "k8s.io/code-generator"
[[override]]
branch = "release-1.14"
branch = "release-1.16"
name = "k8s.io/apimachinery"
[[override]]
branch = "release-11.0"
branch = "release-1.16"
name = "k8s.io/apiextensions-apiserver"
[[override]]
branch = "release-1.16"
name = "k8s.io/apiserver"
[[override]]
branch = "release-1.16"
name = "k8s.io/kubectl"
[[override]]
branch = "release-1.16"
name = "k8s.io/cli-runtime"
[[override]]
version = "2.0.3"
name = "sigs.k8s.io/kustomize"
# ASCIIRenderer does not implement blackfriday.Renderer
[[override]]
name = "github.com/russross/blackfriday"
version = "1.5.2"
[[override]]
branch = "release-13.0"
name = "k8s.io/client-go"
[[override]]
name = "github.com/casbin/casbin"
version = "1.9.1"
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.2.2"
@@ -71,12 +100,18 @@ required = [
branch = "master"
name = "github.com/yudai/gojsondiff"
[[constraint]]
# Fixes: Could not introduce sigs.k8s.io/kustomize@v2.0.3, as it has a dependency on github.com/spf13/cobra with constraint ^0.0.2, which has no overlap with existing constraint 0.0.5 from (root)
[[override]]
name = "github.com/spf13/cobra"
revision = "fe5e611709b0c57fa4a89136deaa8e1d4004d053"
revision = "0.0.5"
# TODO: move off of k8s.io/kube-openapi and use controller-tools for CRD spec generation
# (override argoproj/argo contraint on master)
[[override]]
revision = "411b2483e5034420675ebcdd4a55fc76fe5e55cf"
revision = "30be4d16710ac61bce31eb28a01054596fe6a9f1"
name = "k8s.io/kube-openapi"
# jsonpatch replace operation does not apply: doc is missing key: /metadata/annotations
[[override]]
name = "github.com/evanphx/json-patch"
version = "v4.1.0"

View File

@@ -97,7 +97,7 @@ manifests-local:
./hack/update-manifests.sh
.PHONY: manifests
manifests:
manifests: dev-tools-image
$(call run-in-dev-tool,make manifests-local IMAGE_TAG='${IMAGE_TAG}')
@@ -221,3 +221,20 @@ release-precheck: manifests
.PHONY: release
release: pre-commit release-precheck image release-cli
.PHONY: build-docs
build-docs:
mkdocs build
.PHONY: serve-docs
serve-docs:
mkdocs serve
.PHONY: lint-docs
lint-docs:
# https://github.com/dkhamsing/awesome_bot
find docs -name '*.md' -exec grep -l http {} + | xargs docker run --rm -v $(PWD):/mnt:ro dkhamsing/awesome_bot -t 3 --allow-dupe --allow-redirect --white-list `cat white-list | grep -v "#" | tr "\n" ','` --skip-save-results --
.PHONY: publish-docs
publish-docs: lint-docs
mkdocs gh-deploy

View File

@@ -1,6 +1,6 @@
controller: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true go run ./cmd/argocd-application-controller/main.go --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
api-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true go run ./cmd/argocd-server/main.go --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} --staticassets ui/dist/app"
dex: sh -c "go run ./cmd/argocd-util/main.go gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml quay.io/dexidp/dex:v2.14.0 serve /dex.yaml"
dex: sh -c "go run github.com/argoproj/argo-cd/cmd/argocd-util gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml quay.io/dexidp/dex:v2.21.0 serve /dex.yaml"
redis: docker run --rm --name argocd-redis -i -p ${ARGOCD_E2E_REDIS_PORT:-6379}:${ARGOCD_E2E_REDIS_PORT:-6379} redis:5.0.3-alpine --save "" --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}
repo-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true go run ./cmd/argocd-repo-server/main.go --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}"
ui: sh -c 'cd ui && ${ARGOCD_E2E_YARN_CMD:-yarn} start'

View File

@@ -21,36 +21,62 @@ Application deployment and lifecycle management should be automated, auditable,
Organizations below are **officially** using Argo CD. Please send a PR with your organization name if you are using Argo CD.
1. [127Labs](https://127labs.com/)
1. [Adevinta](https://www.adevinta.com/)
1. [ANSTO - Australian Synchrotron](https://www.synchrotron.org.au/)
1. [ARZ Allgemeines Rechenzentrum GmbH ](https://www.arz.at/)
1. [Baloise](https://www.baloise.com)
1. [BioBox Analytics](https://biobox.io)
1. [CARFAX](https://www.carfax.com)
1. [Celonis](https://www.celonis.com/)
1. [Codility](https://www.codility.com/)
1. [Commonbond](https://commonbond.co/)
1. [CyberAgent](https://www.cyberagent.co.jp/en/)
1. [Cybozu](https://cybozu-global.com)
1. [EDF Renewables](https://www.edf-re.com/)
1. [Elium](https://www.elium.com)
1. [END.](https://www.endclothing.com/)
1. [Fave](https://myfave.com)
1. [Future PLC](https://www.futureplc.com/)
1. [GMETRI](https://gmetri.com/)
1. [hipages](https://hipages.com.au/)
1. [Intuit](https://www.intuit.com/)
1. [KintoHub](https://www.kintohub.com/)
1. [KompiTech GmbH](https://www.kompitech.com/)
1. [Lytt](https://www.lytt.co/)
1. [Mambu](https://www.mambu.com/)
1. [Major League Baseball](https://mlb.com)
1. [Mambu](https://www.mambu.com/)
1. [Max Kelsen](https://www.maxkelsen.com/)
1. [Mirantis](https://mirantis.com/)
1. [OpenSaaS Studio](https://opensaas.studio)
1. [Optoro](https://www.optoro.com/)
1. [Peloton Interactive](https://www.onepeloton.com/)
1. [Pipefy](https://www.pipefy.com/)
1. [Riskified](https://www.riskified.com/)
1. [Red Hat](https://www.redhat.com/)
1. [Saildrone](https://www.saildrone.com/)
1. [Saloodo! GmbH](https://www.saloodo.com)
1. [Syncier](https://syncier.com/)
1. [Tesla](https://tesla.com/)
1. [Tiger Analytics](https://www.tigeranalytics.com/)
1. [tZERO](https://www.tzero.com/)
1. [Ticketmaster](https://ticketmaster.com)
1. [Twilio SendGrid](https://sendgrid.com)
1. [Yieldlab](https://www.yieldlab.de/)
1. [UBIO](https://ub.io/)
1. [Universidad Mesoamericana](https://www.umes.edu.gt/)
1. [Viaduct](https://www.viaduct.ai/)
1. [Volvo Cars](https://www.volvocars.com/)
1. [Walkbase](https://www.walkbase.com/)
## Documentation
To learn more about Argo CD [go to the complete documentation](https://argoproj.github.io/argo-cd/).
Check live demo at https://cd.apps.argoproj.io/.
## Community Blogs and Presentations
1. [Tutorial: Everything You Need To Become A GitOps Ninja](https://www.youtube.com/watch?v=r50tRQjisxw) 90m tutorial on GitOps and Argo CD.
1. [Comparison of Argo CD, Spinnaker, Jenkins X, and Tekton](https://www.inovex.de/blog/spinnaker-vs-argo-cd-vs-tekton-vs-jenkins-x/)
1. [Simplify and Automate Deployments Using GitOps with IBM Multicloud Manager 3.1.2](https://medium.com/ibm-cloud/simplify-and-automate-deployments-using-gitops-with-ibm-multicloud-manager-3-1-2-4395af317359)
1. [GitOps for Kubeflow using Argo CD](https://www.kubeflow.org/docs/use-cases/gitops-for-kubeflow/)
@@ -59,3 +85,4 @@ To learn more about Argo CD [go to the complete documentation](https://argoproj.
1. [CI/CD in Light Speed with K8s and Argo CD](https://www.youtube.com/watch?v=OdzH82VpMwI&feature=youtu.be)
1. [Machine Learning as Code](https://www.youtube.com/watch?v=VXrGp5er1ZE&t=0s&index=135&list=PLj6h78yzYM2PZf9eA7bhWnIh_mK1vyOfU). Among other things, describes how Kubeflow uses Argo CD to implement GitOPs for ML
1. [Argo CD - GitOps Continuous Delivery for Kubernetes](https://www.youtube.com/watch?v=aWDIQMbp1cc&feature=youtu.be&t=1m4s)
1. [Introduction to Argo CD : Kubernetes DevOps CI/CD](https://www.youtube.com/watch?v=2WSJF7d8dUg&feature=youtu.be)

View File

@@ -1 +1 @@
1.3.0
1.4.3

View File

@@ -16,6 +16,42 @@
"version": "version not set"
},
"paths": {
"/api/v1/account/can-i/{resource}/{action}/{subresource}": {
"get": {
"tags": [
"AccountService"
],
"operationId": "CanI",
"parameters": [
{
"type": "string",
"name": "resource",
"in": "path",
"required": true
},
{
"type": "string",
"name": "action",
"in": "path",
"required": true
},
{
"type": "string",
"name": "subresource",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "(empty)",
"schema": {
"$ref": "#/definitions/accountCanIResponse"
}
}
}
}
},
"/api/v1/account/password": {
"put": {
"tags": [
@@ -49,7 +85,7 @@
"ApplicationService"
],
"summary": "List returns list of applications",
"operationId": "ListMixin7",
"operationId": "ListMixin8",
"parameters": [
{
"type": "string",
@@ -99,7 +135,7 @@
"ApplicationService"
],
"summary": "Create creates an application",
"operationId": "CreateMixin7",
"operationId": "CreateMixin8",
"parameters": [
{
"name": "body",
@@ -126,7 +162,7 @@
"ApplicationService"
],
"summary": "Update updates an application",
"operationId": "UpdateMixin7",
"operationId": "UpdateMixin8",
"parameters": [
{
"type": "string",
@@ -165,6 +201,31 @@
"name": "applicationName",
"in": "path",
"required": true
},
{
"type": "string",
"name": "namespace",
"in": "query"
},
{
"type": "string",
"name": "name",
"in": "query"
},
{
"type": "string",
"name": "version",
"in": "query"
},
{
"type": "string",
"name": "group",
"in": "query"
},
{
"type": "string",
"name": "kind",
"in": "query"
}
],
"responses": {
@@ -189,6 +250,31 @@
"name": "applicationName",
"in": "path",
"required": true
},
{
"type": "string",
"name": "namespace",
"in": "query"
},
{
"type": "string",
"name": "name",
"in": "query"
},
{
"type": "string",
"name": "version",
"in": "query"
},
{
"type": "string",
"name": "group",
"in": "query"
},
{
"type": "string",
"name": "kind",
"in": "query"
}
],
"responses": {
@@ -207,7 +293,7 @@
"ApplicationService"
],
"summary": "Get returns an application by name",
"operationId": "GetMixin7",
"operationId": "GetMixin8",
"parameters": [
{
"type": "string",
@@ -257,7 +343,7 @@
"ApplicationService"
],
"summary": "Delete deletes an application",
"operationId": "DeleteMixin7",
"operationId": "DeleteMixin8",
"parameters": [
{
"type": "string",
@@ -1051,7 +1137,7 @@
"ProjectService"
],
"summary": "List returns list of projects",
"operationId": "ListMixin5",
"operationId": "ListMixin6",
"parameters": [
{
"type": "string",
@@ -1073,7 +1159,7 @@
"ProjectService"
],
"summary": "Create a new project.",
"operationId": "CreateMixin5",
"operationId": "CreateMixin6",
"parameters": [
{
"name": "body",
@@ -1100,7 +1186,7 @@
"ProjectService"
],
"summary": "Get returns a project by name",
"operationId": "GetMixin5",
"operationId": "GetMixin6",
"parameters": [
{
"type": "string",
@@ -1123,7 +1209,7 @@
"ProjectService"
],
"summary": "Delete deletes a project",
"operationId": "DeleteMixin5",
"operationId": "DeleteMixin6",
"parameters": [
{
"type": "string",
@@ -1198,7 +1284,7 @@
"ProjectService"
],
"summary": "Update updates a project",
"operationId": "UpdateMixin5",
"operationId": "UpdateMixin6",
"parameters": [
{
"type": "string",
@@ -1302,18 +1388,134 @@
}
}
},
"/api/v1/repocreds": {
"get": {
"tags": [
"RepoCredsService"
],
"summary": "ListRepositoryCredentials gets a list of all configured repository credential sets",
"operationId": "ListRepositoryCredentials",
"parameters": [
{
"type": "string",
"description": "Repo URL for query.",
"name": "url",
"in": "query"
}
],
"responses": {
"200": {
"description": "(empty)",
"schema": {
"$ref": "#/definitions/v1alpha1RepoCredsList"
}
}
}
},
"post": {
"tags": [
"RepoCredsService"
],
"summary": "CreateRepositoryCredentials creates a new repository credential set",
"operationId": "CreateRepositoryCredentials",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1alpha1RepoCreds"
}
}
],
"responses": {
"200": {
"description": "(empty)",
"schema": {
"$ref": "#/definitions/v1alpha1RepoCreds"
}
}
}
}
},
"/api/v1/repocreds/{creds.url}": {
"put": {
"tags": [
"RepoCredsService"
],
"summary": "UpdateRepositoryCredentials updates a repository credential set",
"operationId": "UpdateRepositoryCredentials",
"parameters": [
{
"type": "string",
"name": "creds.url",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1alpha1RepoCreds"
}
}
],
"responses": {
"200": {
"description": "(empty)",
"schema": {
"$ref": "#/definitions/v1alpha1RepoCreds"
}
}
}
}
},
"/api/v1/repocreds/{url}": {
"delete": {
"tags": [
"RepoCredsService"
],
"summary": "DeleteRepositoryCredentials deletes a repository credential set from the configuration",
"operationId": "DeleteRepositoryCredentials",
"parameters": [
{
"type": "string",
"name": "url",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "(empty)",
"schema": {
"$ref": "#/definitions/repocredsRepoCredsResponse"
}
}
}
}
},
"/api/v1/repositories": {
"get": {
"tags": [
"RepositoryService"
],
"summary": "List returns list of repos",
"operationId": "ListMixin3",
"summary": "ListRepositories gets a list of all configured repositories",
"operationId": "ListRepositories",
"parameters": [
{
"type": "string",
"description": "Repo URL for query.",
"name": "repo",
"in": "query"
},
{
"type": "boolean",
"format": "boolean",
"description": "Whether to force a cache refresh on repo's connection state.",
"name": "forceRefresh",
"in": "query"
}
],
"responses": {
@@ -1329,8 +1531,8 @@
"tags": [
"RepositoryService"
],
"summary": "Create creates a repo",
"operationId": "CreateMixin3",
"summary": "CreateRepository creates a new repository configuration",
"operationId": "CreateRepository",
"parameters": [
{
"name": "body",
@@ -1356,8 +1558,8 @@
"tags": [
"RepositoryService"
],
"summary": "Update updates a repo",
"operationId": "UpdateMixin3",
"summary": "UpdateRepository updates a repository configuration",
"operationId": "UpdateRepository",
"parameters": [
{
"type": "string",
@@ -1389,8 +1591,8 @@
"tags": [
"RepositoryService"
],
"summary": "Delete deletes a repo",
"operationId": "DeleteMixin3",
"summary": "DeleteRepository deletes a repository from the configuration",
"operationId": "DeleteRepository",
"parameters": [
{
"type": "string",
@@ -1451,6 +1653,13 @@
"name": "repo",
"in": "path",
"required": true
},
{
"type": "boolean",
"format": "boolean",
"description": "Whether to force a cache refresh on repo's connection state.",
"name": "forceRefresh",
"in": "query"
}
],
"responses": {
@@ -1535,7 +1744,7 @@
"SessionService"
],
"summary": "Create a new JWT for authentication and set a cookie if using HTTP.",
"operationId": "CreateMixin9",
"operationId": "CreateMixin10",
"parameters": [
{
"name": "body",
@@ -1560,7 +1769,7 @@
"SessionService"
],
"summary": "Delete an existing JWT cookie if using HTTP.",
"operationId": "DeleteMixin9",
"operationId": "DeleteMixin10",
"responses": {
"200": {
"description": "(empty)",
@@ -1676,6 +1885,14 @@
}
},
"definitions": {
"accountCanIResponse": {
"type": "object",
"properties": {
"value": {
"type": "string"
}
}
},
"accountUpdatePasswordRequest": {
"type": "object",
"properties": {
@@ -2060,6 +2277,10 @@
}
}
},
"repocredsRepoCredsResponse": {
"type": "object",
"title": "RepoCredsResponse is a resonse to most repository credentials requests"
},
"repositoryAppInfo": {
"type": "object",
"title": "AppInfo contains application type and app file path",
@@ -2203,7 +2424,8 @@
"type": "string"
},
"revision": {
"type": "string"
"type": "string",
"title": "resolved revision"
},
"server": {
"type": "string"
@@ -2393,7 +2615,7 @@
},
"state": {
"type": "string",
"title": "State of this Series: Ongoing or Finished"
"title": "State of this Series: Ongoing or Finished\nDeprecated. Planned removal for 1.18"
}
}
},
@@ -2411,16 +2633,14 @@
}
}
},
"v1Fields": {
"v1FieldsV1": {
"description": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set,\nor a string representing a sub-field or item. The string will follow one of these four formats:\n'f:<name>', where <name> is the name of a field in a struct, or key in a map\n'v:<value>', where <value> is the exact json formatted value of a list item\n'i:<index>', where <index> is position of a item in a list\n'k:<keys>', where <keys> is a map of a list item's key fields to their unique values\nIf a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff",
"type": "object",
"title": "Fields stores a set of fields in a data structure like a Trie.\nTo understand how this is used, see: https://github.com/kubernetes-sigs/structured-merge-diff",
"properties": {
"map": {
"description": "Map stores a set of fields in a data structure like a Trie.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set,\nor a string representing a sub-field or item. The string will follow one of these four formats:\n'f:<name>', where <name> is the name of a field in a struct, or key in a map\n'v:<value>', where <value> is the exact json formatted value of a list item\n'i:<index>', where <index> is position of a item in a list\n'k:<keys>', where <keys> is a map of a list item's key fields to their unique values\nIf a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/v1Fields"
}
"Raw": {
"description": "Raw is the underlying serialization of this object.",
"type": "string",
"format": "byte"
}
}
},
@@ -2437,32 +2657,6 @@
}
}
},
"v1Initializer": {
"description": "Initializer is information about an initializer that has not yet completed.",
"type": "object",
"properties": {
"name": {
"description": "name of the process that is responsible for initializing this object.",
"type": "string"
}
}
},
"v1Initializers": {
"description": "Initializers tracks the progress of initialization.",
"type": "object",
"properties": {
"pending": {
"type": "array",
"title": "Pending is a list of initializers that must execute in order before this object is visible.\nWhen the last pending initializer is removed, and no failing result is set, the initializers\nstruct will be set to nil and the object is considered as initialized and visible to all\nclients.\n+patchMergeKey=name\n+patchStrategy=merge",
"items": {
"$ref": "#/definitions/v1Initializer"
}
},
"result": {
"$ref": "#/definitions/v1Status"
}
}
},
"v1ListMeta": {
"description": "ListMeta describes metadata that synthetic resources must have, including lists and\nvarious status objects. A resource may have only one of {ObjectMeta, ListMeta}.",
"type": "object",
@@ -2471,13 +2665,18 @@
"description": "continue may be set if the user set a limit on the number of items returned, and indicates that\nthe server has more data available. The value is opaque and may be used to issue another request\nto the endpoint that served this list to retrieve the next set of available objects. Continuing a\nconsistent list may not be possible if the server configuration has changed or more than a few\nminutes have passed. The resourceVersion field returned when using this continue value will be\nidentical to the value in the first response, unless you have received this token from an error\nmessage.",
"type": "string"
},
"remainingItemCount": {
"type": "string",
"format": "int64",
"title": "remainingItemCount is the number of subsequent items in the list which are not included in this\nlist response. If the list request contained label or field selectors, then the number of\nremaining items is unknown and the field will be left unset and omitted during serialization.\nIf the list is complete (either because it is not chunking or because this is the last chunk),\nthen there are no more remaining items and this field will be left unset and omitted during\nserialization.\nServers older than v1.15 do not set this field.\nThe intended use of the remainingItemCount is *estimating* the size of a collection. Clients\nshould not rely on the remainingItemCount to be set or to be exact.\n+optional"
},
"resourceVersion": {
"type": "string",
"title": "String that identifies the server's internal version of this object that\ncan be used by clients to determine when objects have changed.\nValue must be treated as opaque by clients and passed unmodified back to the server.\nPopulated by the system.\nRead-only.\nMore info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency\n+optional"
"title": "String that identifies the server's internal version of this object that\ncan be used by clients to determine when objects have changed.\nValue must be treated as opaque by clients and passed unmodified back to the server.\nPopulated by the system.\nRead-only.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency\n+optional"
},
"selfLink": {
"type": "string",
"title": "selfLink is a URL representing this object.\nPopulated by the system.\nRead-only.\n+optional"
"description": "selfLink is a URL representing this object.\nPopulated by the system.\nRead-only.\n\nDEPRECATED\nKubernetes will stop propagating this field in 1.20 release and the field is planned\nto be removed in 1.21 release.\n+optional",
"type": "string"
}
}
},
@@ -2503,8 +2702,12 @@
"description": "APIVersion defines the version of this resource that this field set\napplies to. The format is \"group/version\" just like the top-level\nAPIVersion field. It is necessary to track the version of a field\nset because it cannot be automatically converted.",
"type": "string"
},
"fields": {
"$ref": "#/definitions/v1Fields"
"fieldsType": {
"type": "string",
"title": "FieldsType is the discriminator for the different fields format and version.\nThere is currently only one possible value: \"FieldsV1\""
},
"fieldsV1": {
"$ref": "#/definitions/v1FieldsV1"
},
"manager": {
"description": "Manager is an identifier of the workflow managing these fields.",
@@ -2569,7 +2772,7 @@
}
},
"generateName": {
"description": "GenerateName is an optional prefix, used by the server, to generate a unique\nname ONLY IF the Name field has not been provided.\nIf this field is used, the name returned to the client will be different\nthan the name passed. This value will also be combined with a unique suffix.\nThe provided value has the same validation rules as the Name field,\nand may be truncated by the length of the suffix required to make the value\nunique on the server.\n\nIf this field is specified and the generated name exists, the server will\nNOT return a 409 - instead, it will either return 201 Created or 500 with Reason\nServerTimeout indicating a unique name could not be found in the time allotted, and the client\nshould retry (optionally after the time indicated in the Retry-After header).\n\nApplied only if Name is not specified.\nMore info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency\n+optional",
"description": "GenerateName is an optional prefix, used by the server, to generate a unique\nname ONLY IF the Name field has not been provided.\nIf this field is used, the name returned to the client will be different\nthan the name passed. This value will also be combined with a unique suffix.\nThe provided value has the same validation rules as the Name field,\nand may be truncated by the length of the suffix required to make the value\nunique on the server.\n\nIf this field is specified and the generated name exists, the server will\nNOT return a 409 - instead, it will either return 201 Created or 500 with Reason\nServerTimeout indicating a unique name could not be found in the time allotted, and the client\nshould retry (optionally after the time indicated in the Retry-After header).\n\nApplied only if Name is not specified.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency\n+optional",
"type": "string"
},
"generation": {
@@ -2577,9 +2780,6 @@
"format": "int64",
"title": "A sequence number representing a specific generation of the desired state.\nPopulated by the system. Read-only.\n+optional"
},
"initializers": {
"$ref": "#/definitions/v1Initializers"
},
"labels": {
"type": "object",
"title": "Map of string keys and values that can be used to organize and categorize\n(scope and select) objects. May match selectors of replication controllers\nand services.\nMore info: http://kubernetes.io/docs/user-guide/labels\n+optional",
@@ -2588,7 +2788,7 @@
}
},
"managedFields": {
"description": "ManagedFields maps workflow-id and version to the set of fields\nthat are managed by that workflow. This is mostly for internal\nhousekeeping, and users typically shouldn't need to set or\nunderstand this field. A workflow can be the user's name, a\ncontroller's name, or the name of a specific apply path like\n\"ci-cd\". The set of fields is always in the version that the\nworkflow used when modifying the object.\n\nThis field is alpha and can be changed or removed without notice.\n\n+optional",
"description": "ManagedFields maps workflow-id and version to the set of fields\nthat are managed by that workflow. This is mostly for internal\nhousekeeping, and users typically shouldn't need to set or\nunderstand this field. A workflow can be the user's name, a\ncontroller's name, or the name of a specific apply path like\n\"ci-cd\". The set of fields is always in the version that the\nworkflow used when modifying the object.\n\n+optional",
"type": "array",
"items": {
"$ref": "#/definitions/v1ManagedFieldsEntry"
@@ -2610,12 +2810,12 @@
}
},
"resourceVersion": {
"description": "An opaque value that represents the internal version of this object that can\nbe used by clients to determine when objects have changed. May be used for optimistic\nconcurrency, change detection, and the watch operation on a resource or set of resources.\nClients must treat these values as opaque and passed unmodified back to the server.\nThey may only be valid for a particular resource or set of resources.\n\nPopulated by the system.\nRead-only.\nValue must be treated as opaque by clients and .\nMore info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency\n+optional",
"description": "An opaque value that represents the internal version of this object that can\nbe used by clients to determine when objects have changed. May be used for optimistic\nconcurrency, change detection, and the watch operation on a resource or set of resources.\nClients must treat these values as opaque and passed unmodified back to the server.\nThey may only be valid for a particular resource or set of resources.\n\nPopulated by the system.\nRead-only.\nValue must be treated as opaque by clients and .\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency\n+optional",
"type": "string"
},
"selfLink": {
"type": "string",
"title": "SelfLink is a URL representing this object.\nPopulated by the system.\nRead-only.\n+optional"
"description": "SelfLink is a URL representing this object.\nPopulated by the system.\nRead-only.\n\nDEPRECATED\nKubernetes will stop propagating this field in 1.20 release and the field is planned\nto be removed in 1.21 release.\n+optional",
"type": "string"
},
"uid": {
"description": "UID is the unique in time and space value for this object. It is typically generated by\nthe server on successful creation of a resource and is not allowed to change on PUT\noperations.\n\nPopulated by the system.\nRead-only.\nMore info: http://kubernetes.io/docs/user-guide/identifiers#uids\n+optional",
@@ -2637,7 +2837,7 @@
},
"kind": {
"type": "string",
"title": "Kind of the referent.\nMore info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds\n+optional"
"title": "Kind of the referent.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n+optional"
},
"name": {
"type": "string",
@@ -2649,7 +2849,7 @@
},
"resourceVersion": {
"type": "string",
"title": "Specific resourceVersion to which this reference is made, if any.\nMore info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency\n+optional"
"title": "Specific resourceVersion to which this reference is made, if any.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency\n+optional"
},
"uid": {
"type": "string",
@@ -2677,7 +2877,7 @@
},
"kind": {
"type": "string",
"title": "Kind of the referent.\nMore info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds"
"title": "Kind of the referent.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds"
},
"name": {
"type": "string",
@@ -2689,87 +2889,6 @@
}
}
},
"v1Status": {
"description": "Status is a return value for calls that don't return other objects.",
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32",
"title": "Suggested HTTP return code for this status, 0 if not set.\n+optional"
},
"details": {
"$ref": "#/definitions/v1StatusDetails"
},
"message": {
"type": "string",
"title": "A human-readable description of the status of this operation.\n+optional"
},
"metadata": {
"$ref": "#/definitions/v1ListMeta"
},
"reason": {
"type": "string",
"title": "A machine-readable description of why this operation is in the\n\"Failure\" status. If this value is empty there\nis no information available. A Reason clarifies an HTTP status\ncode but does not override it.\n+optional"
},
"status": {
"type": "string",
"title": "Status of the operation.\nOne of: \"Success\" or \"Failure\".\nMore info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status\n+optional"
}
}
},
"v1StatusCause": {
"description": "StatusCause provides more information about an api.Status failure, including\ncases when multiple errors are encountered.",
"type": "object",
"properties": {
"field": {
"description": "The field of the resource that has caused this error, as named by its JSON\nserialization. May include dot and postfix notation for nested attributes.\nArrays are zero-indexed. Fields may appear more than once in an array of\ncauses due to fields having multiple errors.\nOptional.\n\nExamples:\n \"name\" - the field \"name\" on the current resource\n \"items[0].name\" - the field \"name\" on the first array entry in \"items\"\n+optional",
"type": "string"
},
"message": {
"type": "string",
"title": "A human-readable description of the cause of the error. This field may be\npresented as-is to a reader.\n+optional"
},
"reason": {
"type": "string",
"title": "A machine-readable description of the cause of the error. If this value is\nempty there is no information available.\n+optional"
}
}
},
"v1StatusDetails": {
"description": "StatusDetails is a set of additional properties that MAY be set by the\nserver to provide additional information about a response. The Reason\nfield of a Status object defines what attributes will be set. Clients\nmust ignore fields that do not match the defined type of each attribute,\nand should assume that any attribute may be empty, invalid, or under\ndefined.",
"type": "object",
"properties": {
"causes": {
"type": "array",
"title": "The Causes array includes more details associated with the StatusReason\nfailure. Not all StatusReasons may provide detailed causes.\n+optional",
"items": {
"$ref": "#/definitions/v1StatusCause"
}
},
"group": {
"type": "string",
"title": "The group attribute of the resource associated with the status StatusReason.\n+optional"
},
"kind": {
"type": "string",
"title": "The kind attribute of the resource associated with the status StatusReason.\nOn some operations may differ from the requested resource Kind.\nMore info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds\n+optional"
},
"name": {
"type": "string",
"title": "The name attribute of the resource associated with the status StatusReason\n(when there is a single name which can be described).\n+optional"
},
"retryAfterSeconds": {
"type": "integer",
"format": "int32",
"title": "If specified, the time in seconds before the operation should be retried. Some errors may indicate\nthe client must take an alternate action - for those errors this field may indicate how long to wait\nbefore taking the alternate action.\n+optional"
},
"uid": {
"type": "string",
"title": "UID of the resource.\n(when there is a single resource which can be described).\nMore info: http://kubernetes.io/docs/user-guide/identifiers#uids\n+optional"
}
}
},
"v1Time": {
"description": "Time is a wrapper around time.Time which supports correct\nmarshaling to YAML and JSON. Wrappers are provided for many\nof the factory methods that the time package offers.\n\n+protobuf.options.marshal=false\n+protobuf.as=Timestamp\n+protobuf.options.(gogoproto.goproto_stringer)=false",
"type": "object",
@@ -2904,6 +3023,9 @@
"type": "object",
"title": "ApplicationCondition contains details about current application condition",
"properties": {
"lastTransitionTime": {
"$ref": "#/definitions/v1Time"
},
"message": {
"type": "string",
"title": "Message contains human-readable message indicating details about condition"
@@ -3078,6 +3200,10 @@
"namePrefix": {
"type": "string",
"title": "NamePrefix is a prefix appended to resources for kustomize apps"
},
"nameSuffix": {
"type": "string",
"title": "NameSuffix is a suffix appended to resources for kustomize apps"
}
}
},
@@ -3121,6 +3247,11 @@
"description": "Project is a application project name. Empty name means that application belongs to 'default' project.",
"type": "string"
},
"revisionHistoryLimit": {
"description": "This limits this number of items kept in the apps revision history.\nThis should only be changed in exceptional circumstances.\nSetting to zero will store no history. This will reduce storage used.\nIncreasing will increase the space used to store the history, so we do not recommend increasing it.\nDefault is 10.",
"type": "string",
"format": "int64"
},
"source": {
"$ref": "#/definitions/v1alpha1ApplicationSource"
},
@@ -3239,6 +3370,13 @@
"type": "string",
"title": "Name of the cluster. If omitted, will use the server address"
},
"namespaces": {
"description": "Holds list of namespaces which are accessible in that cluster. Cluster level resources would be ignored if namespace list if not empty.",
"type": "array",
"items": {
"type": "string"
}
},
"server": {
"type": "string",
"title": "Server is the API server URL of the Kubernetes cluster"
@@ -3518,6 +3656,51 @@
}
}
},
"v1alpha1RepoCreds": {
"type": "object",
"title": "RepoCreds holds a repository credentials definition",
"properties": {
"password": {
"type": "string",
"title": "Password for authenticating at the repo server"
},
"sshPrivateKey": {
"type": "string",
"title": "SSH private key data for authenticating at the repo server (only Git repos)"
},
"tlsClientCertData": {
"type": "string",
"title": "TLS client cert data for authenticating at the repo server"
},
"tlsClientCertKey": {
"type": "string",
"title": "TLS client cert key for authenticating at the repo server"
},
"url": {
"type": "string",
"title": "URL is the URL that this credentials matches to"
},
"username": {
"type": "string",
"title": "Username for authenticating at the repo server"
}
}
},
"v1alpha1RepoCredsList": {
"description": "RepositoryList is a collection of Repositories.",
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/v1alpha1RepoCreds"
}
},
"metadata": {
"$ref": "#/definitions/v1ListMeta"
}
}
},
"v1alpha1Repository": {
"type": "object",
"title": "Repository is a repository holding application configurations",
@@ -3530,6 +3713,11 @@
"format": "boolean",
"title": "Whether git-lfs support should be enabled for this repo"
},
"inheritedCreds": {
"type": "boolean",
"format": "boolean",
"title": "Whether credentials were inherited from a credential set"
},
"insecure": {
"type": "boolean",
"format": "boolean",
@@ -3672,7 +3860,8 @@
"title": "ResourceDiff holds the diff of a live and target resource object",
"properties": {
"diff": {
"type": "string"
"type": "string",
"title": "Diff contains the JSON patch between target and live resource\nDeprecated: use NormalizedLiveState and PredictedLiveState to render the difference"
},
"group": {
"type": "string"
@@ -3685,7 +3874,8 @@
"type": "string"
},
"liveState": {
"type": "string"
"type": "string",
"title": "TargetState contains the JSON live resource manifest"
},
"name": {
"type": "string"
@@ -3693,8 +3883,17 @@
"namespace": {
"type": "string"
},
"normalizedLiveState": {
"type": "string",
"title": "NormalizedLiveState contains JSON serialized live resource state with applied normalizations"
},
"predictedLiveState": {
"type": "string",
"title": "PredictedLiveState contains JSON serialized resource state that is calculated based on normalized and target resource state"
},
"targetState": {
"type": "string"
"type": "string",
"title": "TargetState contains the JSON serialized resource manifest defined in the Git/Helm"
}
}
},

View File

@@ -21,7 +21,7 @@ import (
"github.com/argoproj/argo-cd/errors"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
"github.com/argoproj/argo-cd/reposerver/apiclient"
"github.com/argoproj/argo-cd/util/cache"
appstatecache "github.com/argoproj/argo-cd/util/cache/appstate"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/settings"
@@ -48,7 +48,7 @@ func newCommand() *cobra.Command {
glogLevel int
metricsPort int
kubectlParallelismLimit int64
cacheSrc func() (*cache.Cache, error)
cacheSrc func() (*appstatecache.Cache, error)
)
var command = cobra.Command{
Use: cliName,
@@ -77,7 +77,7 @@ func newCommand() *cobra.Command {
errors.CheckError(err)
settingsMgr := settings.NewSettingsManager(ctx, kubeClient, namespace)
kubectl := kube.KubectlCmd{}
kubectl := &kube.KubectlCmd{}
appController, err := controller.NewApplicationController(
namespace,
settingsMgr,
@@ -116,7 +116,7 @@ func newCommand() *cobra.Command {
command.Flags().IntVar(&selfHealTimeoutSeconds, "self-heal-timeout-seconds", 5, "Specifies timeout between application self heal attempts")
command.Flags().Int64Var(&kubectlParallelismLimit, "kubectl-parallelism-limit", 20, "Number of allowed concurrent kubectl fork/execs. Any value less the 1 means no limit.")
cacheSrc = cache.AddCacheFlagsToCmd(&command)
cacheSrc = appstatecache.AddCacheFlagsToCmd(&command)
return &command
}

View File

@@ -13,8 +13,8 @@ import (
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/reposerver"
reposervercache "github.com/argoproj/argo-cd/reposerver/cache"
"github.com/argoproj/argo-cd/reposerver/metrics"
"github.com/argoproj/argo-cd/util/cache"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/stats"
"github.com/argoproj/argo-cd/util/tls"
@@ -31,7 +31,7 @@ func newCommand() *cobra.Command {
parallelismLimit int64
listenPort int
metricsPort int
cacheSrc func() (*cache.Cache, error)
cacheSrc func() (*reposervercache.Cache, error)
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
)
var command = cobra.Command{
@@ -72,7 +72,7 @@ func newCommand() *cobra.Command {
command.Flags().IntVar(&listenPort, "port", common.DefaultPortRepoServer, "Listen on given port for incoming connections")
command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortRepoServerMetrics, "Start metrics server on given port")
tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(&command)
cacheSrc = cache.AddCacheFlagsToCmd(&command)
cacheSrc = reposervercache.AddCacheFlagsToCmd(&command)
return &command
}

View File

@@ -13,7 +13,7 @@ import (
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
"github.com/argoproj/argo-cd/reposerver/apiclient"
"github.com/argoproj/argo-cd/server"
"github.com/argoproj/argo-cd/util/cache"
servercache "github.com/argoproj/argo-cd/server/cache"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/stats"
"github.com/argoproj/argo-cd/util/tls"
@@ -35,7 +35,8 @@ func NewCommand() *cobra.Command {
dexServerAddress string
disableAuth bool
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
cacheSrc func() (*cache.Cache, error)
cacheSrc func() (*servercache.Cache, error)
frameOptions string
)
var command = &cobra.Command{
Use: cliName,
@@ -76,6 +77,7 @@ func NewCommand() *cobra.Command {
DisableAuth: disableAuth,
TLSConfigCustomizer: tlsConfigCustomizer,
Cache: cache,
XFrameOptions: frameOptions,
}
stats.RegisterStackDumper()
@@ -105,7 +107,8 @@ func NewCommand() *cobra.Command {
command.Flags().IntVar(&listenPort, "port", common.DefaultPortAPIServer, "Listen on given port")
command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortArgoCDAPIServerMetrics, "Start metrics on given port")
command.Flags().IntVar(&repoServerTimeoutSeconds, "repo-server-timeout-seconds", 60, "Repo server RPC call timeout seconds.")
command.Flags().StringVar(&frameOptions, "x-frame-options", "sameorigin", "Set X-Frame-Options header in HTTP responses to `value`. To disable, set to \"\".")
tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(command)
cacheSrc = cache.AddCacheFlagsToCmd(command)
cacheSrc = servercache.AddCacheFlagsToCmd(command)
return command
}

View File

@@ -8,6 +8,7 @@ import (
"io/ioutil"
"os"
"os/exec"
"reflect"
"syscall"
"github.com/ghodss/yaml"
@@ -24,9 +25,8 @@ import (
"k8s.io/client-go/tools/clientcmd"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/db"
"github.com/argoproj/argo-cd/util/dex"
@@ -73,6 +73,7 @@ func NewCommand() *cobra.Command {
command.AddCommand(NewImportCommand())
command.AddCommand(NewExportCommand())
command.AddCommand(NewClusterConfig())
command.AddCommand(NewProjectsCommand())
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
return command
@@ -108,7 +109,7 @@ func NewRunDexCommand() *cobra.Command {
} else {
err = ioutil.WriteFile("/tmp/dex.yaml", dexCfgBytes, 0644)
errors.CheckError(err)
log.Info(string(dexCfgBytes))
log.Info(redactor(string(dexCfgBytes)))
cmd = exec.Command("dex", "serve", "/tmp/dex.yaml")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -219,6 +220,7 @@ func NewImportCommand() *cobra.Command {
os.Exit(1)
}
config, err := clientConfig.ClientConfig()
errors.CheckError(err)
config.QPS = 100
config.Burst = 50
errors.CheckError(err)
@@ -241,43 +243,49 @@ func NewImportCommand() *cobra.Command {
// 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
pruneObjects := make(map[kube.ResourceKey]string)
pruneObjects := make(map[kube.ResourceKey]unstructured.Unstructured)
configMaps, err := acdClients.configMaps.List(metav1.ListOptions{})
errors.CheckError(err)
// referencedSecrets holds any secrets referenced in the argocd-cm configmap. These
// secrets need to be imported too
var referencedSecrets map[string]bool
for _, cm := range configMaps.Items {
cmName := cm.GetName()
if cmName == common.ArgoCDConfigMapName || cmName == common.ArgoCDRBACConfigMapName {
pruneObjects[kube.ResourceKey{Group: "", Kind: "ConfigMap", Name: cm.GetName()}] = cm.GetResourceVersion()
if isArgoCDConfigMap(cm.GetName()) {
pruneObjects[kube.ResourceKey{Group: "", Kind: "ConfigMap", Name: cm.GetName()}] = cm
}
if cm.GetName() == common.ArgoCDConfigMapName {
referencedSecrets = getReferencedSecrets(cm)
}
}
secrets, err := acdClients.secrets.List(metav1.ListOptions{})
errors.CheckError(err)
for _, secret := range secrets.Items {
if isArgoCDSecret(nil, secret) {
pruneObjects[kube.ResourceKey{Group: "", Kind: "Secret", Name: secret.GetName()}] = secret.GetResourceVersion()
if isArgoCDSecret(referencedSecrets, secret) {
pruneObjects[kube.ResourceKey{Group: "", Kind: "Secret", Name: secret.GetName()}] = secret
}
}
applications, err := acdClients.applications.List(metav1.ListOptions{})
errors.CheckError(err)
for _, app := range applications.Items {
pruneObjects[kube.ResourceKey{Group: "argoproj.io", Kind: "Application", Name: app.GetName()}] = app.GetResourceVersion()
pruneObjects[kube.ResourceKey{Group: "argoproj.io", Kind: "Application", Name: app.GetName()}] = app
}
projects, err := acdClients.projects.List(metav1.ListOptions{})
errors.CheckError(err)
for _, proj := range projects.Items {
pruneObjects[kube.ResourceKey{Group: "argoproj.io", Kind: "AppProject", Name: proj.GetName()}] = proj.GetResourceVersion()
pruneObjects[kube.ResourceKey{Group: "argoproj.io", Kind: "AppProject", Name: proj.GetName()}] = proj
}
// Create or replace existing object
objs, err := kube.SplitYAML(string(input))
backupObjects, err := kube.SplitYAML(string(input))
errors.CheckError(err)
for _, obj := range objs {
gvk := obj.GroupVersionKind()
key := kube.ResourceKey{Group: gvk.Group, Kind: gvk.Kind, Name: obj.GetName()}
resourceVersion, exists := pruneObjects[key]
for _, bakObj := range backupObjects {
gvk := bakObj.GroupVersionKind()
key := kube.ResourceKey{Group: gvk.Group, Kind: gvk.Kind, Name: bakObj.GetName()}
liveObj, exists := pruneObjects[key]
delete(pruneObjects, key)
var dynClient dynamic.ResourceInterface
switch obj.GetKind() {
switch bakObj.GetKind() {
case "Secret":
dynClient = acdClients.secrets
case "ConfigMap":
@@ -289,17 +297,19 @@ func NewImportCommand() *cobra.Command {
}
if !exists {
if !dryRun {
_, err = dynClient.Create(obj, metav1.CreateOptions{})
_, err = dynClient.Create(bakObj, metav1.CreateOptions{})
errors.CheckError(err)
}
fmt.Printf("%s/%s %s created%s\n", gvk.Group, gvk.Kind, obj.GetName(), dryRunMsg)
fmt.Printf("%s/%s %s created%s\n", gvk.Group, gvk.Kind, bakObj.GetName(), dryRunMsg)
} else if specsEqual(*bakObj, liveObj) {
fmt.Printf("%s/%s %s unchanged%s\n", gvk.Group, gvk.Kind, bakObj.GetName(), dryRunMsg)
} else {
if !dryRun {
obj.SetResourceVersion(resourceVersion)
_, err = dynClient.Update(obj, metav1.UpdateOptions{})
newLive := updateLive(bakObj, &liveObj)
_, err = dynClient.Update(newLive, metav1.UpdateOptions{})
errors.CheckError(err)
}
fmt.Printf("%s/%s %s replaced%s\n", gvk.Group, gvk.Kind, obj.GetName(), dryRunMsg)
fmt.Printf("%s/%s %s updated%s\n", gvk.Group, gvk.Kind, bakObj.GetName(), dryRunMsg)
}
}
@@ -427,11 +437,37 @@ func getReferencedSecrets(un unstructured.Unstructured) map[string]bool {
err := runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, &cm)
errors.CheckError(err)
referencedSecrets := make(map[string]bool)
// Referenced repository secrets
if reposRAW, ok := cm.Data["repositories"]; ok {
repoCreds := make([]settings.RepoCredentials, 0)
err := yaml.Unmarshal([]byte(reposRAW), &repoCreds)
repos := make([]settings.Repository, 0)
err := yaml.Unmarshal([]byte(reposRAW), &repos)
errors.CheckError(err)
for _, cred := range repoCreds {
for _, cred := range repos {
if cred.PasswordSecret != nil {
referencedSecrets[cred.PasswordSecret.Name] = true
}
if cred.SSHPrivateKeySecret != nil {
referencedSecrets[cred.SSHPrivateKeySecret.Name] = true
}
if cred.UsernameSecret != nil {
referencedSecrets[cred.UsernameSecret.Name] = true
}
if cred.TLSClientCertDataSecret != nil {
referencedSecrets[cred.TLSClientCertDataSecret.Name] = true
}
if cred.TLSClientCertKeySecret != nil {
referencedSecrets[cred.TLSClientCertKeySecret.Name] = true
}
}
}
// Referenced repository credentials secrets
if reposRAW, ok := cm.Data["repository.credentials"]; ok {
creds := make([]settings.RepositoryCredentials, 0)
err := yaml.Unmarshal([]byte(reposRAW), &creds)
errors.CheckError(err)
for _, cred := range creds {
if cred.PasswordSecret != nil {
referencedSecrets[cred.PasswordSecret.Name] = true
}
@@ -477,6 +513,57 @@ func isArgoCDSecret(repoSecretRefs map[string]bool, un unstructured.Unstructured
return false
}
// isArgoCDConfigMap returns true if the configmap name is one of argo cd's well known configmaps
func isArgoCDConfigMap(name string) bool {
switch name {
case common.ArgoCDConfigMapName, common.ArgoCDRBACConfigMapName, common.ArgoCDKnownHostsConfigMapName, common.ArgoCDTLSCertsConfigMapName:
return true
}
return false
}
// specsEqual returns if the spec, data, labels, annotations, and finalizers of the two
// supplied objects are equal, indicating that no update is necessary during importing
func specsEqual(left, right unstructured.Unstructured) bool {
if !reflect.DeepEqual(left.GetAnnotations(), right.GetAnnotations()) {
return false
}
if !reflect.DeepEqual(left.GetLabels(), right.GetLabels()) {
return false
}
if !reflect.DeepEqual(left.GetFinalizers(), right.GetFinalizers()) {
return false
}
switch left.GetKind() {
case "Secret", "ConfigMap":
leftData, _, _ := unstructured.NestedMap(left.Object, "data")
rightData, _, _ := unstructured.NestedMap(right.Object, "data")
return reflect.DeepEqual(leftData, rightData)
case "AppProject", "Application":
leftSpec, _, _ := unstructured.NestedMap(left.Object, "spec")
rightSpec, _, _ := unstructured.NestedMap(right.Object, "spec")
return reflect.DeepEqual(leftSpec, rightSpec)
}
return false
}
// updateLive replaces the live object's finalizers, spec, annotations, labels, and data from the
// backup object but leaves all other fields intact (status, other metadata, etc...)
func updateLive(bak, live *unstructured.Unstructured) *unstructured.Unstructured {
newLive := live.DeepCopy()
newLive.SetAnnotations(bak.GetAnnotations())
newLive.SetLabels(bak.GetLabels())
newLive.SetFinalizers(bak.GetFinalizers())
switch live.GetKind() {
case "Secret", "ConfigMap":
newLive.Object["data"] = bak.Object["data"]
case "AppProject", "Application":
newLive.Object["spec"] = bak.Object["spec"]
}
return newLive
}
// export writes the unstructured object and removes extraneous cruft from output before writing
func export(w io.Writer, un unstructured.Unstructured) {
name := un.GetName()
@@ -532,6 +619,38 @@ func NewClusterConfig() *cobra.Command {
return command
}
func iterateStringFields(obj interface{}, callback func(name string, val string) string) {
if mapField, ok := obj.(map[string]interface{}); ok {
for field, val := range mapField {
if strVal, ok := val.(string); ok {
mapField[field] = callback(field, strVal)
} else {
iterateStringFields(val, callback)
}
}
} else if arrayField, ok := obj.([]interface{}); ok {
for i := range arrayField {
iterateStringFields(arrayField[i], callback)
}
}
}
func redactor(dirtyString string) string {
config := make(map[string]interface{})
err := yaml.Unmarshal([]byte(dirtyString), &config)
errors.CheckError(err)
iterateStringFields(config, func(name string, val string) string {
if name == "clientSecret" || name == "secret" {
return "********"
} else {
return val
}
})
data, err := yaml.Marshal(config)
errors.CheckError(err)
return string(data)
}
func main() {
if err := NewCommand().Execute(); err != nil {
fmt.Println(err)

View File

@@ -0,0 +1,76 @@
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
var textToRedact = `
connectors:
- config:
clientID: aabbccddeeff00112233
clientSecret: |
theSecret
orgs:
- name: your-github-org
redirectURI: https://argocd.example.com/api/dex/callback
id: github
name: GitHub
type: github
grpc:
addr: 0.0.0.0:5557
issuer: https://argocd.example.com/api/dex
oauth2:
skipApprovalScreen: true
staticClients:
- id: argo-cd
name: Argo CD
redirectURIs:
- https://argocd.example.com/auth/callback
secret: Dis9M-GA11oTwZVQQWdDklPQw-sWXZkWJFyyEhMs
- id: argo-cd-cli
name: Argo CD CLI
public: true
redirectURIs:
- http://localhost
storage:
type: memory
web:
http: 0.0.0.0:5556`
var expectedRedaction = `connectors:
- config:
clientID: aabbccddeeff00112233
clientSecret: '********'
orgs:
- name: your-github-org
redirectURI: https://argocd.example.com/api/dex/callback
id: github
name: GitHub
type: github
grpc:
addr: 0.0.0.0:5557
issuer: https://argocd.example.com/api/dex
oauth2:
skipApprovalScreen: true
staticClients:
- id: argo-cd
name: Argo CD
redirectURIs:
- https://argocd.example.com/auth/callback
secret: '********'
- id: argo-cd-cli
name: Argo CD CLI
public: true
redirectURIs:
- http://localhost
storage:
type: memory
web:
http: 0.0.0.0:5556
`
func TestSecretsRedactor(t *testing.T) {
assert.Equal(t, expectedRedaction, redactor(textToRedact))
}

192
cmd/argocd-util/projects.go Normal file
View File

@@ -0,0 +1,192 @@
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
appclient "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/typed/application/v1alpha1"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/diff"
"github.com/argoproj/argo-cd/util/kube"
"github.com/spf13/cobra"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/clientcmd"
)
func NewProjectsCommand() *cobra.Command {
var command = &cobra.Command{
Use: "projects",
Run: func(c *cobra.Command, args []string) {
c.HelpFunc()(c, args)
},
}
command.AddCommand(NewUpdatePolicyRuleCommand())
return command
}
func globMatch(pattern string, val string) bool {
if pattern == "*" {
return true
}
if ok, err := filepath.Match(pattern, val); ok && err == nil {
return true
}
return false
}
func getModification(modification string, resource string, scope string, permission string) (func(string, string) string, error) {
switch modification {
case "set":
if scope == "" {
return nil, fmt.Errorf("Flag --group cannot be empty if permission should be set in role")
}
if permission == "" {
return nil, fmt.Errorf("Flag --permission cannot be empty if permission should be set in role")
}
return func(proj string, action string) string {
return fmt.Sprintf("%s, %s, %s/%s, %s", resource, action, proj, scope, permission)
}, nil
case "remove":
return func(proj string, action string) string {
return ""
}, nil
}
return nil, fmt.Errorf("modification %s is not supported", modification)
}
func saveProject(updated v1alpha1.AppProject, orig v1alpha1.AppProject, projectsIf appclient.AppProjectInterface, dryRun bool) error {
fmt.Printf("===== %s ======\n", updated.Name)
target, err := kube.ToUnstructured(&updated)
errors.CheckError(err)
live, err := kube.ToUnstructured(&orig)
if err != nil {
return err
}
_ = diff.PrintDiff(updated.Name, target, live)
if !dryRun {
_, err = projectsIf.Update(&updated)
if err != nil {
return err
}
}
return nil
}
func formatPolicy(proj string, role string, permission string) string {
return fmt.Sprintf("p, proj:%s:%s, %s", proj, role, permission)
}
func split(input string, delimiter string) []string {
parts := strings.Split(input, delimiter)
for i := range parts {
parts[i] = strings.TrimSpace(parts[i])
}
return parts
}
func NewUpdatePolicyRuleCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
resource string
scope string
rolePattern string
permission string
dryRun bool
)
var command = &cobra.Command{
Use: "update-role-policy PROJECT_GLOB MODIFICATION ACTION",
Short: "Implement bulk project role update. Useful to back-fill existing project policies or remove obsolete actions.",
Example: ` # Add policy that allows executing any action (action/*) to roles which name matches to *deployer* in all projects
argocd-util projects update-role-policy '*' set 'action/*' --role '*deployer*' --resource applications --scope '*' --permission allow
# Remove policy that which manages running (action/*) from all roles which name matches *deployer* in all projects
argocd-util projects update-role-policy '*' remove override --role '*deployer*'
`,
Run: func(c *cobra.Command, args []string) {
if len(args) != 3 {
c.HelpFunc()(c, args)
os.Exit(1)
}
projectGlob := args[0]
modificationType := args[1]
action := args[2]
config, err := clientConfig.ClientConfig()
errors.CheckError(err)
config.QPS = 100
config.Burst = 50
namespace, _, err := clientConfig.Namespace()
errors.CheckError(err)
appclients := appclientset.NewForConfigOrDie(config)
modification, err := getModification(modificationType, resource, scope, permission)
errors.CheckError(err)
projIf := appclients.ArgoprojV1alpha1().AppProjects(namespace)
err = updateProjects(projIf, projectGlob, rolePattern, action, modification, dryRun)
errors.CheckError(err)
},
}
command.Flags().StringVar(&resource, "resource", "", "Resource e.g. 'applications'")
command.Flags().StringVar(&scope, "scope", "", "Resource scope e.g. '*'")
command.Flags().StringVar(&rolePattern, "role", "*", "Role name pattern e.g. '*deployer*'")
command.Flags().StringVar(&permission, "permission", "", "Action permission")
command.Flags().BoolVar(&dryRun, "dry-run", true, "Dry run")
clientConfig = cli.AddKubectlFlagsToCmd(command)
return command
}
func updateProjects(projIf appclient.AppProjectInterface, projectGlob string, rolePattern string, action string, modification func(string, string) string, dryRun bool) error {
projects, err := projIf.List(v1.ListOptions{})
if err != nil {
return err
}
for _, proj := range projects.Items {
if !globMatch(projectGlob, proj.Name) {
continue
}
origProj := proj.DeepCopy()
updated := false
for i, role := range proj.Spec.Roles {
if !globMatch(rolePattern, role.Name) {
continue
}
actionPolicyIndex := -1
for i := range role.Policies {
parts := split(role.Policies[i], ",")
if len(parts) != 6 || parts[3] != action {
continue
}
actionPolicyIndex = i
break
}
policyPermission := modification(proj.Name, action)
if actionPolicyIndex == -1 && policyPermission != "" {
updated = true
role.Policies = append(role.Policies, formatPolicy(proj.Name, role.Name, policyPermission))
} else if actionPolicyIndex > -1 && policyPermission == "" {
updated = true
role.Policies = append(role.Policies[:actionPolicyIndex], role.Policies[actionPolicyIndex+1:]...)
} else if actionPolicyIndex > -1 && policyPermission != "" {
updated = true
role.Policies[actionPolicyIndex] = formatPolicy(proj.Name, role.Name, policyPermission)
}
proj.Spec.Roles[i] = role
}
if updated {
err = saveProject(proj, *origProj, projIf, dryRun)
if err != nil {
return err
}
}
}
return nil
}

View File

@@ -0,0 +1,78 @@
package main
import (
"testing"
"github.com/stretchr/testify/assert"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/pkg/client/clientset/versioned/fake"
)
const (
namespace = "default"
)
func newProj(name string, roleNames ...string) *v1alpha1.AppProject {
var roles []v1alpha1.ProjectRole
for i := range roleNames {
roles = append(roles, v1alpha1.ProjectRole{Name: roleNames[i]})
}
return &v1alpha1.AppProject{ObjectMeta: v1.ObjectMeta{
Name: name,
Namespace: namespace,
}, Spec: v1alpha1.AppProjectSpec{
Roles: roles,
}}
}
func TestUpdateProjects_FindMatchingProject(t *testing.T) {
clientset := fake.NewSimpleClientset(newProj("foo", "test"), newProj("bar", "test"))
modification, err := getModification("set", "*", "*", "allow")
assert.NoError(t, err)
err = updateProjects(clientset.ArgoprojV1alpha1().AppProjects(namespace), "ba*", "*", "set", modification, false)
assert.NoError(t, err)
fooProj, err := clientset.ArgoprojV1alpha1().AppProjects(namespace).Get("foo", v1.GetOptions{})
assert.NoError(t, err)
assert.Len(t, fooProj.Spec.Roles[0].Policies, 0)
barProj, err := clientset.ArgoprojV1alpha1().AppProjects(namespace).Get("bar", v1.GetOptions{})
assert.NoError(t, err)
assert.EqualValues(t, barProj.Spec.Roles[0].Policies, []string{"p, proj:bar:test, *, set, bar/*, allow"})
}
func TestUpdateProjects_FindMatchingRole(t *testing.T) {
clientset := fake.NewSimpleClientset(newProj("proj", "foo", "bar"))
modification, err := getModification("set", "*", "*", "allow")
assert.NoError(t, err)
err = updateProjects(clientset.ArgoprojV1alpha1().AppProjects(namespace), "*", "fo*", "set", modification, false)
assert.NoError(t, err)
proj, err := clientset.ArgoprojV1alpha1().AppProjects(namespace).Get("proj", v1.GetOptions{})
assert.NoError(t, err)
assert.EqualValues(t, proj.Spec.Roles[0].Policies, []string{"p, proj:proj:foo, *, set, proj/*, allow"})
assert.Len(t, proj.Spec.Roles[1].Policies, 0)
}
func TestGetModification_SetPolicy(t *testing.T) {
modification, err := getModification("set", "*", "*", "allow")
assert.NoError(t, err)
policy := modification("proj", "myaction")
assert.Equal(t, "*, myaction, proj/*, allow", policy)
}
func TestGetModification_RemovePolicy(t *testing.T) {
modification, err := getModification("remove", "*", "*", "allow")
assert.NoError(t, err)
policy := modification("proj", "myaction")
assert.Equal(t, "", policy)
}
func TestGetModification_NotSupported(t *testing.T) {
_, err := getModification("bar", "*", "*", "allow")
assert.Errorf(t, err, "modification bar is not supported")
}

View File

@@ -17,6 +17,7 @@ import (
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
accountpkg "github.com/argoproj/argo-cd/pkg/apiclient/account"
"github.com/argoproj/argo-cd/pkg/apiclient/session"
"github.com/argoproj/argo-cd/server/rbacpolicy"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/localconfig"
@@ -33,6 +34,7 @@ func NewAccountCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
}
command.AddCommand(NewAccountUpdatePasswordCommand(clientOpts))
command.AddCommand(NewAccountGetUserInfoCommand(clientOpts))
command.AddCommand(NewAccountCanICommand(clientOpts))
return command
}
@@ -144,3 +146,41 @@ func NewAccountGetUserInfoCommand(clientOpts *argocdclient.ClientOptions) *cobra
command.Flags().StringVarP(&output, "output", "o", "", "Output format. One of: yaml, json")
return command
}
func NewAccountCanICommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
return &cobra.Command{
Use: "can-i ACTION RESOURCE SUBRESOURCE",
Short: "Can I",
Example: fmt.Sprintf(`
# Can I sync any app?
argocd account can-i sync applications '*'
# Can I update a project?
argocd account can-i update projects 'default'
# Can I create a cluster?
argocd account can-i create cluster '*'
Actions: %v
Resources: %v
`, rbacpolicy.Resources, rbacpolicy.Actions),
Run: func(c *cobra.Command, args []string) {
if len(args) != 3 {
c.HelpFunc()(c, args)
os.Exit(1)
}
conn, client := argocdclient.NewClientOrDie(clientOpts).NewAccountClientOrDie()
defer util.Close(conn)
ctx := context.Background()
response, err := client.CanI(ctx, &accountpkg.CanIRequest{
Action: args[0],
Resource: args[1],
Subresource: args[2],
})
errors.CheckError(err)
fmt.Println(response.Value)
},
}
}

View File

@@ -19,12 +19,12 @@ import (
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/yudai/gojsondiff"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/controller"
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/pkg/apiclient"
@@ -46,6 +46,7 @@ import (
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/resource/ignore"
"github.com/argoproj/argo-cd/util/templates"
"github.com/argoproj/argo-cd/util/text/label"
)
var (
@@ -98,10 +99,30 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
fileURL string
appName string
upsert bool
labels []string
)
var command = &cobra.Command{
Use: "create APPNAME",
Short: "Create an application",
Example: `
# Create a directory app
argocd app create guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --dest-namespace default --dest-server https://kubernetes.default.svc --directory-recurse
# Create a Jsonnet app
argocd app create jsonnet-guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path jsonnet-guestbook --dest-namespace default --dest-server https://kubernetes.default.svc --jsonnet-ext-str replicas=2
# Create a Helm app
argocd app create helm-guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path helm-guestbook --dest-namespace default --dest-server https://kubernetes.default.svc --helm-set replicaCount=2
# Create a Helm app from a Helm repo
argocd app create nginx-ingress --repo https://kubernetes-charts.storage.googleapis.com --helm-chart nginx-ingress --revision 1.24.3 --dest-namespace default --dest-server https://kubernetes.default.svc
# Create a Kustomize app
argocd app create kustomize-guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path kustomize-guestbook --dest-namespace default --dest-server https://kubernetes.default.svc --kustomize-image gcr.io/heptio-images/ks-guestbook-demo=0.1
# Create a app using a custom tool:
argocd app create ksane --repo https://github.com/argoproj/argocd-example-apps.git --path plugins/kasane --dest-namespace default --dest-server https://kubernetes.default.svc --config-management-plugin kasane
`,
Run: func(c *cobra.Command, args []string) {
var app argoappv1.Application
argocdClient := argocdclient.NewClientOrDie(clientOpts)
@@ -140,13 +161,15 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
Name: appName,
},
}
setAppOptions(c.Flags(), &app, &appOpts)
setAppSpecOptions(c.Flags(), &app.Spec, &appOpts)
setParameterOverrides(&app, appOpts.parameters)
setLabels(&app, labels)
}
if app.Name == "" {
c.HelpFunc()(c, args)
os.Exit(1)
}
conn, appIf := argocdClient.NewApplicationClientOrDie()
defer util.Close(conn)
appCreateRequest := applicationpkg.ApplicationCreateRequest{
@@ -161,6 +184,7 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
command.Flags().StringVar(&appName, "name", "", "A name for the app, ignored if a file is set (DEPRECATED)")
command.Flags().BoolVar(&upsert, "upsert", false, "Allows to override application with the same name even if supplied application spec is different from existing spec")
command.Flags().StringVarP(&fileURL, "file", "f", "", "Filename or URL to Kubernetes manifests for the app")
command.Flags().StringArrayVarP(&labels, "label", "l", []string{}, "Labels to apply to the app")
// Only complete files with appropriate extension.
err := command.Flags().SetAnnotation("file", cobra.BashCompFilenameExt, []string{"json", "yaml", "yml"})
if err != nil {
@@ -170,6 +194,12 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
return command
}
func setLabels(app *argoappv1.Application, labels []string) {
mapLabels, err := label.Parse(labels)
errors.CheckError(err)
app.SetLabels(mapLabels)
}
func getRefreshType(refresh bool, hardRefresh bool) *string {
if hardRefresh {
refreshType := string(argoappv1.RefreshTypeHard)
@@ -216,15 +246,10 @@ func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
windows := proj.Spec.SyncWindows.Matches(app)
switch output {
case "yaml":
yamlBytes, err := yaml.Marshal(app)
case "yaml", "json":
err := PrintResource(app, output)
errors.CheckError(err)
fmt.Println(string(yamlBytes))
case "json":
jsonBytes, err := json.MarshalIndent(app, "", " ")
errors.CheckError(err)
fmt.Println(string(jsonBytes))
case "":
case "wide", "":
aURL := appURL(acdClient, app.Name)
printAppSummaryTable(app, aURL, windows)
@@ -249,11 +274,11 @@ func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
_ = w.Flush()
}
default:
log.Fatalf("Unknown output format: %s", output)
errors.CheckError(fmt.Errorf("unknown output format: %s", output))
}
},
}
command.Flags().StringVarP(&output, "output", "o", "", "Output format. One of: yaml, json")
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide")
command.Flags().BoolVar(&showOperation, "show-operation", false, "Show application operation")
command.Flags().BoolVar(&showParams, "show-params", false, "Show application parameters and overrides")
command.Flags().BoolVar(&refresh, "refresh", false, "Refresh application data when retrieving")
@@ -353,9 +378,9 @@ func printAppSourceDetails(appSrc *argoappv1.ApplicationSource) {
}
func printAppConditions(w io.Writer, app *argoappv1.Application) {
fmt.Fprintf(w, "CONDITION\tMESSAGE\n")
_, _ = fmt.Fprintf(w, "CONDITION\tMESSAGE\tLAST TRANSITION\n")
for _, item := range app.Status.Conditions {
fmt.Fprintf(w, "%s\t%s\n", item.Type, item.Message)
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", item.Type, item.Message, item.LastTransitionTime)
}
}
@@ -393,15 +418,15 @@ func printParams(app *argoappv1.Application) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
if app.Spec.Source.Ksonnet != nil {
fmt.Println()
fmt.Fprintf(w, "COMPONENT\tNAME\tVALUE\n")
_, _ = fmt.Fprintf(w, "COMPONENT\tNAME\tVALUE\n")
for _, p := range app.Spec.Source.Ksonnet.Parameters {
fmt.Fprintf(w, "%s\t%s\t%s\n", p.Component, p.Name, truncateString(p.Value, paramLenLimit))
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", p.Component, p.Name, truncateString(p.Value, paramLenLimit))
}
} else if app.Spec.Source.Helm != nil {
fmt.Println()
fmt.Fprintf(w, "NAME\tVALUE\n")
_, _ = fmt.Fprintf(w, "NAME\tVALUE\n")
for _, p := range app.Spec.Source.Helm.Parameters {
fmt.Fprintf(w, "%s\t%s\n", p.Name, truncateString(p.Value, paramLenLimit))
_, _ = fmt.Fprintf(w, "%s\t%s\n", p.Name, truncateString(p.Value, paramLenLimit))
}
}
_ = w.Flush()
@@ -427,7 +452,7 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
defer util.Close(conn)
app, err := appIf.Get(ctx, &applicationpkg.ApplicationQuery{Name: &appName})
errors.CheckError(err)
visited := setAppOptions(c.Flags(), app, &appOpts)
visited := setAppSpecOptions(c.Flags(), &app.Spec, &appOpts)
if visited == 0 {
log.Error("Please set at least one option to update")
c.HelpFunc()(c, args)
@@ -445,71 +470,80 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
return command
}
func setAppOptions(flags *pflag.FlagSet, app *argoappv1.Application, appOpts *appOptions) int {
func setAppSpecOptions(flags *pflag.FlagSet, spec *argoappv1.ApplicationSpec, appOpts *appOptions) int {
visited := 0
flags.Visit(func(f *pflag.Flag) {
visited++
switch f.Name {
case "repo":
app.Spec.Source.RepoURL = appOpts.repoURL
spec.Source.RepoURL = appOpts.repoURL
case "path":
app.Spec.Source.Path = appOpts.appPath
spec.Source.Path = appOpts.appPath
case "helm-chart":
app.Spec.Source.Chart = appOpts.chart
spec.Source.Chart = appOpts.chart
case "env":
setKsonnetOpt(&app.Spec.Source, &appOpts.env)
setKsonnetOpt(&spec.Source, &appOpts.env)
case "revision":
app.Spec.Source.TargetRevision = appOpts.revision
spec.Source.TargetRevision = appOpts.revision
case "revision-history-limit":
i := int64(appOpts.revisionHistoryLimit)
spec.RevisionHistoryLimit = &i
case "values":
setHelmOpt(&app.Spec.Source, helmOpts{valueFiles: appOpts.valuesFiles})
setHelmOpt(&spec.Source, helmOpts{valueFiles: appOpts.valuesFiles})
case "release-name":
setHelmOpt(&app.Spec.Source, helmOpts{releaseName: appOpts.releaseName})
setHelmOpt(&spec.Source, helmOpts{releaseName: appOpts.releaseName})
case "helm-set":
setHelmOpt(&app.Spec.Source, helmOpts{helmSets: appOpts.helmSets})
setHelmOpt(&spec.Source, helmOpts{helmSets: appOpts.helmSets})
case "helm-set-string":
setHelmOpt(&app.Spec.Source, helmOpts{helmSetStrings: appOpts.helmSetStrings})
setHelmOpt(&spec.Source, helmOpts{helmSetStrings: appOpts.helmSetStrings})
case "directory-recurse":
app.Spec.Source.Directory = &argoappv1.ApplicationSourceDirectory{Recurse: appOpts.directoryRecurse}
spec.Source.Directory = &argoappv1.ApplicationSourceDirectory{Recurse: appOpts.directoryRecurse}
case "config-management-plugin":
app.Spec.Source.Plugin = &argoappv1.ApplicationSourcePlugin{Name: appOpts.configManagementPlugin}
spec.Source.Plugin = &argoappv1.ApplicationSourcePlugin{Name: appOpts.configManagementPlugin}
case "dest-server":
app.Spec.Destination.Server = appOpts.destServer
spec.Destination.Server = appOpts.destServer
case "dest-namespace":
app.Spec.Destination.Namespace = appOpts.destNamespace
spec.Destination.Namespace = appOpts.destNamespace
case "project":
app.Spec.Project = appOpts.project
spec.Project = appOpts.project
case "nameprefix":
setKustomizeOpt(&app.Spec.Source, &appOpts.namePrefix)
setKustomizeOpt(&spec.Source, kustomizeOpts{namePrefix: appOpts.namePrefix})
case "namesuffix":
setKustomizeOpt(&spec.Source, kustomizeOpts{nameSuffix: appOpts.nameSuffix})
case "kustomize-image":
setKustomizeImages(&app.Spec.Source, appOpts.kustomizeImages)
setKustomizeOpt(&spec.Source, kustomizeOpts{images: appOpts.kustomizeImages})
case "jsonnet-tla-str":
setJsonnetOpt(&app.Spec.Source, appOpts.jsonnetTlaStr, false)
setJsonnetOpt(&spec.Source, appOpts.jsonnetTlaStr, false)
case "jsonnet-tla-code":
setJsonnetOpt(&app.Spec.Source, appOpts.jsonnetTlaCode, true)
setJsonnetOpt(&spec.Source, appOpts.jsonnetTlaCode, true)
case "jsonnet-ext-var-str":
setJsonnetOptExtVar(&spec.Source, appOpts.jsonnetExtVarStr, false)
case "jsonnet-ext-var-code":
setJsonnetOptExtVar(&spec.Source, appOpts.jsonnetExtVarCode, true)
case "sync-policy":
switch appOpts.syncPolicy {
case "automated":
app.Spec.SyncPolicy = &argoappv1.SyncPolicy{
spec.SyncPolicy = &argoappv1.SyncPolicy{
Automated: &argoappv1.SyncPolicyAutomated{},
}
case "none":
app.Spec.SyncPolicy = nil
spec.SyncPolicy = nil
default:
log.Fatalf("Invalid sync-policy: %s", appOpts.syncPolicy)
}
}
})
if flags.Changed("auto-prune") {
if app.Spec.SyncPolicy == nil || app.Spec.SyncPolicy.Automated == nil {
if spec.SyncPolicy == nil || spec.SyncPolicy.Automated == nil {
log.Fatal("Cannot set --auto-prune: application not configured with automatic sync")
}
app.Spec.SyncPolicy.Automated.Prune = appOpts.autoPrune
spec.SyncPolicy.Automated.Prune = appOpts.autoPrune
}
if flags.Changed("self-heal") {
if app.Spec.SyncPolicy == nil || app.Spec.SyncPolicy.Automated == nil {
if spec.SyncPolicy == nil || spec.SyncPolicy.Automated == nil {
log.Fatal("Cannot set --self-helf: application not configured with automatic sync")
}
app.Spec.SyncPolicy.Automated.SelfHeal = appOpts.selfHeal
spec.SyncPolicy.Automated.SelfHeal = appOpts.selfHeal
}
return visited
@@ -527,22 +561,19 @@ func setKsonnetOpt(src *argoappv1.ApplicationSource, env *string) {
}
}
func setKustomizeOpt(src *argoappv1.ApplicationSource, namePrefix *string) {
if src.Kustomize == nil {
src.Kustomize = &argoappv1.ApplicationSourceKustomize{}
}
if namePrefix != nil {
src.Kustomize.NamePrefix = *namePrefix
}
if src.Kustomize.IsZero() {
src.Kustomize = nil
}
type kustomizeOpts struct {
namePrefix string
nameSuffix string
images []string
}
func setKustomizeImages(src *argoappv1.ApplicationSource, images []string) {
func setKustomizeOpt(src *argoappv1.ApplicationSource, opts kustomizeOpts) {
if src.Kustomize == nil {
src.Kustomize = &argoappv1.ApplicationSourceKustomize{}
}
for _, image := range images {
src.Kustomize.NamePrefix = opts.namePrefix
src.Kustomize.NameSuffix = opts.nameSuffix
for _, image := range opts.images {
src.Kustomize.MergeImage(argoappv1.KustomizeImage(image))
}
if src.Kustomize.IsZero() {
@@ -604,7 +635,7 @@ func setJsonnetOpt(src *argoappv1.ApplicationSource, tlaParameters []string, cod
Value: parts[1],
Code: code}
}
existingTLAs := []argoappv1.JsonnetVar{}
var existingTLAs []argoappv1.JsonnetVar
for i := range src.Directory.Jsonnet.TLAs {
if src.Directory.Jsonnet.TLAs[i].Code != code {
existingTLAs = append(existingTLAs, src.Directory.Jsonnet.TLAs[i])
@@ -616,7 +647,15 @@ func setJsonnetOpt(src *argoappv1.ApplicationSource, tlaParameters []string, cod
if src.Directory.IsZero() {
src.Directory = nil
}
}
func setJsonnetOptExtVar(src *argoappv1.ApplicationSource, jsonnetExtVar []string, code bool) {
if src.Directory == nil {
src.Directory = &argoappv1.ApplicationSourceDirectory{}
}
for _, j := range jsonnetExtVar {
src.Directory.Jsonnet.ExtVars = append(src.Directory.Jsonnet.ExtVars, argoappv1.NewJsonnetVar(j, code))
}
}
type appOptions struct {
@@ -625,6 +664,7 @@ type appOptions struct {
chart string
env string
revision string
revisionHistoryLimit int
destServer string
destNamespace string
parameters []string
@@ -637,10 +677,13 @@ type appOptions struct {
autoPrune bool
selfHeal bool
namePrefix string
nameSuffix string
directoryRecurse bool
configManagementPlugin string
jsonnetTlaStr []string
jsonnetTlaCode []string
jsonnetExtVarStr []string
jsonnetExtVarCode []string
kustomizeImages []string
}
@@ -649,23 +692,27 @@ func addAppFlags(command *cobra.Command, opts *appOptions) {
command.Flags().StringVar(&opts.appPath, "path", "", "Path in repository to the app directory, ignored if a file is set")
command.Flags().StringVar(&opts.chart, "helm-chart", "", "Helm Chart name")
command.Flags().StringVar(&opts.env, "env", "", "Application environment to monitor")
command.Flags().StringVar(&opts.revision, "revision", "", "The tracking source branch, tag, or commit the application will sync to")
command.Flags().StringVar(&opts.destServer, "dest-server", "", "K8s cluster URL (overrides the server URL specified in the ksonnet app.yaml)")
command.Flags().StringVar(&opts.revision, "revision", "", "The tracking source branch, tag, commit or Helm chart version the application will sync to")
command.Flags().IntVar(&opts.revisionHistoryLimit, "revision-history-limit", common.RevisionHistoryLimit, "How many items to keep in revision history")
command.Flags().StringVar(&opts.destServer, "dest-server", "", "K8s cluster URL (e.g. https://kubernetes.default.svc)")
command.Flags().StringVar(&opts.destNamespace, "dest-namespace", "", "K8s target namespace (overrides the namespace specified in the ksonnet app.yaml)")
command.Flags().StringArrayVarP(&opts.parameters, "parameter", "p", []string{}, "set a parameter override (e.g. -p guestbook=image=example/guestbook:latest)")
command.Flags().StringArrayVar(&opts.valuesFiles, "values", []string{}, "Helm values file(s) to use")
command.Flags().StringVar(&opts.releaseName, "release-name", "", "Helm release-name")
command.Flags().StringArrayVar(&opts.helmSets, "helm-set", []string{}, "Helm set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
command.Flags().StringArrayVar(&opts.helmSetStrings, "helm-set-string", []string{}, "Helm set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
command.Flags().StringArrayVar(&opts.helmSets, "helm-set", []string{}, "Helm set values on the command line (can be repeated to set several values: --helm-set key1=val1 --helm-set key2=val2)")
command.Flags().StringArrayVar(&opts.helmSetStrings, "helm-set-string", []string{}, "Helm set STRING values on the command line (can be repeated to set several values: --helm-set-string key1=val1 --helm-set-string key2=val2)")
command.Flags().StringVar(&opts.project, "project", "", "Application project name")
command.Flags().StringVar(&opts.syncPolicy, "sync-policy", "", "Set the sync policy (one of: automated, none)")
command.Flags().BoolVar(&opts.autoPrune, "auto-prune", false, "Set automatic pruning when sync is automated")
command.Flags().BoolVar(&opts.selfHeal, "self-heal", false, "Set self healing when sync is automated")
command.Flags().StringVar(&opts.namePrefix, "nameprefix", "", "Kustomize nameprefix")
command.Flags().StringVar(&opts.nameSuffix, "namesuffix", "", "Kustomize namesuffix")
command.Flags().BoolVar(&opts.directoryRecurse, "directory-recurse", false, "Recurse directory")
command.Flags().StringVar(&opts.configManagementPlugin, "config-management-plugin", "", "Config management plugin name")
command.Flags().StringArrayVar(&opts.jsonnetTlaStr, "jsonnet-tla-str", []string{}, "Jsonnet top level string arguments")
command.Flags().StringArrayVar(&opts.jsonnetTlaCode, "jsonnet-tla-code", []string{}, "Jsonnet top level code arguments")
command.Flags().StringArrayVar(&opts.jsonnetExtVarStr, "jsonnet-ext-var-str", []string{}, "Jsonnet string ext var")
command.Flags().StringArrayVar(&opts.jsonnetExtVarCode, "jsonnet-ext-var-code", []string{}, "Jsonnet ext var")
command.Flags().StringArrayVar(&opts.kustomizeImages, "kustomize-image", []string{}, "Kustomize images (e.g. --kustomize-image node:8.15.0 --kustomize-image mysql=mariadb,alpine@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d)")
}
@@ -771,8 +818,8 @@ func liveObjects(resources []*argoappv1.ResourceDiff) ([]*unstructured.Unstructu
return objs, nil
}
func getLocalObjects(app *argoappv1.Application, local, appLabelKey, kubeVersion string) []*unstructured.Unstructured {
manifestStrings := getLocalObjectsString(app, local, appLabelKey, kubeVersion, nil)
func getLocalObjects(app *argoappv1.Application, local, appLabelKey, kubeVersion string, kustomizeOptions *argoappv1.KustomizeOptions) []*unstructured.Unstructured {
manifestStrings := getLocalObjectsString(app, local, appLabelKey, kubeVersion, kustomizeOptions)
objs := make([]*unstructured.Unstructured, len(manifestStrings))
for i := range manifestStrings {
obj := unstructured.Unstructured{}
@@ -784,11 +831,12 @@ func getLocalObjects(app *argoappv1.Application, local, appLabelKey, kubeVersion
}
func getLocalObjectsString(app *argoappv1.Application, local, appLabelKey, kubeVersion string, kustomizeOptions *argoappv1.KustomizeOptions) []string {
res, err := repository.GenerateManifests(local, &repoapiclient.ManifestRequest{
ApplicationSource: &app.Spec.Source,
res, err := repository.GenerateManifests(local, "/", app.Spec.Source.TargetRevision, &repoapiclient.ManifestRequest{
Repo: &argoappv1.Repository{Repo: app.Spec.Source.RepoURL},
AppLabelKey: appLabelKey,
AppLabelValue: app.Name,
Namespace: app.Spec.Destination.Namespace,
ApplicationSource: &app.Spec.Source,
KustomizeOptions: kustomizeOptions,
KubeVersion: kubeVersion,
})
@@ -871,22 +919,13 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
defer util.Close(conn)
cluster, err := clusterIf.Get(context.Background(), &clusterpkg.ClusterQuery{Server: app.Spec.Destination.Server})
errors.CheckError(err)
util.Close(conn)
localObjs := groupLocalObjs(getLocalObjects(app, local, argoSettings.AppLabelKey, cluster.ServerVersion), liveObjs, app.Spec.Destination.Namespace)
localObjs := groupLocalObjs(getLocalObjects(app, local, argoSettings.AppLabelKey, cluster.ServerVersion, argoSettings.KustomizeOptions), liveObjs, app.Spec.Destination.Namespace)
for _, res := range resources.Items {
var live = &unstructured.Unstructured{}
err := json.Unmarshal([]byte(res.LiveState), &live)
err := json.Unmarshal([]byte(res.NormalizedLiveState), &live)
errors.CheckError(err)
var key kube.ResourceKey
if live != nil {
key = kube.GetResourceKey(live)
} else {
var target = &unstructured.Unstructured{}
err = json.Unmarshal([]byte(res.TargetState), &target)
errors.CheckError(err)
key = kube.GetResourceKey(target)
}
key := kube.ResourceKey{Name: res.Name, Namespace: res.Namespace, Group: res.Group, Kind: res.Kind}
if key.Kind == kube.SecretKind && key.Group == "" {
// Don't bother comparing secrets, argo-cd doesn't have access to k8s secret data
delete(localObjs, key)
@@ -925,7 +964,7 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
for i := range resources.Items {
res := resources.Items[i]
var live = &unstructured.Unstructured{}
err := json.Unmarshal([]byte(res.LiveState), &live)
err := json.Unmarshal([]byte(res.NormalizedLiveState), &live)
errors.CheckError(err)
var target = &unstructured.Unstructured{}
@@ -956,24 +995,26 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
}
normalizer, err := argo.NewDiffNormalizer(app.Spec.IgnoreDifferences, overrides)
errors.CheckError(err)
// Diff is already available in ResourceDiff Diff field but we have to recalculate diff again due to https://github.com/yudai/gojsondiff/issues/31
diffRes := diff.Diff(item.target, item.live, normalizer)
diffRes, err := diff.Diff(item.target, item.live, normalizer)
errors.CheckError(err)
if diffRes.Modified || item.target == nil || item.live == nil {
fmt.Printf("===== %s/%s %s/%s ======\n", item.key.Group, item.key.Kind, item.key.Namespace, item.key.Name)
var live *unstructured.Unstructured
var target *unstructured.Unstructured
if item.target != nil && item.live != nil {
target = item.live
live = item.live.DeepCopy()
gojsondiff.New().ApplyPatch(live.Object, diffRes.Diff)
live = &unstructured.Unstructured{}
err = json.Unmarshal(diffRes.PredictedLive, live)
errors.CheckError(err)
} else {
live = item.live
target = item.target
}
foundDiffs = true
err = diff.PrintDiff(item.key.Name, target, live)
errors.CheckError(err)
_ = diff.PrintDiff(item.key.Name, target, live)
}
}
if foundDiffs {
@@ -984,7 +1025,7 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
}
command.Flags().BoolVar(&refresh, "refresh", false, "Refresh application data when retrieving")
command.Flags().BoolVar(&hardRefresh, "hard-refresh", false, "Refresh application data as well as target manifests cache")
command.Flags().StringVar(&local, "local", "", "Compare live app to a local ksonnet app")
command.Flags().StringVar(&local, "local", "", "Compare live app to a local manifests")
return command
}
@@ -1037,7 +1078,7 @@ func printApplicationTable(apps []argoappv1.Application, output *string) {
} else {
fmtStr = "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n"
}
fmt.Fprintf(w, fmtStr, headers...)
_, _ = fmt.Fprintf(w, fmtStr, headers...)
for _, app := range apps {
vals := []interface{}{
app.Name,
@@ -1052,7 +1093,7 @@ func printApplicationTable(apps []argoappv1.Application, output *string) {
if *output == "wide" {
vals = append(vals, app.Spec.Source.RepoURL, app.Spec.Source.Path, app.Spec.Source.TargetRevision)
}
fmt.Fprintf(w, fmtStr, vals...)
_, _ = fmt.Fprintf(w, fmtStr, vals...)
}
_ = w.Flush()
}
@@ -1061,28 +1102,41 @@ func printApplicationTable(apps []argoappv1.Application, output *string) {
func NewApplicationListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
output string
selector string
projects []string
)
var command = &cobra.Command{
Use: "list",
Short: "List applications",
Example: ` # List all apps
argocd app list
# List apps by label, in this example we listing apps that are children of another app (aka app-of-apps)
argocd app list -l app.kubernetes.io/instance=my-app`,
Run: func(c *cobra.Command, args []string) {
conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie()
defer util.Close(conn)
apps, err := appIf.List(context.Background(), &applicationpkg.ApplicationQuery{})
apps, err := appIf.List(context.Background(), &applicationpkg.ApplicationQuery{Selector: selector})
errors.CheckError(err)
appList := apps.Items
if len(projects) != 0 {
appList = argo.FilterByProjects(appList, projects)
}
if output == "name" {
switch output {
case "yaml", "json":
err := PrintResourceList(appList, output, false)
errors.CheckError(err)
case "name":
printApplicationNames(appList)
} else {
case "wide", "":
printApplicationTable(appList, &output)
default:
errors.CheckError(fmt.Errorf("unknown output format: %s", output))
}
},
}
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: wide|name")
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: wide|name|json|yaml")
command.Flags().StringVarP(&selector, "selector", "l", "", "List apps by label")
command.Flags().StringArrayVarP(&projects, "project", "p", []string{}, "Filter by project name")
return command
}
@@ -1126,7 +1180,6 @@ func formatConditionsSummary(app argoappv1.Application) string {
const (
resourceFieldDelimiter = ":"
resourceFieldCount = 3
labelFieldDelimiter = "="
)
func parseSelectedResources(resources []string) []argoappv1.SyncOperationResource {
@@ -1149,21 +1202,6 @@ func parseSelectedResources(resources []string) []argoappv1.SyncOperationResourc
return selectedResources
}
func parseLabels(labels []string) (map[string]string, error) {
var selectedLabels map[string]string
if labels != nil {
selectedLabels = map[string]string{}
for _, r := range labels {
fields := strings.Split(r, labelFieldDelimiter)
if len(fields) != 2 {
return nil, fmt.Errorf("labels should have key%svalue, but instead got: %s", labelFieldDelimiter, r)
}
selectedLabels[fields[0]] = fields[1]
}
}
return selectedLabels, nil
}
// NewApplicationWaitCommand returns a new instance of an `argocd app wait` command
func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
@@ -1172,13 +1210,22 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
watchSuspended bool
watchOperations bool
timeout uint
selector string
resources []string
)
var command = &cobra.Command{
Use: "wait APPNAME",
Use: "wait [APPNAME.. | -l selector]",
Short: "Wait for an application to reach a synced and healthy state",
Example: ` # Wait for an app
argocd app wait my-app
# Wait for multiple apps
argocd app wait my-app other-app
# Wait for apps by label, in this example we waiting for apps that are children of another app (aka app-of-apps)
argocd app wait -l app.kubernetes.io/instance=apps`,
Run: func(c *cobra.Command, args []string) {
if len(args) != 1 {
if len(args) == 0 && selector == "" {
c.HelpFunc()(c, args)
os.Exit(1)
}
@@ -1189,15 +1236,27 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
watchSuspended = false
}
selectedResources := parseSelectedResources(resources)
appName := args[0]
appNames := args
acdClient := argocdclient.NewClientOrDie(clientOpts)
_, err := waitOnApplicationStatus(acdClient, appName, timeout, watchSync, watchHealth, watchOperations, watchSuspended, selectedResources)
errors.CheckError(err)
closer, appIf := acdClient.NewApplicationClientOrDie()
defer util.Close(closer)
if selector != "" {
list, err := appIf.List(context.Background(), &applicationpkg.ApplicationQuery{Selector: selector})
errors.CheckError(err)
for _, i := range list.Items {
appNames = append(appNames, i.Name)
}
}
for _, appName := range appNames {
_, err := waitOnApplicationStatus(acdClient, appName, timeout, watchSync, watchHealth, watchOperations, watchSuspended, selectedResources)
errors.CheckError(err)
}
},
}
command.Flags().BoolVar(&watchSync, "sync", false, "Wait for sync")
command.Flags().BoolVar(&watchHealth, "health", false, "Wait for health")
command.Flags().BoolVar(&watchSuspended, "suspended", false, "Wait for suspended")
command.Flags().StringVarP(&selector, "selector", "l", "", "Wait for apps by label")
command.Flags().StringArrayVar(&resources, "resource", []string{}, fmt.Sprintf("Sync only specific resources as GROUP%sKIND%sNAME. Fields may be blank. This option may be specified repeatedly", resourceFieldDelimiter, resourceFieldDelimiter))
command.Flags().BoolVar(&watchOperations, "operation", false, "Wait for pending operations")
command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time out after this many seconds")
@@ -1218,6 +1277,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
revision string
resources []string
labels []string
selector string
prune bool
dryRun bool
timeout uint
@@ -1227,10 +1287,23 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
local string
)
var command = &cobra.Command{
Use: "sync APPNAME",
Use: "sync [APPNAME... | -l selector]",
Short: "Sync an application to its target state",
Example: ` # Sync an app
argocd app sync my-app
# Sync multiples apps
argocd app sync my-app other-app
# Sync apps by label, in this example we sync apps that are children of another app (aka app-of-apps)
argocd app sync -l app.kubernetes.io/instance=my-app
# Sync a specific resource
# Resource should be formatted as GROUP:KIND:NAME. If no GROUP is specified then :KIND:NAME
argocd app sync my-app --resource :Service:my-service
argocd app sync my-app --resource argoproj.io:Rollout:my-rollout`,
Run: func(c *cobra.Command, args []string) {
if len(args) != 1 {
if len(args) == 0 && selector == "" {
c.HelpFunc()(c, args)
os.Exit(1)
}
@@ -1238,104 +1311,116 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
conn, appIf := acdClient.NewApplicationClientOrDie()
defer util.Close(conn)
appName := args[0]
selectedLabels, parseErr := parseLabels(labels)
if parseErr != nil {
log.Fatal(parseErr)
}
if len(selectedLabels) > 0 {
ctx := context.Background()
q := applicationpkg.ApplicationManifestQuery{
Name: &appName,
Revision: revision,
}
res, err := appIf.GetManifests(ctx, &q)
if err != nil {
log.Fatal(err)
}
for _, mfst := range res.Manifests {
obj, err := argoappv1.UnmarshalToUnstructured(mfst)
errors.CheckError(err)
for key, selectedValue := range selectedLabels {
if objectValue, ok := obj.GetLabels()[key]; ok && selectedValue == objectValue {
gvk := obj.GroupVersionKind()
resources = append(resources, fmt.Sprintf("%s:%s:%s", gvk.Group, gvk.Kind, obj.GetName()))
}
}
}
// If labels are provided and none are found return error only if specific resources were also not
// specified.
if len(resources) == 0 {
log.Fatalf("No matching resources found for labels: %v", labels)
return
}
}
selectedResources := parseSelectedResources(resources)
var localObjsStrings []string
if local != "" {
app, err := appIf.Get(context.Background(), &applicationpkg.ApplicationQuery{Name: &appName})
errors.CheckError(err)
if app.Spec.SyncPolicy != nil && app.Spec.SyncPolicy.Automated != nil {
log.Fatal("Cannot use local sync when Automatic Sync Policy is enabled")
}
errors.CheckError(err)
conn, settingsIf := acdClient.NewSettingsClientOrDie()
argoSettings, err := settingsIf.Get(context.Background(), &settingspkg.SettingsQuery{})
errors.CheckError(err)
util.Close(conn)
conn, clusterIf := acdClient.NewClusterClientOrDie()
defer util.Close(conn)
cluster, err := clusterIf.Get(context.Background(), &clusterpkg.ClusterQuery{Server: app.Spec.Destination.Server})
errors.CheckError(err)
util.Close(conn)
localObjsStrings = getLocalObjectsString(app, local, cluster.ServerVersion, argoSettings.AppLabelKey, argoSettings.KustomizeOptions)
}
syncReq := applicationpkg.ApplicationSyncRequest{
Name: &appName,
DryRun: dryRun,
Revision: revision,
Resources: selectedResources,
Prune: prune,
Manifests: localObjsStrings,
}
switch strategy {
case "apply":
syncReq.Strategy = &argoappv1.SyncStrategy{Apply: &argoappv1.SyncStrategyApply{}}
syncReq.Strategy.Apply.Force = force
case "", "hook":
syncReq.Strategy = &argoappv1.SyncStrategy{Hook: &argoappv1.SyncStrategyHook{}}
syncReq.Strategy.Hook.Force = force
default:
log.Fatalf("Unknown sync strategy: '%s'", strategy)
}
ctx := context.Background()
_, err := appIf.Sync(ctx, &syncReq)
selectedLabels, err := label.Parse(labels)
errors.CheckError(err)
if !async {
app, err := waitOnApplicationStatus(acdClient, appName, timeout, false, false, true, false, selectedResources)
appNames := args
if selector != "" {
list, err := appIf.List(context.Background(), &applicationpkg.ApplicationQuery{Selector: selector})
errors.CheckError(err)
// unlike list, we'd want to fail if nothing was found
if len(list.Items) == 0 {
log.Fatalf("no apps match selector %v", selector)
}
for _, i := range list.Items {
appNames = append(appNames, i.Name)
}
}
// Only get resources to be pruned if sync was application-wide
if len(selectedResources) == 0 {
pruningRequired := app.Status.OperationState.SyncResult.Resources.PruningRequired()
if pruningRequired > 0 {
log.Fatalf("%d resources require pruning", pruningRequired)
for _, appName := range appNames {
if len(selectedLabels) > 0 {
ctx := context.Background()
q := applicationpkg.ApplicationManifestQuery{
Name: &appName,
Revision: revision,
}
if !app.Status.OperationState.Phase.Successful() && !dryRun {
os.Exit(1)
res, err := appIf.GetManifests(ctx, &q)
if err != nil {
log.Fatal(err)
}
for _, mfst := range res.Manifests {
obj, err := argoappv1.UnmarshalToUnstructured(mfst)
errors.CheckError(err)
for key, selectedValue := range selectedLabels {
if objectValue, ok := obj.GetLabels()[key]; ok && selectedValue == objectValue {
gvk := obj.GroupVersionKind()
resources = append(resources, fmt.Sprintf("%s:%s:%s", gvk.Group, gvk.Kind, obj.GetName()))
}
}
}
// If labels are provided and none are found return error only if specific resources were also not
// specified.
if len(resources) == 0 {
log.Fatalf("No matching resources found for labels: %v", labels)
return
}
}
selectedResources := parseSelectedResources(resources)
var localObjsStrings []string
if local != "" {
app, err := appIf.Get(context.Background(), &applicationpkg.ApplicationQuery{Name: &appName})
errors.CheckError(err)
if app.Spec.SyncPolicy != nil && app.Spec.SyncPolicy.Automated != nil {
log.Fatal("Cannot use local sync when Automatic Sync Policy is enabled")
}
errors.CheckError(err)
conn, settingsIf := acdClient.NewSettingsClientOrDie()
argoSettings, err := settingsIf.Get(context.Background(), &settingspkg.SettingsQuery{})
errors.CheckError(err)
util.Close(conn)
conn, clusterIf := acdClient.NewClusterClientOrDie()
defer util.Close(conn)
cluster, err := clusterIf.Get(context.Background(), &clusterpkg.ClusterQuery{Server: app.Spec.Destination.Server})
errors.CheckError(err)
util.Close(conn)
localObjsStrings = getLocalObjectsString(app, local, argoSettings.AppLabelKey, cluster.ServerVersion, argoSettings.KustomizeOptions)
}
syncReq := applicationpkg.ApplicationSyncRequest{
Name: &appName,
DryRun: dryRun,
Revision: revision,
Resources: selectedResources,
Prune: prune,
Manifests: localObjsStrings,
}
switch strategy {
case "apply":
syncReq.Strategy = &argoappv1.SyncStrategy{Apply: &argoappv1.SyncStrategyApply{}}
syncReq.Strategy.Apply.Force = force
case "", "hook":
syncReq.Strategy = &argoappv1.SyncStrategy{Hook: &argoappv1.SyncStrategyHook{}}
syncReq.Strategy.Hook.Force = force
default:
log.Fatalf("Unknown sync strategy: '%s'", strategy)
}
ctx := context.Background()
_, err := appIf.Sync(ctx, &syncReq)
errors.CheckError(err)
if !async {
app, err := waitOnApplicationStatus(acdClient, appName, timeout, false, false, true, false, selectedResources)
errors.CheckError(err)
// Only get resources to be pruned if sync was application-wide
if len(selectedResources) == 0 {
pruningRequired := app.Status.OperationState.SyncResult.Resources.PruningRequired()
if pruningRequired > 0 {
log.Fatalf("%d resources require pruning", pruningRequired)
}
if !app.Status.OperationState.Phase.Successful() && !dryRun {
os.Exit(1)
}
}
}
}
@@ -1345,6 +1430,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
command.Flags().BoolVar(&prune, "prune", false, "Allow deleting unexpected resources")
command.Flags().StringVar(&revision, "revision", "", "Sync to a specific revision. Preserves parameter overrides")
command.Flags().StringArrayVar(&resources, "resource", []string{}, fmt.Sprintf("Sync only specific resources as GROUP%sKIND%sNAME. Fields may be blank. This option may be specified repeatedly", resourceFieldDelimiter, resourceFieldDelimiter))
command.Flags().StringVarP(&selector, "selector", "l", "", "Sync apps that match this label")
command.Flags().StringArrayVar(&labels, "label", []string{}, fmt.Sprintf("Sync only specific resources with a label. This option may be specified repeatedly."))
command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time out after this many seconds")
command.Flags().StringVar(&strategy, "strategy", "", "Sync strategy (one of: apply|hook)")
@@ -1542,7 +1628,7 @@ func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout
if len(selectedResources) > 0 {
selectedResourcesAreReady = true
for _, state := range getResourceStates(app, selectedResources) {
resourceIsReady := checkResourceStatus(watchSync, watchHealth, false, watchSuspended, state.Health, state.Status, nil)
resourceIsReady := checkResourceStatus(watchSync, watchHealth, watchOperation, watchSuspended, state.Health, state.Status, appEvent.Application.Operation)
if !resourceIsReady {
selectedResourcesAreReady = false
break
@@ -1565,7 +1651,7 @@ func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout
if prevState, found := prevStates[stateKey]; found {
if watchHealth && prevState.Health != argoappv1.HealthStatusUnknown && prevState.Health != argoappv1.HealthStatusDegraded && newState.Health == argoappv1.HealthStatusDegraded {
printFinalStatus(app)
return nil, fmt.Errorf("Application '%s' health state has transitioned from %s to %s", appName, prevState.Health, newState.Health)
return nil, fmt.Errorf("application '%s' health state has transitioned from %s to %s", appName, prevState.Health, newState.Health)
}
doPrint = prevState.Merge(newState)
} else {
@@ -1573,13 +1659,13 @@ func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout
doPrint = true
}
if doPrint {
fmt.Fprintf(w, waitFormatString, prevStates[stateKey].FormatItems()...)
_, _ = fmt.Fprintf(w, waitFormatString, prevStates[stateKey].FormatItems()...)
}
}
_ = w.Flush()
}
printFinalStatus(app)
return nil, fmt.Errorf("Timed out (%ds) waiting for app %q match desired state", timeout, appName)
return nil, fmt.Errorf("timed out (%ds) waiting for app %q match desired state", timeout, appName)
}
// setParameterOverrides updates an existing or appends a new parameter override in the application
@@ -1662,13 +1748,13 @@ func printApplicationHistoryIds(revHistory []argoappv1.RevisionHistory) {
// Print a history table for an application.
func printApplicationHistoryTable(revHistory []argoappv1.RevisionHistory) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "ID\tDATE\tREVISION\n")
_, _ = fmt.Fprintf(w, "ID\tDATE\tREVISION\n")
for _, depInfo := range revHistory {
rev := depInfo.Source.TargetRevision
if len(depInfo.Revision) >= 7 {
rev = fmt.Sprintf("%s (%s)", rev, depInfo.Revision[0:7])
}
fmt.Fprintf(w, "%d\t%s\t%s\n", depInfo.ID, depInfo.DeployedAt, rev)
_, _ = fmt.Fprintf(w, "%d\t%s\t%s\n", depInfo.ID, depInfo.DeployedAt, rev)
}
_ = w.Flush()
}
@@ -1962,11 +2048,11 @@ func filterResources(command *cobra.Command, resources []*argoappv1.ResourceDiff
if resourceName != "" && resourceName != obj.GetName() {
continue
}
if kind != "" && kind != gvk.Kind {
if kind != gvk.Kind {
continue
}
copy := obj.DeepCopy()
filteredObjects = append(filteredObjects, copy)
deepCopy := obj.DeepCopy()
filteredObjects = append(filteredObjects, deepCopy)
}
if len(filteredObjects) == 0 {
log.Fatal("No matching resource found")

View File

@@ -7,7 +7,6 @@ import (
"log"
"os"
"strconv"
"strings"
"text/tabwriter"
"github.com/ghodss/yaml"
@@ -121,7 +120,8 @@ func NewApplicationResourceActionsListCommand(clientOpts *argocdclient.ClientOpt
func NewApplicationResourceActionsRunCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var namespace string
var resourceName string
var kindArg string
var kind string
var group string
var all bool
var command = &cobra.Command{
Use: "run APPNAME ACTION",
@@ -130,7 +130,9 @@ func NewApplicationResourceActionsRunCommand(clientOpts *argocdclient.ClientOpti
command.Flags().StringVar(&resourceName, "resource-name", "", "Name of resource")
command.Flags().StringVar(&namespace, "namespace", "", "Namespace")
command.Flags().StringVar(&kindArg, "kind", "", "Kind")
command.Flags().StringVar(&kind, "kind", "", "Kind")
command.Flags().StringVar(&group, "group", "", "Group")
errors.CheckError(command.MarkFlagRequired("kind"))
command.Flags().BoolVar(&all, "all", false, "Indicates whether to run the action on multiple matching resources")
command.Run = func(c *cobra.Command, args []string) {
@@ -146,31 +148,14 @@ func NewApplicationResourceActionsRunCommand(clientOpts *argocdclient.ClientOpti
ctx := context.Background()
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{ApplicationName: &appName})
errors.CheckError(err)
var group string
var kind string
var actionNameOnly string
// Backwards comparability for running resume actions
if actionName == "resume" && kindArg == "Rollout" {
group = "argoproj.io"
kind = "Rollout"
actionNameOnly = "resume"
commandTail := ""
if resourceName != "" {
commandTail += " --resource-name " + resourceName
filteredObjects := filterResources(command, resources.Items, group, kind, namespace, resourceName, all)
var resGroup = filteredObjects[0].GroupVersionKind().Group
for i := range filteredObjects[1:] {
if filteredObjects[i].GroupVersionKind().Group != resGroup {
log.Fatal("Ambiguous resource group. Use flag --group to specify resource group explicitly.")
}
if namespace != "" {
commandTail += " --namespace " + namespace
}
if all {
commandTail += " --all"
}
fmt.Printf("\nWarning: this syntax for running the \"resume\" action has been deprecated. Please run the action as\n\n\targocd app actions run %s argoproj.io/Rollout/resume%s\n\n", appName, commandTail)
} else {
group, kind, actionNameOnly = parseActionName(actionName)
}
filteredObjects := filterResources(command, resources.Items, group, kind, namespace, resourceName, all)
for i := range filteredObjects {
obj := filteredObjects[i]
gvk := obj.GroupVersionKind()
@@ -181,18 +166,10 @@ func NewApplicationResourceActionsRunCommand(clientOpts *argocdclient.ClientOpti
ResourceName: objResourceName,
Group: gvk.Group,
Kind: gvk.Kind,
Action: actionNameOnly,
Action: actionName,
})
errors.CheckError(err)
}
}
return command
}
func parseActionName(action string) (string, string, string) {
actionSplit := strings.Split(action, "/")
if len(actionSplit) != 3 {
log.Fatal("Action name is malformed")
}
return actionSplit[0], actionSplit[1], actionSplit[2]
}

View File

@@ -8,24 +8,6 @@ import (
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
)
func TestParseLabels(t *testing.T) {
validLabels := []string{"key=value", "foo=bar", "intuit=inc"}
result, err := parseLabels(validLabels)
assert.NoError(t, err)
assert.Len(t, result, 3)
invalidLabels := []string{"key=value", "too=many=equals"}
_, err = parseLabels(invalidLabels)
assert.Error(t, err)
emptyLabels := []string{}
result, err = parseLabels(emptyLabels)
assert.NoError(t, err)
assert.Len(t, result, 0)
}
func Test_setHelmOpt(t *testing.T) {
t.Run("Zero", func(t *testing.T) {
src := v1alpha1.ApplicationSource{}

View File

@@ -29,6 +29,24 @@ func NewCertCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
c.HelpFunc()(c, args)
os.Exit(1)
},
Example: ` # Add a TLS certificate for cd.example.com to ArgoCD cert store from a file
argocd cert add-tls --from ~/mycert.pem cd.example.com
# Add a TLS certificate for cd.example.com to ArgoCD via stdin
cat ~/mycert.pem | argocd cert add-tls cd.example.com
# Add SSH known host entries for cd.example.com to ArgoCD by scanning host
ssh-keyscan cd.example.com | argocd cert add-ssh --batch
# List all known TLS certificates
argocd cert list --cert-type https
# Remove all TLS certificates for cd.example.com
argocd cert rm --cert-type https cd.example.com
# Remove all certificates and SSH known host entries for cd.example.com
argocd cert rm cd.example.com
`,
}
command.AddCommand(NewCertAddSSHCommand(clientOpts))
@@ -239,6 +257,7 @@ func NewCertListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
certType string
hostNamePattern string
sortOrder string
output string
)
var command = &cobra.Command{
Use: "list",
@@ -258,11 +277,22 @@ func NewCertListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
defer util.Close(conn)
certificates, err := certIf.ListCertificates(context.Background(), &certificatepkg.RepositoryCertificateQuery{HostNamePattern: hostNamePattern, CertType: certType})
errors.CheckError(err)
printCertTable(certificates.Items, sortOrder)
switch output {
case "yaml", "json":
err := PrintResourceList(certificates.Items, output, false)
errors.CheckError(err)
case "wide", "":
printCertTable(certificates.Items, sortOrder)
default:
errors.CheckError(fmt.Errorf("unknown output format: %s", output))
}
},
}
command.Flags().StringVar(&sortOrder, "sort", "", "set display sort order, valid: 'hostname', 'type'")
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide")
command.Flags().StringVar(&sortOrder, "sort", "", "set display sort order for output format wide. One of: hostname|type")
command.Flags().StringVar(&certType, "cert-type", "", "only list certificates of given type, valid: 'ssh','https'")
command.Flags().StringVar(&hostNamePattern, "hostname-pattern", "", "only list certificates for hosts matching given glob-pattern")
return command

View File

@@ -9,7 +9,6 @@ import (
"strings"
"text/tabwriter"
"github.com/ghodss/yaml"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"k8s.io/client-go/kubernetes"
@@ -34,6 +33,18 @@ func NewClusterCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clientc
c.HelpFunc()(c, args)
os.Exit(1)
},
Example: ` # List all known clusters in JSON format:
argocd cluster list -o json
# Add a target cluster configuration to ArgoCD. The context must exist in your kubectl config:
argocd cluster add example-cluster
# Get specific details about a cluster in plain text (wide) format:
argocd cluster get example-cluster -o wide
# Remove a target cluster context from ArgoCD
argocd cluster rm example-cluster
`,
}
command.AddCommand(NewClusterAddCommand(clientOpts, pathOpts))
@@ -52,9 +63,10 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
awsRoleArn string
awsClusterName string
systemNamespace string
namespaces []string
)
var command = &cobra.Command{
Use: "add",
Use: "add CONTEXT",
Short: fmt.Sprintf("%s cluster add CONTEXT", cliName),
Run: func(c *cobra.Command, args []string) {
var configAccess clientcmd.ConfigAccess = pathOpts
@@ -65,9 +77,10 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
}
config, err := configAccess.GetStartingConfig()
errors.CheckError(err)
clstContext := config.Contexts[args[0]]
contextName := args[0]
clstContext := config.Contexts[contextName]
if clstContext == nil {
log.Fatalf("Context %s does not exist in kubeconfig", args[0])
log.Fatalf("Context %s does not exist in kubeconfig", contextName)
}
overrides := clientcmd.ConfigOverrides{
@@ -88,12 +101,12 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
// Install RBAC resources for managing the cluster
clientset, err := kubernetes.NewForConfig(conf)
errors.CheckError(err)
managerBearerToken, err = clusterauth.InstallClusterManagerRBAC(clientset, systemNamespace)
managerBearerToken, err = clusterauth.InstallClusterManagerRBAC(clientset, systemNamespace, namespaces)
errors.CheckError(err)
}
conn, clusterIf := argocdclient.NewClientOrDie(clientOpts).NewClusterClientOrDie()
defer util.Close(conn)
clst := NewCluster(args[0], conf, managerBearerToken, awsAuthConf)
clst := newCluster(contextName, namespaces, conf, managerBearerToken, awsAuthConf)
if inCluster {
clst.Server = common.KubernetesInternalAPIServerAddr
}
@@ -101,9 +114,9 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
Cluster: clst,
Upsert: upsert,
}
clst, err = clusterIf.Create(context.Background(), &clstCreateReq)
_, err = clusterIf.Create(context.Background(), &clstCreateReq)
errors.CheckError(err)
fmt.Printf("Cluster '%s' added\n", clst.Name)
fmt.Printf("Cluster '%s' added\n", clst.Server)
},
}
command.PersistentFlags().StringVar(&pathOpts.LoadingRules.ExplicitPath, pathOpts.ExplicitFileFlag, pathOpts.LoadingRules.ExplicitPath, "use a particular kubeconfig file")
@@ -112,6 +125,7 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
command.Flags().StringVar(&awsClusterName, "aws-cluster-name", "", "AWS Cluster name if set then aws-iam-authenticator will be used to access cluster")
command.Flags().StringVar(&awsRoleArn, "aws-role-arn", "", "Optional AWS role arn. If set then AWS IAM Authenticator assume a role to perform cluster operations instead of the default AWS credential provider chain.")
command.Flags().StringVar(&systemNamespace, "system-namespace", common.DefaultSystemNamespace, "Use different system namespace")
command.Flags().StringArrayVar(&namespaces, "namespace", nil, "List of namespaces which are allowed to manage")
return command
}
@@ -154,7 +168,7 @@ func printKubeContexts(ca clientcmd.ConfigAccess) {
}
}
func NewCluster(name string, conf *rest.Config, managerBearerToken string, awsAuthConf *argoappv1.AWSAuthConfig) *argoappv1.Cluster {
func newCluster(name string, namespaces []string, conf *rest.Config, managerBearerToken string, awsAuthConf *argoappv1.AWSAuthConfig) *argoappv1.Cluster {
tlsClientConfig := argoappv1.TLSClientConfig{
Insecure: conf.TLSClientConfig.Insecure,
ServerName: conf.TLSClientConfig.ServerName,
@@ -166,8 +180,9 @@ func NewCluster(name string, conf *rest.Config, managerBearerToken string, awsAu
tlsClientConfig.CAData = data
}
clst := argoappv1.Cluster{
Server: conf.Host,
Name: name,
Server: conf.Host,
Name: name,
Namespaces: namespaces,
Config: argoappv1.ClusterConfig{
BearerToken: managerBearerToken,
TLSClientConfig: tlsClientConfig,
@@ -179,9 +194,13 @@ func NewCluster(name string, conf *rest.Config, managerBearerToken string, awsAu
// NewClusterGetCommand returns a new instance of an `argocd cluster get` command
func NewClusterGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
output string
)
var command = &cobra.Command{
Use: "get CLUSTER",
Short: "Get cluster information",
Use: "get SERVER",
Short: "Get cluster information",
Example: `argocd cluster get https://12.34.567.89`,
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
c.HelpFunc()(c, args)
@@ -189,23 +208,68 @@ func NewClusterGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
}
conn, clusterIf := argocdclient.NewClientOrDie(clientOpts).NewClusterClientOrDie()
defer util.Close(conn)
clusters := make([]argoappv1.Cluster, 0)
for _, clusterName := range args {
clst, err := clusterIf.Get(context.Background(), &clusterpkg.ClusterQuery{Server: clusterName})
errors.CheckError(err)
yamlBytes, err := yaml.Marshal(clst)
clusters = append(clusters, *clst)
}
switch output {
case "yaml", "json":
err := PrintResourceList(clusters, output, true)
errors.CheckError(err)
fmt.Printf("%v", string(yamlBytes))
case "wide", "":
printClusterDetails(clusters)
case "server":
printClusterServers(clusters)
default:
errors.CheckError(fmt.Errorf("unknown output format: %s", output))
}
},
}
// we have yaml as default to not break backwards-compatibility
command.Flags().StringVarP(&output, "output", "o", "yaml", "Output format. One of: json|yaml|wide|server")
return command
}
func strWithDefault(value string, def string) string {
if value == "" {
return def
}
return value
}
func formatNamespaces(cluster argoappv1.Cluster) string {
if len(cluster.Namespaces) == 0 {
return "all namespaces"
}
return strings.Join(cluster.Namespaces, ", ")
}
func printClusterDetails(clusters []argoappv1.Cluster) {
for _, cluster := range clusters {
fmt.Printf("Cluster information\n\n")
fmt.Printf(" Server URL: %s\n", cluster.Server)
fmt.Printf(" Server Name: %s\n", strWithDefault(cluster.Name, "-"))
fmt.Printf(" Server Version: %s\n", cluster.ServerVersion)
fmt.Printf(" Namespaces: %s\n", formatNamespaces(cluster))
fmt.Printf("\nTLS configuration\n\n")
fmt.Printf(" Client cert: %v\n", string(cluster.Config.TLSClientConfig.CertData) != "")
fmt.Printf(" Cert validation: %v\n", !cluster.Config.TLSClientConfig.Insecure)
fmt.Printf("\nAuthentication\n\n")
fmt.Printf(" Basic authentication: %v\n", cluster.Config.Username != "")
fmt.Printf(" oAuth authentication: %v\n", cluster.Config.BearerToken != "")
fmt.Printf(" AWS authentication: %v\n", cluster.Config.AWSAuthConfig != nil)
fmt.Println()
}
}
// NewClusterRemoveCommand returns a new instance of an `argocd cluster list` command
func NewClusterRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "rm CLUSTER",
Short: "Remove cluster credentials",
Use: "rm SERVER",
Short: "Remove cluster credentials",
Example: `argocd cluster rm https://12.34.567.89`,
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
c.HelpFunc()(c, args)
@@ -234,7 +298,11 @@ func printClusterTable(clusters []argoappv1.Cluster) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
_, _ = fmt.Fprintf(w, "SERVER\tNAME\tVERSION\tSTATUS\tMESSAGE\n")
for _, c := range clusters {
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", c.Server, c.Name, c.ServerVersion, c.ConnectionState.Status, c.ConnectionState.Message)
server := c.Server
if len(c.Namespaces) > 0 {
server = fmt.Sprintf("%s (%d namespaces)", c.Server, len(c.Namespaces))
}
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", server, c.Name, c.ServerVersion, c.ConnectionState.Status, c.ConnectionState.Message)
}
_ = w.Flush()
}
@@ -259,22 +327,29 @@ func NewClusterListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
defer util.Close(conn)
clusters, err := clusterIf.List(context.Background(), &clusterpkg.ClusterQuery{})
errors.CheckError(err)
if output == "server" {
switch output {
case "yaml", "json":
err := PrintResourceList(clusters.Items, output, false)
errors.CheckError(err)
case "server":
printClusterServers(clusters.Items)
} else {
case "wide", "":
printClusterTable(clusters.Items)
default:
errors.CheckError(fmt.Errorf("unknown output format: %s", output))
}
},
}
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: wide|server")
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|server")
return command
}
// NewClusterRotateAuthCommand returns a new instance of an `argocd cluster rotate-auth` command
func NewClusterRotateAuthCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "rotate-auth CLUSTER",
Short: fmt.Sprintf("%s cluster rotate-auth CLUSTER", cliName),
Use: "rotate-auth SERVER",
Short: fmt.Sprintf("%s cluster rotate-auth SERVER", cliName),
Example: fmt.Sprintf("%s cluster rotate-auth https://12.34.567.89", cliName),
Run: func(c *cobra.Command, args []string) {
if len(args) != 1 {
c.HelpFunc()(c, args)

View File

@@ -1,5 +1,13 @@
package commands
import (
"encoding/json"
"fmt"
"reflect"
"github.com/ghodss/yaml"
)
const (
cliName = "argocd"
@@ -7,3 +15,58 @@ const (
// the OAuth2 login flow.
DefaultSSOLocalPort = 8085
)
// PrintResource prints a single resource in YAML or JSON format to stdout according to the output format
func PrintResource(resource interface{}, output string) error {
switch output {
case "json":
jsonBytes, err := json.MarshalIndent(resource, "", " ")
if err != nil {
return err
}
fmt.Println(string(jsonBytes))
case "yaml":
yamlBytes, err := yaml.Marshal(resource)
if err != nil {
return err
}
fmt.Print(string(yamlBytes))
default:
return fmt.Errorf("unknown output format: %s", output)
}
return nil
}
// PrintResourceList marshals & prints a list of resources to stdout according to the output format
func PrintResourceList(resources interface{}, output string, single bool) error {
kt := reflect.ValueOf(resources)
// Sometimes, we want to marshal the first resource of a slice or array as single item
if kt.Kind() == reflect.Slice || kt.Kind() == reflect.Array {
if single && kt.Len() == 1 {
return PrintResource(kt.Index(0).Interface(), output)
}
// If we have a zero len list, prevent printing "null"
if kt.Len() == 0 {
return PrintResource([]string{}, output)
}
}
switch output {
case "json":
jsonBytes, err := json.MarshalIndent(resources, "", " ")
if err != nil {
return err
}
fmt.Println(string(jsonBytes))
case "yaml":
yamlBytes, err := yaml.Marshal(resources)
if err != nil {
return err
}
fmt.Print(string(yamlBytes))
default:
return fmt.Errorf("unknown output format: %s", output)
}
return nil
}

View File

@@ -0,0 +1,142 @@
package commands
import (
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
// Be careful with tabs vs. spaces in the following expected formats. Indents
// should all be spaces, no tabs.
const expectYamlSingle = `bar: ""
baz: foo
foo: bar
`
const expectJsonSingle = `{
"bar": "",
"baz": "foo",
"foo": "bar"
}
`
const expectYamlList = `one:
bar: ""
baz: foo
foo: bar
two:
bar: ""
baz: foo
foo: bar
`
const expectJsonList = `{
"one": {
"bar": "",
"baz": "foo",
"foo": "bar"
},
"two": {
"bar": "",
"baz": "foo",
"foo": "bar"
}
}
`
// Rather dirty hack to capture stdout from PrintResource() and PrintResourceList()
func captureOutput(f func() error) (string, error) {
stdout := os.Stdout
r, w, err := os.Pipe()
if err != nil {
return "", err
}
os.Stdout = w
err = f()
w.Close()
if err != nil {
os.Stdout = stdout
return "", err
}
str, err := ioutil.ReadAll(r)
os.Stdout = stdout
if err != nil {
return "", err
}
return string(str), err
}
func Test_PrintResource(t *testing.T) {
testResource := map[string]string{
"foo": "bar",
"bar": "",
"baz": "foo",
}
str, err := captureOutput(func() error {
err := PrintResource(testResource, "yaml")
return err
})
assert.NoError(t, err)
assert.Equal(t, str, expectYamlSingle)
str, err = captureOutput(func() error {
err := PrintResource(testResource, "json")
return err
})
assert.NoError(t, err)
assert.Equal(t, str, expectJsonSingle)
err = PrintResource(testResource, "unknown")
assert.Error(t, err)
}
func Test_PrintResourceList(t *testing.T) {
testResource := map[string]map[string]string{
"one": {
"foo": "bar",
"bar": "",
"baz": "foo",
},
"two": {
"foo": "bar",
"bar": "",
"baz": "foo",
},
}
testResource2 := make([]map[string]string, 0)
testResource2 = append(testResource2, testResource["one"])
str, err := captureOutput(func() error {
err := PrintResourceList(testResource, "yaml", false)
return err
})
assert.NoError(t, err)
assert.Equal(t, str, expectYamlList)
str, err = captureOutput(func() error {
err := PrintResourceList(testResource, "json", false)
return err
})
assert.NoError(t, err)
assert.Equal(t, str, expectJsonList)
str, err = captureOutput(func() error {
err := PrintResourceList(testResource2, "yaml", true)
return err
})
assert.NoError(t, err)
assert.Equal(t, str, expectYamlSingle)
str, err = captureOutput(func() error {
err := PrintResourceList(testResource2, "json", true)
return err
})
assert.NoError(t, err)
assert.Equal(t, str, expectJsonSingle)
err = PrintResourceList(testResource, "unknown", false)
assert.Error(t, err)
}

View File

@@ -8,8 +8,6 @@ import (
"strings"
"text/tabwriter"
"github.com/spf13/pflag"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@@ -22,7 +20,7 @@ import (
func NewContextCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var delete bool
var command = &cobra.Command{
Use: "context",
Use: "context [CONTEXT]",
Aliases: []string{"ctx"},
Short: "Switch between contexts",
Run: func(c *cobra.Command, args []string) {
@@ -30,22 +28,19 @@ func NewContextCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
localCfg, err := localconfig.ReadLocalConfig(clientOpts.ConfigPath)
errors.CheckError(err)
deletePresentContext := false
c.Flags().Visit(func(f *pflag.Flag) {
if f.Name == "delete" {
deletePresentContext = true
if delete {
if len(args) == 0 {
c.HelpFunc()(c, args)
os.Exit(1)
}
})
err := deleteContext(args[0], clientOpts.ConfigPath)
errors.CheckError(err)
return
}
if len(args) == 0 {
if deletePresentContext {
err := deleteContext(localCfg.CurrentContext, clientOpts.ConfigPath)
errors.CheckError(err)
return
} else {
printArgoCDContexts(clientOpts.ConfigPath)
return
}
printArgoCDContexts(clientOpts.ConfigPath)
return
}
ctxName := args[0]
@@ -100,7 +95,7 @@ func deleteContext(context, configPath string) error {
errors.CheckError(err)
} else {
if localCfg.CurrentContext == context {
localCfg.CurrentContext = localCfg.Contexts[0].Name
localCfg.CurrentContext = ""
}
err = localconfig.ValidateLocalConfig(*localCfg)
if err != nil {

View File

@@ -11,20 +11,27 @@ import (
)
const testConfig = `contexts:
- name: argocd.example.com:443
server: argocd.example.com:443
user: argocd.example.com:443
- name: argocd1.example.com:443
server: argocd1.example.com:443
user: argocd1.example.com:443
- name: argocd2.example.com:443
server: argocd2.example.com:443
user: argocd2.example.com:443
- name: localhost:8080
server: localhost:8080
user: localhost:8080
current-context: localhost:8080
servers:
- server: argocd.example.com:443
- server: argocd1.example.com:443
- server: argocd2.example.com:443
- plain-text: true
server: localhost:8080
users:
- auth-token: vErrYS3c3tReFRe$hToken
name: argocd.example.com:443
name: argocd1.example.com:443
refresh-token: vErrYS3c3tReFRe$hToken
- auth-token: vErrYS3c3tReFRe$hToken
name: argocd2.example.com:443
refresh-token: vErrYS3c3tReFRe$hToken
- auth-token: vErrYS3c3tReFRe$hToken
name: localhost:8080`
@@ -42,16 +49,30 @@ func TestContextDelete(t *testing.T) {
assert.Equal(t, localConfig.CurrentContext, "localhost:8080")
assert.Contains(t, localConfig.Contexts, localconfig.ContextRef{Name: "localhost:8080", Server: "localhost:8080", User: "localhost:8080"})
// Delete a non-current context
err = deleteContext("argocd1.example.com:443", testConfigFilePath)
assert.NoError(t, err)
localConfig, err = localconfig.ReadLocalConfig(testConfigFilePath)
assert.NoError(t, err)
assert.Equal(t, localConfig.CurrentContext, "localhost:8080")
assert.NotContains(t, localConfig.Contexts, localconfig.ContextRef{Name: "argocd1.example.com:443", Server: "argocd1.example.com:443", User: "argocd1.example.com:443"})
assert.NotContains(t, localConfig.Servers, localconfig.Server{Server: "argocd1.example.com:443"})
assert.NotContains(t, localConfig.Users, localconfig.User{AuthToken: "vErrYS3c3tReFRe$hToken", Name: "argocd1.example.com:443"})
assert.Contains(t, localConfig.Contexts, localconfig.ContextRef{Name: "argocd2.example.com:443", Server: "argocd2.example.com:443", User: "argocd2.example.com:443"})
assert.Contains(t, localConfig.Contexts, localconfig.ContextRef{Name: "localhost:8080", Server: "localhost:8080", User: "localhost:8080"})
// Delete the current context
err = deleteContext("localhost:8080", testConfigFilePath)
assert.NoError(t, err)
localConfig, err = localconfig.ReadLocalConfig(testConfigFilePath)
assert.NoError(t, err)
assert.Equal(t, localConfig.CurrentContext, "argocd.example.com:443")
assert.Equal(t, localConfig.CurrentContext, "")
assert.NotContains(t, localConfig.Contexts, localconfig.ContextRef{Name: "localhost:8080", Server: "localhost:8080", User: "localhost:8080"})
assert.NotContains(t, localConfig.Servers, localconfig.Server{PlainText: true, Server: "localhost:8080"})
assert.NotContains(t, localConfig.Users, localconfig.User{AuthToken: "vErrYS3c3tReFRe$hToken", Name: "localhost:8080"})
assert.Contains(t, localConfig.Contexts, localconfig.ContextRef{Name: "argocd.example.com:443", Server: "argocd.example.com:443", User: "argocd.example.com:443"})
assert.Contains(t, localConfig.Contexts, localconfig.ContextRef{Name: "argocd2.example.com:443", Server: "argocd2.example.com:443", User: "argocd2.example.com:443"})
// Write the file again so that no conflicts are made in git
err = ioutil.WriteFile(testConfigFilePath, []byte(testConfig), os.ModePerm)

View File

@@ -259,11 +259,12 @@ func oauth2Login(ctx context.Context, port int, oidcSettings *settingspkg.OIDCCo
}
fmt.Printf("Performing %s flow login: %s\n", grantType, url)
time.Sleep(1 * time.Second)
err = open.Run(url)
err = open.Start(url)
errors.CheckError(err)
go func() {
log.Debugf("Listen: %s", srv.Addr)
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
log.Fatalf("Temporary HTTP server failed: %s", err)
}
}()
errMsg := <-completionChan

View File

@@ -30,7 +30,9 @@ func TestLogout(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, localConfig.CurrentContext, "localhost:8080")
assert.NotContains(t, localConfig.Users, localconfig.User{AuthToken: "vErrYS3c3tReFRe$hToken", Name: "localhost:8080"})
assert.Contains(t, localConfig.Contexts, localconfig.ContextRef{Name: "argocd.example.com:443", Server: "argocd.example.com:443", User: "argocd.example.com:443"})
assert.Contains(t, localConfig.Contexts, localconfig.ContextRef{Name: "argocd1.example.com:443", Server: "argocd1.example.com:443", User: "argocd1.example.com:443"})
assert.Contains(t, localConfig.Contexts, localconfig.ContextRef{Name: "argocd2.example.com:443", Server: "argocd2.example.com:443", User: "argocd2.example.com:443"})
assert.Contains(t, localConfig.Contexts, localconfig.ContextRef{Name: "localhost:8080", Server: "localhost:8080", User: "localhost:8080"})
// Write the file again so that no conflicts are made in git
err = ioutil.WriteFile(testConfigFilePath, []byte(testConfig), os.ModePerm)

View File

@@ -527,14 +527,20 @@ func NewProjectListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
defer util.Close(conn)
projects, err := projIf.List(context.Background(), &projectpkg.ProjectQuery{})
errors.CheckError(err)
if output == "name" {
switch output {
case "yaml", "json":
err := PrintResourceList(projects.Items, output, false)
errors.CheckError(err)
case "name":
printProjectNames(projects.Items)
} else {
case "wide", "":
printProjectTable(projects.Items)
default:
errors.CheckError(fmt.Errorf("unknown output format: %s", output))
}
},
}
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: wide|name")
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|name")
return command
}
@@ -580,9 +586,60 @@ func printProjectLine(w io.Writer, p *v1alpha1.AppProject) {
fmt.Fprintf(w, "%s\t%s\t%v\t%v\t%v\t%v\t%v\n", p.Name, p.Spec.Description, destinations, sourceRepos, clusterWhitelist, namespaceBlacklist, formatOrphanedResources(p))
}
func printProject(p *v1alpha1.AppProject) {
const printProjFmtStr = "%-34s%s\n"
fmt.Printf(printProjFmtStr, "Name:", p.Name)
fmt.Printf(printProjFmtStr, "Description:", p.Spec.Description)
// Print destinations
dest0 := "<none>"
if len(p.Spec.Destinations) > 0 {
dest0 = fmt.Sprintf("%s,%s", p.Spec.Destinations[0].Server, p.Spec.Destinations[0].Namespace)
}
fmt.Printf(printProjFmtStr, "Destinations:", dest0)
for i := 1; i < len(p.Spec.Destinations); i++ {
fmt.Printf(printProjFmtStr, "", fmt.Sprintf("%s,%s", p.Spec.Destinations[i].Server, p.Spec.Destinations[i].Namespace))
}
// Print sources
src0 := "<none>"
if len(p.Spec.SourceRepos) > 0 {
src0 = p.Spec.SourceRepos[0]
}
fmt.Printf(printProjFmtStr, "Repositories:", src0)
for i := 1; i < len(p.Spec.SourceRepos); i++ {
fmt.Printf(printProjFmtStr, "", p.Spec.SourceRepos[i])
}
// Print whitelisted cluster resources
cwl0 := "<none>"
if len(p.Spec.ClusterResourceWhitelist) > 0 {
cwl0 = fmt.Sprintf("%s/%s", p.Spec.ClusterResourceWhitelist[0].Group, p.Spec.ClusterResourceWhitelist[0].Kind)
}
fmt.Printf(printProjFmtStr, "Whitelisted Cluster Resources:", cwl0)
for i := 1; i < len(p.Spec.ClusterResourceWhitelist); i++ {
fmt.Printf(printProjFmtStr, "", fmt.Sprintf("%s/%s", p.Spec.ClusterResourceWhitelist[i].Group, p.Spec.ClusterResourceWhitelist[i].Kind))
}
// Print blacklisted namespaced resources
rbl0 := "<none>"
if len(p.Spec.NamespaceResourceBlacklist) > 0 {
rbl0 = fmt.Sprintf("%s/%s", p.Spec.NamespaceResourceBlacklist[0].Group, p.Spec.NamespaceResourceBlacklist[0].Kind)
}
fmt.Printf(printProjFmtStr, "Blacklisted Namespaced Resources:", rbl0)
for i := 1; i < len(p.Spec.NamespaceResourceBlacklist); i++ {
fmt.Printf(printProjFmtStr, "", fmt.Sprintf("%s/%s", p.Spec.NamespaceResourceBlacklist[i].Group, p.Spec.NamespaceResourceBlacklist[i].Kind))
}
fmt.Printf(printProjFmtStr, "Orphaned Resources:", formatOrphanedResources(p))
}
// NewProjectGetCommand returns a new instance of an `argocd proj get` command
func NewProjectGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
const printProjFmtStr = "%-34s%s\n"
var (
output string
)
var command = &cobra.Command{
Use: "get PROJECT",
Short: "Get project details",
@@ -596,51 +653,19 @@ func NewProjectGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
defer util.Close(conn)
p, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
errors.CheckError(err)
fmt.Printf(printProjFmtStr, "Name:", p.Name)
fmt.Printf(printProjFmtStr, "Description:", p.Spec.Description)
// Print destinations
dest0 := "<none>"
if len(p.Spec.Destinations) > 0 {
dest0 = fmt.Sprintf("%s,%s", p.Spec.Destinations[0].Server, p.Spec.Destinations[0].Namespace)
switch output {
case "yaml", "json":
err := PrintResource(p, output)
errors.CheckError(err)
case "wide", "":
printProject(p)
default:
errors.CheckError(fmt.Errorf("unknown output format: %s", output))
}
fmt.Printf(printProjFmtStr, "Destinations:", dest0)
for i := 1; i < len(p.Spec.Destinations); i++ {
fmt.Printf(printProjFmtStr, "", fmt.Sprintf("%s,%s", p.Spec.Destinations[i].Server, p.Spec.Destinations[i].Namespace))
}
// Print sources
src0 := "<none>"
if len(p.Spec.SourceRepos) > 0 {
src0 = p.Spec.SourceRepos[0]
}
fmt.Printf(printProjFmtStr, "Repositories:", src0)
for i := 1; i < len(p.Spec.SourceRepos); i++ {
fmt.Printf(printProjFmtStr, "", p.Spec.SourceRepos[i])
}
// Print whitelisted cluster resources
cwl0 := "<none>"
if len(p.Spec.ClusterResourceWhitelist) > 0 {
cwl0 = fmt.Sprintf("%s/%s", p.Spec.ClusterResourceWhitelist[0].Group, p.Spec.ClusterResourceWhitelist[0].Kind)
}
fmt.Printf(printProjFmtStr, "Whitelisted Cluster Resources:", cwl0)
for i := 1; i < len(p.Spec.ClusterResourceWhitelist); i++ {
fmt.Printf(printProjFmtStr, "", fmt.Sprintf("%s/%s", p.Spec.ClusterResourceWhitelist[i].Group, p.Spec.ClusterResourceWhitelist[i].Kind))
}
// Print blacklisted namespaced resources
rbl0 := "<none>"
if len(p.Spec.NamespaceResourceBlacklist) > 0 {
rbl0 = fmt.Sprintf("%s/%s", p.Spec.NamespaceResourceBlacklist[0].Group, p.Spec.NamespaceResourceBlacklist[0].Kind)
}
fmt.Printf(printProjFmtStr, "Blacklisted Namespaced Resources:", rbl0)
for i := 1; i < len(p.Spec.NamespaceResourceBlacklist); i++ {
fmt.Printf(printProjFmtStr, "", fmt.Sprintf("%s/%s", p.Spec.NamespaceResourceBlacklist[i].Group, p.Spec.NamespaceResourceBlacklist[i].Kind))
}
fmt.Printf(printProjFmtStr, "Orphaned Resources:", formatOrphanedResources(p))
},
}
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide")
return command
}

View File

@@ -285,14 +285,20 @@ func NewProjectRoleListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
project, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
errors.CheckError(err)
if output == "name" {
switch output {
case "json", "yaml":
err := PrintResourceList(project.Spec.Roles, output, false)
errors.CheckError(err)
case "name":
printProjectRoleListName(project.Spec.Roles)
} else {
case "wide", "":
printProjectRoleListTable(project.Spec.Roles)
default:
errors.CheckError(fmt.Errorf("unknown output format: %s", output))
}
},
}
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: wide|name")
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|name")
return command
}

View File

@@ -235,6 +235,9 @@ func NewProjectWindowsUpdateCommand(clientOpts *argocdclient.ClientOptions) *cob
// NewProjectWindowsListCommand returns a new instance of an `argocd proj windows list` command
func NewProjectWindowsListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
output string
)
var command = &cobra.Command{
Use: "list PROJECT",
Short: "List project sync windows",
@@ -249,10 +252,18 @@ func NewProjectWindowsListCommand(clientOpts *argocdclient.ClientOptions) *cobra
proj, err := projIf.Get(context.Background(), &projectpkg.ProjectQuery{Name: projName})
errors.CheckError(err)
printSyncWindows(proj)
switch output {
case "yaml", "json":
err := PrintResourceList(proj.Spec.SyncWindows, output, false)
errors.CheckError(err)
case "wide", "":
printSyncWindows(proj)
default:
errors.CheckError(fmt.Errorf("unknown output format: %s", output))
}
},
}
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide")
return command
}

View File

@@ -10,6 +10,7 @@ import (
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
repositorypkg "github.com/argoproj/argo-cd/pkg/apiclient/repository"
@@ -50,13 +51,20 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
)
// For better readability and easier formatting
var repoAddExamples = `
Add a SSH repository using a private key for authentication, ignoring the server's host key:",
$ argocd repo add git@git.example.com --insecure-ignore-host-key --ssh-private-key-path ~/id_rsa",
Add a HTTPS repository using username/password and TLS client certificates:",
$ argocd repo add https://git.example.com --username git --password secret --tls-client-cert-path ~/mycert.crt --tls-client-cert-key-path ~/mycert.key",
Add a HTTPS repository using username/password without verifying the server's TLS certificate:",
$ argocd repo add https://git.example.com --username git --password secret --insecure-skip-server-verification",
var repoAddExamples = ` # Add a Git repository via SSH using a private key for authentication, ignoring the server's host key:
argocd repo add git@git.example.com:repos/repo --insecure-ignore-host-key --ssh-private-key-path ~/id_rsa
# Add a private Git repository via HTTPS using username/password and TLS client certificates:
argocd repo add https://git.example.com/repos/repo --username git --password secret --tls-client-cert-path ~/mycert.crt --tls-client-cert-key-path ~/mycert.key
# Add a private Git repository via HTTPS using username/password without verifying the server's TLS certificate
argocd repo add https://git.example.com/repos/repo --username git --password secret --insecure-skip-server-verification
# Add a public Helm repository named 'stable' via HTTPS
argocd repo add https://kubernetes-charts.storage.googleapis.com --type helm --name stable
# Add a private Helm repository named 'stable' via HTTPS
argocd repo add https://kubernetes-charts.storage.googleapis.com --type helm --name stable --username test --password test
`
var command = &cobra.Command{
@@ -108,11 +116,17 @@ Add a HTTPS repository using username/password without verifying the server's TL
}
}
// Set repository connection properties only when creating repository, not
// when creating repository credentials.
// InsecureIgnoreHostKey is deprecated and only here for backwards compat
repo.InsecureIgnoreHostKey = insecureIgnoreHostKey
repo.Insecure = insecureSkipServerVerification
repo.EnableLFS = enableLfs
if repo.Type == "helm" && repo.Name == "" {
errors.CheckError(fmt.Errorf("Must specify --name for repos of type 'helm'"))
}
conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoClientOrDie()
defer util.Close(conn)
@@ -125,6 +139,10 @@ Add a HTTPS repository using username/password without verifying the server's TL
// We let the server check access to the repository before adding it. If
// it is a private repo, but we cannot access with with the credentials
// that were supplied, we bail out.
//
// Skip validation if we are just adding credentials template, chances
// are high that we do not have the given URL pointing to a valid Git
// repo anyway.
repoAccessReq := repositorypkg.RepoAccessQuery{
Repo: repo.Repo,
Type: repo.Type,
@@ -143,13 +161,14 @@ Add a HTTPS repository using username/password without verifying the server's TL
Repo: &repo,
Upsert: upsert,
}
createdRepo, err := repoIf.Create(context.Background(), &repoCreateReq)
createdRepo, err := repoIf.CreateRepository(context.Background(), &repoCreateReq)
errors.CheckError(err)
fmt.Printf("repository '%s' added\n", createdRepo.Repo)
},
}
command.Flags().StringVar(&repo.Type, "type", "", "type of the repository, \"git\" or \"helm\"")
command.Flags().StringVar(&repo.Name, "name", "", "name of the repository")
command.Flags().StringVar(&repo.Type, "type", common.DefaultRepoType, "type of the repository, \"git\" or \"helm\"")
command.Flags().StringVar(&repo.Name, "name", "", "name of the repository, mandatory for repositories of type helm")
command.Flags().StringVar(&repo.Username, "username", "", "username to the repository")
command.Flags().StringVar(&repo.Password, "password", "", "password to the repository")
command.Flags().StringVar(&sshPrivateKeyPath, "ssh-private-key-path", "", "path to the private ssh key (e.g. ~/.ssh/id_rsa)")
@@ -175,7 +194,7 @@ func NewRepoRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoClientOrDie()
defer util.Close(conn)
for _, repoURL := range args {
_, err := repoIf.Delete(context.Background(), &repositorypkg.RepoQuery{Repo: repoURL})
_, err := repoIf.DeleteRepository(context.Background(), &repositorypkg.RepoQuery{Repo: repoURL})
errors.CheckError(err)
}
},
@@ -186,20 +205,24 @@ func NewRepoRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
// Print table of repo info
func printRepoTable(repos appsv1.Repositories) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
_, _ = fmt.Fprintf(w, "TYPE\tNAME\tREPO\tINSECURE\tLFS\tUSER\tSTATUS\tMESSAGE\n")
_, _ = fmt.Fprintf(w, "TYPE\tNAME\tREPO\tINSECURE\tLFS\tCREDS\tSTATUS\tMESSAGE\n")
for _, r := range repos {
var username string
if r.Username == "" {
username = "-"
var hasCreds string
if !r.HasCredentials() {
hasCreds = "false"
} else {
username = r.Username
if r.InheritedCreds {
hasCreds = "inherited"
} else {
hasCreds = "true"
}
}
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%v\t%s\t%s\t%s\n", r.Type, r.Name, r.Repo, r.IsInsecure(), r.EnableLFS, username, r.ConnectionState.Status, r.ConnectionState.Message)
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%v\t%s\t%s\t%s\n", r.Type, r.Name, r.Repo, r.IsInsecure(), r.EnableLFS, hasCreds, r.ConnectionState.Status, r.ConnectionState.Message)
}
_ = w.Flush()
}
// Print list of repo urls
// Print list of repo urls or url patterns for repository credentials
func printRepoUrls(repos appsv1.Repositories) {
for _, r := range repos {
fmt.Println(r.Repo)
@@ -209,7 +232,8 @@ func printRepoUrls(repos appsv1.Repositories) {
// NewRepoListCommand returns a new instance of an `argocd repo rm` command
func NewRepoListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
output string
output string
refresh string
)
var command = &cobra.Command{
Use: "list",
@@ -217,15 +241,32 @@ func NewRepoListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
Run: func(c *cobra.Command, args []string) {
conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoClientOrDie()
defer util.Close(conn)
repos, err := repoIf.List(context.Background(), &repositorypkg.RepoQuery{})
forceRefresh := false
switch refresh {
case "":
case "hard":
forceRefresh = true
default:
err := fmt.Errorf("--refresh must be one of: 'hard'")
errors.CheckError(err)
}
repos, err := repoIf.ListRepositories(context.Background(), &repositorypkg.RepoQuery{ForceRefresh: forceRefresh})
errors.CheckError(err)
if output == "url" {
switch output {
case "yaml", "json":
err := PrintResourceList(repos.Items, output, false)
errors.CheckError(err)
case "url":
printRepoUrls(repos.Items)
} else {
// wide is the default
case "wide", "":
printRepoTable(repos.Items)
default:
errors.CheckError(fmt.Errorf("unknown output format: %s", output))
}
},
}
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: wide|url")
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|url")
command.Flags().StringVar(&refresh, "refresh", "", "Force a cache refresh on connection status")
return command
}

View File

@@ -0,0 +1,203 @@
package commands
import (
"context"
"fmt"
"io/ioutil"
"os"
"text/tabwriter"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
repocredspkg "github.com/argoproj/argo-cd/pkg/apiclient/repocreds"
appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/git"
)
// NewRepoCredsCommand returns a new instance of an `argocd repo` command
func NewRepoCredsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "repocreds",
Short: "Manage repository connection parameters",
Run: func(c *cobra.Command, args []string) {
c.HelpFunc()(c, args)
os.Exit(1)
},
}
command.AddCommand(NewRepoCredsAddCommand(clientOpts))
command.AddCommand(NewRepoCredsListCommand(clientOpts))
command.AddCommand(NewRepoCredsRemoveCommand(clientOpts))
return command
}
// NewRepoCredsAddCommand returns a new instance of an `argocd repo add` command
func NewRepoCredsAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
repo appsv1.RepoCreds
upsert bool
sshPrivateKeyPath string
tlsClientCertPath string
tlsClientCertKeyPath string
)
// For better readability and easier formatting
var repocredsAddExamples = ` # Add credentials with user/pass authentication to use for all repositories under https://git.example.com/repos
argocd repocreds add https://git.example.com/repos/ --username git --password secret
# Add credentials with SSH private key authentication to use for all repositories under https://git.example.com/repos
argocd repocreds add https://git.example.com/repos/ --ssh-private-key-path ~/.ssh/id_rsa
`
var command = &cobra.Command{
Use: "add REPOURL",
Short: "Add git repository connection parameters",
Example: repocredsAddExamples,
Run: func(c *cobra.Command, args []string) {
if len(args) != 1 {
c.HelpFunc()(c, args)
os.Exit(1)
}
// Repository URL
repo.URL = args[0]
// Specifying ssh-private-key-path is only valid for SSH repositories
if sshPrivateKeyPath != "" {
if ok, _ := git.IsSSHURL(repo.URL); ok {
keyData, err := ioutil.ReadFile(sshPrivateKeyPath)
if err != nil {
log.Fatal(err)
}
repo.SSHPrivateKey = string(keyData)
} else {
err := fmt.Errorf("--ssh-private-key-path is only supported for SSH repositories.")
errors.CheckError(err)
}
}
// tls-client-cert-path and tls-client-cert-key-key-path must always be
// specified together
if (tlsClientCertPath != "" && tlsClientCertKeyPath == "") || (tlsClientCertPath == "" && tlsClientCertKeyPath != "") {
err := fmt.Errorf("--tls-client-cert-path and --tls-client-cert-key-path must be specified together")
errors.CheckError(err)
}
// Specifying tls-client-cert-path is only valid for HTTPS repositories
if tlsClientCertPath != "" {
if git.IsHTTPSURL(repo.URL) {
tlsCertData, err := ioutil.ReadFile(tlsClientCertPath)
errors.CheckError(err)
tlsCertKey, err := ioutil.ReadFile(tlsClientCertKeyPath)
errors.CheckError(err)
repo.TLSClientCertData = string(tlsCertData)
repo.TLSClientCertKey = string(tlsCertKey)
} else {
err := fmt.Errorf("--tls-client-cert-path is only supported for HTTPS repositories")
errors.CheckError(err)
}
}
conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoCredsClientOrDie()
defer util.Close(conn)
// If the user set a username, but didn't supply password via --password,
// then we prompt for it
if repo.Username != "" && repo.Password == "" {
repo.Password = cli.PromptPassword(repo.Password)
}
repoCreateReq := repocredspkg.RepoCredsCreateRequest{
Creds: &repo,
Upsert: upsert,
}
createdRepo, err := repoIf.CreateRepositoryCredentials(context.Background(), &repoCreateReq)
errors.CheckError(err)
fmt.Printf("repository credentials for '%s' added\n", createdRepo.URL)
},
}
command.Flags().StringVar(&repo.Username, "username", "", "username to the repository")
command.Flags().StringVar(&repo.Password, "password", "", "password to the repository")
command.Flags().StringVar(&sshPrivateKeyPath, "ssh-private-key-path", "", "path to the private ssh key (e.g. ~/.ssh/id_rsa)")
command.Flags().StringVar(&tlsClientCertPath, "tls-client-cert-path", "", "path to the TLS client cert (must be PEM format)")
command.Flags().StringVar(&tlsClientCertKeyPath, "tls-client-cert-key-path", "", "path to the TLS client cert's key path (must be PEM format)")
command.Flags().BoolVar(&upsert, "upsert", false, "Override an existing repository with the same name even if the spec differs")
return command
}
// NewRepoCredsRemoveCommand returns a new instance of an `argocd repo list` command
func NewRepoCredsRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "rm CREDSURL",
Short: "Remove repository credentials",
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
c.HelpFunc()(c, args)
os.Exit(1)
}
conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoCredsClientOrDie()
defer util.Close(conn)
for _, repoURL := range args {
_, err := repoIf.DeleteRepositoryCredentials(context.Background(), &repocredspkg.RepoCredsDeleteRequest{Url: repoURL})
errors.CheckError(err)
}
},
}
return command
}
// Print the repository credentials as table
func printRepoCredsTable(repos []appsv1.RepoCreds) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "URL PATTERN\tUSERNAME\tSSH_CREDS\tTLS_CREDS\n")
for _, r := range repos {
if r.Username == "" {
r.Username = "-"
}
fmt.Fprintf(w, "%s\t%s\t%v\t%v\n", r.URL, r.Username, r.SSHPrivateKey != "", r.TLSClientCertData != "")
}
_ = w.Flush()
}
// Print list of repo urls or url patterns for repository credentials
func printRepoCredsUrls(repos []appsv1.RepoCreds) {
for _, r := range repos {
fmt.Println(r.URL)
}
}
// NewRepoCredsListCommand returns a new instance of an `argocd repo rm` command
func NewRepoCredsListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
output string
)
var command = &cobra.Command{
Use: "list",
Short: "List configured repository credentials",
Run: func(c *cobra.Command, args []string) {
conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoCredsClientOrDie()
defer util.Close(conn)
repos, err := repoIf.ListRepositoryCredentials(context.Background(), &repocredspkg.RepoCredsQuery{})
errors.CheckError(err)
switch output {
case "yaml", "json":
err := PrintResourceList(repos.Items, output, false)
errors.CheckError(err)
case "url":
printRepoCredsUrls(repos.Items)
case "wide", "":
printRepoCredsTable(repos.Items)
default:
errors.CheckError(fmt.Errorf("unknown output format: %s", output))
}
},
}
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|url")
return command
}

View File

@@ -43,6 +43,7 @@ func NewCommand() *cobra.Command {
command.AddCommand(NewLoginCommand(&clientOpts))
command.AddCommand(NewReloginCommand(&clientOpts))
command.AddCommand(NewRepoCommand(&clientOpts))
command.AddCommand(NewRepoCredsCommand(&clientOpts))
command.AddCommand(NewContextCommand(&clientOpts))
command.AddCommand(NewProjectCommand(&clientOpts))
command.AddCommand(NewAccountCommand(&clientOpts))
@@ -60,5 +61,7 @@ func NewCommand() *cobra.Command {
command.PersistentFlags().BoolVar(&clientOpts.GRPCWeb, "grpc-web", config.GetBoolFlag("grpc-web"), "Enables gRPC-web protocol. Useful if Argo CD server is behind proxy which does not support HTTP2.")
command.PersistentFlags().StringVar(&logLevel, "loglevel", config.GetFlag("loglevel", "info"), "Set the logging level. One of: debug|info|warn|error")
command.PersistentFlags().StringSliceVarP(&clientOpts.Headers, "header", "H", []string{}, "Sets additional header to all requests made by Argo CD CLI. (Can be repeated multiple times to add multiple headers, also supports comma separated headers)")
command.PersistentFlags().BoolVar(&clientOpts.PortForward, "port-forward", config.GetBoolFlag("port-forward"), "Connect to a random argocd-server port using port forwarding")
command.PersistentFlags().StringVar(&clientOpts.PortForwardNamespace, "port-forward-namespace", config.GetFlag("port-forward-namespace", ""), "Namespace name which should be used for port forwarding")
return command
}

View File

@@ -1,18 +1,25 @@
contexts:
- name: argocd.example.com:443
server: argocd.example.com:443
user: argocd.example.com:443
- name: argocd1.example.com:443
server: argocd1.example.com:443
user: argocd1.example.com:443
- name: argocd2.example.com:443
server: argocd2.example.com:443
user: argocd2.example.com:443
- name: localhost:8080
server: localhost:8080
user: localhost:8080
current-context: localhost:8080
servers:
- server: argocd.example.com:443
- server: argocd1.example.com:443
- server: argocd2.example.com:443
- plain-text: true
server: localhost:8080
users:
- auth-token: vErrYS3c3tReFRe$hToken
name: argocd.example.com:443
name: argocd1.example.com:443
refresh-token: vErrYS3c3tReFRe$hToken
- auth-token: vErrYS3c3tReFRe$hToken
name: argocd2.example.com:443
refresh-token: vErrYS3c3tReFRe$hToken
- auth-token: vErrYS3c3tReFRe$hToken
name: localhost:8080

View File

@@ -3,6 +3,7 @@ package commands
import (
"context"
"fmt"
"io"
"github.com/golang/protobuf/ptypes/empty"
"github.com/spf13/cobra"
@@ -10,60 +11,113 @@ import (
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
"github.com/argoproj/argo-cd/pkg/apiclient/version"
"github.com/argoproj/argo-cd/util"
)
// NewVersionCmd returns a new `version` command to be used as a sub-command to root
func NewVersionCmd(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var short bool
var client bool
var (
short bool
client bool
output string
)
versionCmd := cobra.Command{
Use: "version",
Short: fmt.Sprintf("Print version information"),
Example: ` # Print the full version of client and server to stdout
argocd version
# Print only full version of the client - no connection to server will be made
argocd version --client
# Print the full version of client and server in JSON format
argocd version -o json
# Print only client and server core version strings in YAML format
argocd version --short -o yaml
`,
Run: func(cmd *cobra.Command, args []string) {
version := common.GetVersion()
fmt.Printf("%s: %s\n", cliName, version)
if !short {
fmt.Printf(" BuildDate: %s\n", version.BuildDate)
fmt.Printf(" GitCommit: %s\n", version.GitCommit)
fmt.Printf(" GitTreeState: %s\n", version.GitTreeState)
if version.GitTag != "" {
fmt.Printf(" GitTag: %s\n", version.GitTag)
var (
versionIf version.VersionServiceClient
serverVers *version.VersionMessage
conn io.Closer
err error
)
if !client {
// Get Server version
conn, versionIf = argocdclient.NewClientOrDie(clientOpts).NewVersionClientOrDie()
defer util.Close(conn)
serverVers, err = versionIf.Version(context.Background(), &empty.Empty{})
errors.CheckError(err)
}
switch output {
case "yaml", "json":
clientVers := common.GetVersion()
version := make(map[string]interface{})
if !short {
version["client"] = clientVers
} else {
version["client"] = map[string]string{cliName: clientVers.Version}
}
fmt.Printf(" GoVersion: %s\n", version.GoVersion)
fmt.Printf(" Compiler: %s\n", version.Compiler)
fmt.Printf(" Platform: %s\n", version.Platform)
}
if client {
return
}
// Get Server version
conn, versionIf := argocdclient.NewClientOrDie(clientOpts).NewVersionClientOrDie()
defer util.Close(conn)
serverVers, err := versionIf.Version(context.Background(), &empty.Empty{})
errors.CheckError(err)
fmt.Printf("%s: %s\n", "argocd-server", serverVers.Version)
if !short {
fmt.Printf(" BuildDate: %s\n", serverVers.BuildDate)
fmt.Printf(" GitCommit: %s\n", serverVers.GitCommit)
fmt.Printf(" GitTreeState: %s\n", serverVers.GitTreeState)
if version.GitTag != "" {
fmt.Printf(" GitTag: %s\n", serverVers.GitTag)
if !client {
if !short {
version["server"] = serverVers
} else {
version["server"] = map[string]string{"argocd-server": serverVers.Version}
}
}
fmt.Printf(" GoVersion: %s\n", serverVers.GoVersion)
fmt.Printf(" Compiler: %s\n", serverVers.Compiler)
fmt.Printf(" Platform: %s\n", serverVers.Platform)
fmt.Printf(" Ksonnet Version: %s\n", serverVers.KsonnetVersion)
fmt.Printf(" Kustomize Version: %s\n", serverVers.KustomizeVersion)
fmt.Printf(" Helm Version: %s\n", serverVers.HelmVersion)
fmt.Printf(" Kubectl Version: %s\n", serverVers.KubectlVersion)
err := PrintResource(version, output)
errors.CheckError(err)
case "short":
printVersion(serverVers, client, true)
case "wide", "":
// we use value of short for backward compatibility
printVersion(serverVers, client, short)
default:
errors.CheckError(fmt.Errorf("unknown output format: %s", output))
}
},
}
versionCmd.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|short")
versionCmd.Flags().BoolVar(&short, "short", false, "print just the version number")
versionCmd.Flags().BoolVar(&client, "client", false, "client version only (no server required)")
return &versionCmd
}
func printVersion(serverVers *version.VersionMessage, client bool, short bool) {
version := common.GetVersion()
fmt.Printf("%s: %s\n", cliName, version)
if !short {
fmt.Printf(" BuildDate: %s\n", version.BuildDate)
fmt.Printf(" GitCommit: %s\n", version.GitCommit)
fmt.Printf(" GitTreeState: %s\n", version.GitTreeState)
if version.GitTag != "" {
fmt.Printf(" GitTag: %s\n", version.GitTag)
}
fmt.Printf(" GoVersion: %s\n", version.GoVersion)
fmt.Printf(" Compiler: %s\n", version.Compiler)
fmt.Printf(" Platform: %s\n", version.Platform)
}
if client {
return
}
fmt.Printf("%s: %s\n", "argocd-server", serverVers.Version)
if !short {
fmt.Printf(" BuildDate: %s\n", serverVers.BuildDate)
fmt.Printf(" GitCommit: %s\n", serverVers.GitCommit)
fmt.Printf(" GitTreeState: %s\n", serverVers.GitTreeState)
if version.GitTag != "" {
fmt.Printf(" GitTag: %s\n", serverVers.GitTag)
}
fmt.Printf(" GoVersion: %s\n", serverVers.GoVersion)
fmt.Printf(" Compiler: %s\n", serverVers.Compiler)
fmt.Printf(" Platform: %s\n", serverVers.Platform)
fmt.Printf(" Ksonnet Version: %s\n", serverVers.KsonnetVersion)
fmt.Printf(" Kustomize Version: %s\n", serverVers.KustomizeVersion)
fmt.Printf(" Helm Version: %s\n", serverVers.HelmVersion)
fmt.Printf(" Kubectl Version: %s\n", serverVers.KubectlVersion)
}
}

View File

@@ -21,9 +21,10 @@ const (
ArgoCDTLSCertsConfigMapName = "argocd-tls-certs-cm"
)
// Default system namespace
// Some default configurables
const (
DefaultSystemNamespace = "kube-system"
DefaultRepoType = "git"
)
// Default listener ports for ArgoCD components
@@ -135,6 +136,8 @@ const (
EnvVarTLSDataPath = "ARGOCD_TLS_DATA_PATH"
// Specifies number of git remote operations attempts count
EnvGitAttemptsCount = "ARGOCD_GIT_ATTEMPTS_COUNT"
// Overrides git submodule support, true by default
EnvGitSubmoduleEnabled = "ARGOCD_GIT_MODULES_ENABLED"
)
const (

View File

@@ -7,6 +7,7 @@ import (
"math"
"reflect"
"runtime/debug"
"strconv"
"strings"
"sync"
"time"
@@ -24,6 +25,9 @@ import (
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
// make sure to register workqueue prometheus metrics
_ "k8s.io/kubernetes/pkg/util/workqueue/prometheus"
"github.com/argoproj/argo-cd/common"
statecache "github.com/argoproj/argo-cd/controller/cache"
"github.com/argoproj/argo-cd/controller/metrics"
@@ -37,7 +41,7 @@ import (
"github.com/argoproj/argo-cd/reposerver/apiclient"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/argo"
argocache "github.com/argoproj/argo-cd/util/cache"
appstatecache "github.com/argoproj/argo-cd/util/cache/appstate"
"github.com/argoproj/argo-cd/util/db"
"github.com/argoproj/argo-cd/util/diff"
"github.com/argoproj/argo-cd/util/kube"
@@ -65,30 +69,37 @@ func (a CompareWith) Max(b CompareWith) CompareWith {
return CompareWith(math.Max(float64(a), float64(b)))
}
func (a CompareWith) Pointer() *CompareWith {
return &a
}
// ApplicationController is the controller for application resources.
type ApplicationController struct {
cache *argocache.Cache
namespace string
kubeClientset kubernetes.Interface
kubectl kube.Kubectl
applicationClientset appclientset.Interface
auditLogger *argo.AuditLogger
appRefreshQueue workqueue.RateLimitingInterface
appOperationQueue workqueue.RateLimitingInterface
appInformer cache.SharedIndexInformer
appLister applisters.ApplicationLister
projInformer cache.SharedIndexInformer
appStateManager AppStateManager
stateCache statecache.LiveStateCache
statusRefreshTimeout time.Duration
selfHealTimeout time.Duration
repoClientset apiclient.Clientset
db db.ArgoDB
settingsMgr *settings_util.SettingsManager
refreshRequestedApps map[string]CompareWith
refreshRequestedAppsMutex *sync.Mutex
metricsServer *metrics.MetricsServer
kubectlSemaphore *semaphore.Weighted
cache *appstatecache.Cache
namespace string
kubeClientset kubernetes.Interface
kubectl kube.Kubectl
applicationClientset appclientset.Interface
auditLogger *argo.AuditLogger
// queue contains app namespace/name
appRefreshQueue workqueue.RateLimitingInterface
// queue contains app namespace/name/comparisonType and used to request app refresh with the predefined comparison type
appComparisonTypeRefreshQueue workqueue.RateLimitingInterface
appOperationQueue workqueue.RateLimitingInterface
appInformer cache.SharedIndexInformer
appLister applisters.ApplicationLister
projInformer cache.SharedIndexInformer
appStateManager AppStateManager
stateCache statecache.LiveStateCache
statusRefreshTimeout time.Duration
selfHealTimeout time.Duration
repoClientset apiclient.Clientset
db db.ArgoDB
settingsMgr *settings_util.SettingsManager
refreshRequestedApps map[string]CompareWith
refreshRequestedAppsMutex *sync.Mutex
metricsServer *metrics.MetricsServer
kubectlSemaphore *semaphore.Weighted
}
type ApplicationControllerConfig struct {
@@ -103,30 +114,32 @@ func NewApplicationController(
kubeClientset kubernetes.Interface,
applicationClientset appclientset.Interface,
repoClientset apiclient.Clientset,
argoCache *argocache.Cache,
argoCache *appstatecache.Cache,
kubectl kube.Kubectl,
appResyncPeriod time.Duration,
selfHealTimeout time.Duration,
metricsPort int,
kubectlParallelismLimit int64,
) (*ApplicationController, error) {
log.Infof("appResyncPeriod=%v", appResyncPeriod)
db := db.NewDB(namespace, settingsMgr, kubeClientset)
ctrl := ApplicationController{
cache: argoCache,
namespace: namespace,
kubeClientset: kubeClientset,
kubectl: kubectl,
applicationClientset: applicationClientset,
repoClientset: repoClientset,
appRefreshQueue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
appOperationQueue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
db: db,
statusRefreshTimeout: appResyncPeriod,
refreshRequestedApps: make(map[string]CompareWith),
refreshRequestedAppsMutex: &sync.Mutex{},
auditLogger: argo.NewAuditLogger(namespace, kubeClientset, "argocd-application-controller"),
settingsMgr: settingsMgr,
selfHealTimeout: selfHealTimeout,
cache: argoCache,
namespace: namespace,
kubeClientset: kubeClientset,
kubectl: kubectl,
applicationClientset: applicationClientset,
repoClientset: repoClientset,
appRefreshQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "app_reconciliation_queue"),
appOperationQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "app_operation_processing_queue"),
appComparisonTypeRefreshQueue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
db: db,
statusRefreshTimeout: appResyncPeriod,
refreshRequestedApps: make(map[string]CompareWith),
refreshRequestedAppsMutex: &sync.Mutex{},
auditLogger: argo.NewAuditLogger(namespace, kubeClientset, "argocd-application-controller"),
settingsMgr: settingsMgr,
selfHealTimeout: selfHealTimeout,
}
if kubectlParallelismLimit > 0 {
ctrl.kubectlSemaphore = semaphore.NewWeighted(kubectlParallelismLimit)
@@ -136,7 +149,8 @@ func NewApplicationController(
if err != nil {
return nil, err
}
projInformer := v1alpha1.NewAppProjectInformer(applicationClientset, namespace, appResyncPeriod, cache.Indexers{})
indexers := cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}
projInformer := v1alpha1.NewAppProjectInformer(applicationClientset, namespace, appResyncPeriod, indexers)
metricsAddr := fmt.Sprintf("0.0.0.0:%d", metricsPort)
ctrl.metricsServer = metrics.NewMetricsServer(metricsAddr, appLister, func() error {
_, err := kubeClientset.Discovery().ServerVersion()
@@ -194,7 +208,7 @@ func (ctrl *ApplicationController) handleObjectUpdated(managedByApp map[string]b
continue
}
// exclude resource unless it is permitted in the app project. If project is not permitted then it is not controlled by the user and there is no point showing the warning.
if proj, err := ctrl.getAppProj(app); err == nil && proj.IsResourcePermitted(metav1.GroupKind{Group: ref.GroupVersionKind().Group, Kind: ref.Kind}, true) &&
if proj, err := ctrl.getAppProj(app); err == nil && proj.IsGroupKindPermitted(ref.GroupVersionKind().GroupKind(), true) &&
!isKnownOrphanedResourceExclusion(kube.NewResourceKey(ref.GroupVersionKind().Group, ref.GroupVersionKind().Kind, ref.Namespace, ref.Name)) {
managedByApp[app.Name] = false
@@ -203,22 +217,17 @@ func (ctrl *ApplicationController) handleObjectUpdated(managedByApp map[string]b
}
}
for appName, isManagedResource := range managedByApp {
skipForceRefresh := false
obj, exists, err := ctrl.appInformer.GetIndexer().GetByKey(ctrl.namespace + "/" + appName)
if app, ok := obj.(*appv1.Application); exists && err == nil && ok && isSelfReferencedApp(app, ref) {
// Don't force refresh app if related resource is application itself. This prevents infinite reconciliation loop.
skipForceRefresh = true
continue
}
if !skipForceRefresh {
level := ComparisonWithNothing
if isManagedResource {
level = CompareWithRecent
}
ctrl.requestAppRefresh(appName, level)
level := ComparisonWithNothing
if isManagedResource {
level = CompareWithRecent
}
ctrl.appRefreshQueue.Add(fmt.Sprintf("%s/%s", ctrl.namespace, appName))
ctrl.requestAppRefresh(appName, &level, nil)
}
}
@@ -301,7 +310,7 @@ func (ctrl *ApplicationController) getResourceTree(a *appv1.Application, managed
}
orphanedNodes := make([]appv1.ResourceNode, 0)
for k := range orphanedNodesMap {
if k.Namespace != "" && proj.IsResourcePermitted(metav1.GroupKind{Group: k.Group, Kind: k.Kind}, true) && !isKnownOrphanedResourceExclusion(k) {
if k.Namespace != "" && proj.IsGroupKindPermitted(k.GroupKind(), true) && !isKnownOrphanedResourceExclusion(k) {
err := ctrl.stateCache.IterateHierarchy(a.Spec.Destination.Server, k, func(child appv1.ResourceNode, appName string) {
belongToAnotherApp := false
if appName != "" {
@@ -350,7 +359,11 @@ func (ctrl *ApplicationController) managedResources(comparisonResult *comparison
if err != nil {
return nil, err
}
resDiff = *diff.Diff(target, live, comparisonResult.diffNormalizer)
resDiffPtr, err := diff.Diff(target, live, comparisonResult.diffNormalizer)
if err != nil {
return nil, err
}
resDiff = *resDiffPtr
}
if live != nil {
@@ -377,6 +390,8 @@ func (ctrl *ApplicationController) managedResources(comparisonResult *comparison
return nil, err
}
item.Diff = jsonDiff
item.PredictedLiveState = string(resDiff.PredictedLive)
item.NormalizedLiveState = string(resDiff.NormalizedLive)
items[i] = &item
}
@@ -387,7 +402,10 @@ func (ctrl *ApplicationController) managedResources(comparisonResult *comparison
func (ctrl *ApplicationController) Run(ctx context.Context, statusProcessors int, operationProcessors int) {
defer runtime.HandleCrash()
defer ctrl.appRefreshQueue.ShutDown()
defer ctrl.appComparisonTypeRefreshQueue.ShutDown()
defer ctrl.appOperationQueue.ShutDown()
ctrl.metricsServer.RegisterClustersInfoSource(ctx, ctrl.stateCache)
go ctrl.appInformer.Run(ctx.Done())
go ctrl.projInformer.Run(ctx.Done())
@@ -413,13 +431,30 @@ func (ctrl *ApplicationController) Run(ctx context.Context, statusProcessors int
}, time.Second, ctx.Done())
}
go wait.Until(func() {
for ctrl.processAppComparisonTypeQueueItem() {
}
}, time.Second, ctx.Done())
<-ctx.Done()
}
func (ctrl *ApplicationController) requestAppRefresh(appName string, compareWith CompareWith) {
ctrl.refreshRequestedAppsMutex.Lock()
defer ctrl.refreshRequestedAppsMutex.Unlock()
ctrl.refreshRequestedApps[appName] = compareWith.Max(ctrl.refreshRequestedApps[appName])
func (ctrl *ApplicationController) requestAppRefresh(appName string, compareWith *CompareWith, after *time.Duration) {
key := fmt.Sprintf("%s/%s", ctrl.namespace, appName)
if compareWith != nil && after != nil {
ctrl.appComparisonTypeRefreshQueue.AddAfter(fmt.Sprintf("%s/%d", key, compareWith), *after)
} else {
if compareWith != nil {
ctrl.refreshRequestedAppsMutex.Lock()
ctrl.refreshRequestedApps[appName] = compareWith.Max(ctrl.refreshRequestedApps[appName])
ctrl.refreshRequestedAppsMutex.Unlock()
}
if after != nil {
ctrl.appRefreshQueue.AddAfter(key, *after)
} else {
ctrl.appRefreshQueue.Add(key)
}
}
}
func (ctrl *ApplicationController) isRefreshRequested(appName string) (bool, CompareWith) {
@@ -463,7 +498,7 @@ func (ctrl *ApplicationController) processAppOperationQueueItem() (processNext b
if app.Operation != nil {
ctrl.processRequestedAppOperation(app)
} else if app.DeletionTimestamp != nil && app.CascadedDeletion() {
err = ctrl.finalizeApplicationDeletion(app)
_, err = ctrl.finalizeApplicationDeletion(app)
if err != nil {
ctrl.setAppCondition(app, appv1.ApplicationCondition{
Type: appv1.ApplicationConditionDeletionError,
@@ -476,11 +511,54 @@ func (ctrl *ApplicationController) processAppOperationQueueItem() (processNext b
return
}
func shouldBeDeleted(app *appv1.Application, obj *unstructured.Unstructured) bool {
func (ctrl *ApplicationController) processAppComparisonTypeQueueItem() (processNext bool) {
key, shutdown := ctrl.appComparisonTypeRefreshQueue.Get()
processNext = true
defer func() {
if r := recover(); r != nil {
log.Errorf("Recovered from panic: %+v\n%s", r, debug.Stack())
}
ctrl.appComparisonTypeRefreshQueue.Done(key)
}()
if shutdown {
processNext = false
return
}
if parts := strings.Split(key.(string), "/"); len(parts) != 3 {
log.Warnf("Unexpected key format in appComparisonTypeRefreshTypeQueue. Key should consists of namespace/name/comparisonType but got: %s", key.(string))
} else {
if compareWith, err := strconv.Atoi(parts[2]); err != nil {
log.Warnf("Unable to parse comparison type: %v", err)
return
} else {
ctrl.requestAppRefresh(parts[1], CompareWith(compareWith).Pointer(), nil)
}
}
return
}
// shouldbeDeleted returns whether a given resource obj should be deleted on cascade delete of application app
func (ctrl *ApplicationController) shouldBeDeleted(app *appv1.Application, obj *unstructured.Unstructured) bool {
return !kube.IsCRD(obj) && !isSelfReferencedApp(app, kube.GetObjectRef(obj))
}
func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Application) error {
func (ctrl *ApplicationController) getPermittedAppLiveObjects(app *appv1.Application, proj *appv1.AppProject) (map[kube.ResourceKey]*unstructured.Unstructured, error) {
objsMap, err := ctrl.stateCache.GetManagedLiveObjs(app, []*unstructured.Unstructured{})
if err != nil {
return nil, err
}
// Don't delete live resources which are not permitted in the app project
for k, v := range objsMap {
if !proj.IsLiveResourcePermitted(v, app.Spec.Destination.Server) {
delete(objsMap, k)
}
}
return objsMap, nil
}
func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Application) ([]*unstructured.Unstructured, error) {
logCtx := log.WithField("application", app.Name)
logCtx.Infof("Deleting resources")
// Get refreshed application info, since informer app copy might be stale
@@ -489,23 +567,28 @@ func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Applic
if !apierr.IsNotFound(err) {
logCtx.Errorf("Unable to get refreshed application info prior deleting resources: %v", err)
}
return nil
return nil, nil
}
proj, err := ctrl.getAppProj(app)
if err != nil {
return nil, err
}
objsMap, err := ctrl.stateCache.GetManagedLiveObjs(app, []*unstructured.Unstructured{})
objsMap, err := ctrl.getPermittedAppLiveObjects(app, proj)
if err != nil {
return err
return nil, err
}
objs := make([]*unstructured.Unstructured, 0)
for k := range objsMap {
if objsMap[k].GetDeletionTimestamp() == nil && shouldBeDeleted(app, objsMap[k]) {
if ctrl.shouldBeDeleted(app, objsMap[k]) && objsMap[k].GetDeletionTimestamp() == nil {
objs = append(objs, objsMap[k])
}
}
cluster, err := ctrl.db.GetCluster(context.Background(), app.Spec.Destination.Server)
if err != nil {
return err
return nil, err
}
config := metrics.AddMetricsTransportWrapper(ctrl.metricsServer, app, cluster.RESTConfig())
@@ -514,29 +597,30 @@ func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Applic
return ctrl.kubectl.DeleteResource(config, obj.GroupVersionKind(), obj.GetName(), obj.GetNamespace(), false)
})
if err != nil {
return err
return objs, err
}
objsMap, err = ctrl.stateCache.GetManagedLiveObjs(app, []*unstructured.Unstructured{})
objsMap, err = ctrl.getPermittedAppLiveObjects(app, proj)
if err != nil {
return err
return nil, err
}
for k, obj := range objsMap {
if !shouldBeDeleted(app, obj) {
if !ctrl.shouldBeDeleted(app, obj) {
delete(objsMap, k)
}
}
if len(objsMap) > 0 {
logCtx.Infof("%d objects remaining for deletion", len(objsMap))
return nil
return objs, nil
}
err = ctrl.cache.SetAppManagedResources(app.Name, nil)
if err != nil {
return err
return objs, err
}
err = ctrl.cache.SetAppResourcesTree(app.Name, nil)
if err != nil {
return err
return objs, err
}
app.SetCascadedDeletion(false)
var patch []byte
@@ -547,26 +631,16 @@ func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Applic
})
_, err = ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace).Patch(app.Name, types.MergePatchType, patch)
if err != nil {
return err
return objs, err
}
logCtx.Info("Successfully deleted resources")
return nil
logCtx.Infof("Successfully deleted %d resources", len(objs))
return objs, nil
}
func (ctrl *ApplicationController) setAppCondition(app *appv1.Application, condition appv1.ApplicationCondition) {
index := -1
for i, exiting := range app.Status.Conditions {
if exiting.Type == condition.Type {
index = i
break
}
}
if index > -1 {
app.Status.Conditions[index] = condition
} else {
app.Status.Conditions = append(app.Status.Conditions, condition)
}
app.Status.SetConditions([]appv1.ApplicationCondition{condition}, map[appv1.ApplicationConditionType]bool{condition.Type: true})
var patch []byte
patch, err := json.Marshal(map[string]interface{}{
"status": map[string]interface{}{
@@ -641,10 +715,9 @@ func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Appli
if state.Phase.Completed() {
// if we just completed an operation, force a refresh so that UI will report up-to-date
// sync/health information
if key, err := cache.MetaNamespaceKeyFunc(app); err == nil {
if _, err := cache.MetaNamespaceKeyFunc(app); err == nil {
// force app refresh with using CompareWithLatest comparison type and trigger app reconciliation loop
ctrl.requestAppRefresh(app.Name, CompareWithLatest)
ctrl.appRefreshQueue.Add(key)
ctrl.requestAppRefresh(app.Name, CompareWithLatest.Pointer(), nil)
} else {
logCtx.Warnf("Fails to requeue application: %v", err)
}
@@ -770,7 +843,17 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
logCtx.Warnf("Failed to get cached managed resources for tree reconciliation, fallback to full reconciliation")
} else {
if tree, err := ctrl.getResourceTree(app, managedResources); err != nil {
app.Status.Conditions = []appv1.ApplicationCondition{{Type: appv1.ApplicationConditionComparisonError, Message: err.Error()}}
app.Status.SetConditions(
[]appv1.ApplicationCondition{
{
Type: appv1.ApplicationConditionComparisonError,
Message: err.Error(),
},
},
map[appv1.ApplicationConditionType]bool{
appv1.ApplicationConditionComparisonError: true,
},
)
} else {
app.Status.Summary = tree.GetSummary()
if err = ctrl.cache.SetAppResourcesTree(app.Name, tree); err != nil {
@@ -785,7 +868,7 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
}
}
hasErrors := ctrl.refreshAppConditions(app)
project, hasErrors := ctrl.refreshAppConditions(app)
if hasErrors {
app.Status.Sync.Status = appv1.SyncStatusCodeUnknown
app.Status.Health.Status = appv1.HealthStatusUnknown
@@ -803,7 +886,8 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
revision = app.Status.Sync.Revision
}
compareResult := ctrl.appStateManager.CompareAppState(app, revision, app.Spec.Source, refreshType == appv1.RefreshTypeHard, localManifests)
observedAt := metav1.Now()
compareResult := ctrl.appStateManager.CompareAppState(app, project, revision, app.Spec.Source, refreshType == appv1.RefreshTypeHard, localManifests)
ctrl.normalizeApplication(origApp, app)
@@ -814,24 +898,27 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
app.Status.Summary = tree.GetSummary()
}
project, err := ctrl.getAppProj(app)
if err != nil {
logCtx.Infof("Could not lookup project for %s in order to check schedules state", app.Name)
} else {
if project.Spec.SyncWindows.Matches(app).CanSync(false) {
syncErrCond := ctrl.autoSync(app, compareResult.syncStatus, compareResult.resources)
if syncErrCond != nil {
app.Status.SetConditions([]appv1.ApplicationCondition{*syncErrCond}, map[appv1.ApplicationConditionType]bool{appv1.ApplicationConditionSyncError: true})
} else {
app.Status.SetConditions([]appv1.ApplicationCondition{}, map[appv1.ApplicationConditionType]bool{appv1.ApplicationConditionSyncError: true})
}
if project.Spec.SyncWindows.Matches(app).CanSync(false) {
syncErrCond := ctrl.autoSync(app, compareResult.syncStatus, compareResult.resources)
if syncErrCond != nil {
app.Status.SetConditions(
[]appv1.ApplicationCondition{*syncErrCond},
map[appv1.ApplicationConditionType]bool{appv1.ApplicationConditionSyncError: true},
)
} else {
logCtx.Infof("Sync prevented by sync window")
app.Status.SetConditions(
[]appv1.ApplicationCondition{},
map[appv1.ApplicationConditionType]bool{appv1.ApplicationConditionSyncError: true},
)
}
} else {
logCtx.Infof("Sync prevented by sync window")
}
app.Status.ObservedAt = &compareResult.reconciledAt
app.Status.ReconciledAt = &compareResult.reconciledAt
if app.Status.ReconciledAt == nil || comparisonLevel == CompareWithLatest {
app.Status.ReconciledAt = &observedAt
}
app.Status.ObservedAt = &observedAt
app.Status.Sync = *compareResult.syncStatus
app.Status.Health = *compareResult.healthStatus
app.Status.Resources = compareResult.resources
@@ -850,23 +937,29 @@ func (ctrl *ApplicationController) needRefreshAppStatus(app *appv1.Application,
compareWith := CompareWithLatest
refreshType := appv1.RefreshTypeNormal
expired := app.Status.ReconciledAt == nil || app.Status.ReconciledAt.Add(statusRefreshTimeout).Before(time.Now().UTC())
if requestedType, ok := app.IsRefreshRequested(); ok || expired {
if ok {
refreshType = requestedType
reason = fmt.Sprintf("%s refresh requested", refreshType)
} else if expired {
reason = fmt.Sprintf("comparison expired. reconciledAt: %v, expiry: %v", app.Status.ReconciledAt, statusRefreshTimeout)
if requestedType, ok := app.IsRefreshRequested(); ok {
// user requested app refresh.
refreshType = requestedType
reason = fmt.Sprintf("%s refresh requested", refreshType)
} else if expired {
// The commented line below mysteriously crashes if app.Status.ReconciledAt is nil
// reason = fmt.Sprintf("comparison expired. reconciledAt: %v, expiry: %v", app.Status.ReconciledAt, statusRefreshTimeout)
//TODO: find existing Golang bug or create a new one
reconciledAtStr := "never"
if app.Status.ReconciledAt != nil {
reconciledAtStr = app.Status.ReconciledAt.String()
}
} else if requested, level := ctrl.isRefreshRequested(app.Name); requested {
compareWith = level
reason = fmt.Sprintf("controller refresh requested")
} else if app.Status.Sync.Status == appv1.SyncStatusCodeUnknown && expired {
reason = "comparison status unknown"
reason = fmt.Sprintf("comparison expired. reconciledAt: %v, expiry: %v", reconciledAtStr, statusRefreshTimeout)
} else if !app.Spec.Source.Equals(app.Status.Sync.ComparedTo.Source) {
reason = "spec.source differs"
} else if !app.Spec.Destination.Equals(app.Status.Sync.ComparedTo.Destination) {
reason = "spec.destination differs"
} else if requested, level := ctrl.isRefreshRequested(app.Name); requested {
compareWith = level
reason = fmt.Sprintf("controller refresh requested")
}
if reason != "" {
logCtx.Infof("Refreshing app status (%s), level (%d)", reason, compareWith)
return true, refreshType, compareWith
@@ -874,7 +967,7 @@ func (ctrl *ApplicationController) needRefreshAppStatus(app *appv1.Application,
return false, refreshType, compareWith
}
func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application) bool {
func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application) (*appv1.AppProject, bool) {
errorConditions := make([]appv1.ApplicationCondition, 0)
proj, err := ctrl.getAppProj(app)
if err != nil {
@@ -904,7 +997,7 @@ func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application)
appv1.ApplicationConditionInvalidSpecError: true,
appv1.ApplicationConditionUnknownError: true,
})
return len(errorConditions) > 0
return proj, len(errorConditions) > 0
}
// normalizeApplication normalizes an application.spec and additionally persists updates if it changed
@@ -1021,12 +1114,7 @@ func (ctrl *ApplicationController) autoSync(app *appv1.Application, syncStatus *
}
} else {
logCtx.Infof("Skipping auto-sync: already attempted sync to %s with timeout %v (retrying in %v)", desiredCommitSHA, ctrl.selfHealTimeout, retryAfter)
if key, err := cache.MetaNamespaceKeyFunc(app); err == nil {
ctrl.requestAppRefresh(app.Name, CompareWithLatest)
ctrl.appRefreshQueue.AddAfter(key, retryAfter)
} else {
logCtx.Warnf("Fails to requeue application: %v", err)
}
ctrl.requestAppRefresh(app.Name, CompareWithLatest.Pointer(), &retryAfter)
return nil
}
@@ -1099,15 +1187,14 @@ func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.Shar
if err != nil {
return
}
var compareWith *CompareWith
oldApp, oldOK := old.(*appv1.Application)
newApp, newOK := new.(*appv1.Application)
if oldOK && newOK {
if toggledAutomatedSync(oldApp, newApp) {
log.WithField("application", newApp.Name).Info("Enabled automated sync")
ctrl.requestAppRefresh(newApp.Name, CompareWithLatest)
}
if oldOK && newOK && automatedSyncEnabled(oldApp, newApp) {
log.WithField("application", newApp.Name).Info("Enabled automated sync")
compareWith = CompareWithLatest.Pointer()
}
ctrl.appRefreshQueue.Add(key)
ctrl.requestAppRefresh(newApp.Name, compareWith, nil)
ctrl.appOperationQueue.Add(key)
},
DeleteFunc: func(obj interface{}) {
@@ -1144,14 +1231,26 @@ func isOperationInProgress(app *appv1.Application) bool {
return app.Status.OperationState != nil && !app.Status.OperationState.Phase.Completed()
}
// toggledAutomatedSync tests if an app went from auto-sync disabled to enabled.
// automatedSyncEnabled tests if an app went from auto-sync disabled to enabled.
// if it was toggled to be enabled, the informer handler will force a refresh
func toggledAutomatedSync(old *appv1.Application, new *appv1.Application) bool {
if new.Spec.SyncPolicy == nil || new.Spec.SyncPolicy.Automated == nil {
return false
func automatedSyncEnabled(oldApp *appv1.Application, newApp *appv1.Application) bool {
oldEnabled := false
oldSelfHealEnabled := false
if oldApp.Spec.SyncPolicy != nil && oldApp.Spec.SyncPolicy.Automated != nil {
oldEnabled = true
oldSelfHealEnabled = oldApp.Spec.SyncPolicy.Automated.SelfHeal
}
// auto-sync is enabled. check if it was previously disabled
if old.Spec.SyncPolicy == nil || old.Spec.SyncPolicy.Automated == nil {
newEnabled := false
newSelfHealEnabled := false
if newApp.Spec.SyncPolicy != nil && newApp.Spec.SyncPolicy.Automated != nil {
newEnabled = true
newSelfHealEnabled = newApp.Spec.SyncPolicy.Automated.SelfHeal
}
if !oldEnabled && newEnabled {
return true
}
if !oldSelfHealEnabled && newSelfHealEnabled {
return true
}
// nothing changed

View File

@@ -2,6 +2,7 @@ package controller
import (
"context"
"encoding/json"
"testing"
"time"
@@ -26,7 +27,8 @@ import (
mockrepoclient "github.com/argoproj/argo-cd/reposerver/apiclient/mocks"
mockreposerver "github.com/argoproj/argo-cd/reposerver/mocks"
"github.com/argoproj/argo-cd/test"
utilcache "github.com/argoproj/argo-cd/util/cache"
cacheutil "github.com/argoproj/argo-cd/util/cache"
appstatecache "github.com/argoproj/argo-cd/util/cache/appstate"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/kube/kubetest"
"github.com/argoproj/argo-cd/util/settings"
@@ -87,7 +89,10 @@ func newFakeController(data *fakeData) *ApplicationController {
kubeClient,
appclientset.NewSimpleClientset(data.apps...),
&mockRepoClientset,
utilcache.NewCache(utilcache.NewInMemoryCache(1*time.Hour)),
appstatecache.NewCache(
cacheutil.NewCache(cacheutil.NewInMemoryCache(1*time.Minute)),
1*time.Minute,
),
kubectl,
time.Minute,
time.Minute,
@@ -106,6 +111,7 @@ func newFakeController(data *fakeData) *ApplicationController {
ctrl.stateCache = &mockStateCache
mockStateCache.On("IsNamespaced", mock.Anything, mock.Anything).Return(true, nil)
mockStateCache.On("GetManagedLiveObjs", mock.Anything, mock.Anything).Return(data.managedLiveObjs, nil)
mockStateCache.On("GetServerVersion", mock.Anything).Return("v1.2.3", nil)
response := make(map[kube.ResourceKey]argoappv1.ResourceNode)
for k, v := range data.namespacedResources {
response[k] = v.ResourceNode
@@ -186,6 +192,17 @@ status:
repoURL: https://github.com/argoproj/argocd-example-apps.git
`
var fakeStrayResource = `
apiVersion: v1
kind: ConfigMap
metadata:
name: test-cm
namespace: invalid
labels:
app.kubernetes.io/instance: my-app
data:
`
func newFakeApp() *argoappv1.Application {
var app argoappv1.Application
err := yaml.Unmarshal([]byte(fakeApp), &app)
@@ -195,6 +212,15 @@ func newFakeApp() *argoappv1.Application {
return &app
}
func newFakeCM() map[string]interface{} {
var cm map[string]interface{}
err := yaml.Unmarshal([]byte(fakeStrayResource), &cm)
if err != nil {
panic(err)
}
return cm
}
func TestAutoSync(t *testing.T) {
app := newFakeApp()
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app}})
@@ -383,27 +409,118 @@ func TestAutoSyncParameterOverrides(t *testing.T) {
// TestFinalizeAppDeletion verifies application deletion
func TestFinalizeAppDeletion(t *testing.T) {
app := newFakeApp()
app.Spec.Destination.Namespace = test.FakeArgoCDNamespace
appObj := kube.MustToUnstructured(&app)
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app}, managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(appObj): appObj,
}})
// Ensure app can be deleted cascading
{
defaultProj := argoappv1.AppProject{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: test.FakeArgoCDNamespace,
},
Spec: argoappv1.AppProjectSpec{
SourceRepos: []string{"*"},
Destinations: []argoappv1.ApplicationDestination{
{
Server: "*",
Namespace: "*",
},
},
},
}
app := newFakeApp()
app.Spec.Destination.Namespace = test.FakeArgoCDNamespace
appObj := kube.MustToUnstructured(&app)
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app, &defaultProj}, managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(appObj): appObj,
}})
patched := false
fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset)
defaultReactor := fakeAppCs.ReactionChain[0]
fakeAppCs.ReactionChain = nil
fakeAppCs.AddReactor("get", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
return defaultReactor.React(action)
})
fakeAppCs.AddReactor("patch", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
patched = true
return true, nil, nil
})
err := ctrl.finalizeApplicationDeletion(app)
assert.NoError(t, err)
assert.True(t, patched)
patched := false
fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset)
defaultReactor := fakeAppCs.ReactionChain[0]
fakeAppCs.ReactionChain = nil
fakeAppCs.AddReactor("get", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
return defaultReactor.React(action)
})
fakeAppCs.AddReactor("patch", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
patched = true
return true, nil, nil
})
_, err := ctrl.finalizeApplicationDeletion(app)
assert.NoError(t, err)
assert.True(t, patched)
}
// Ensure any stray resources irregulary labeled with instance label of app are not deleted upon deleting,
// when app project restriction is in place
{
defaultProj := argoappv1.AppProject{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: test.FakeArgoCDNamespace,
},
Spec: argoappv1.AppProjectSpec{
SourceRepos: []string{"*"},
Destinations: []argoappv1.ApplicationDestination{
{
Server: "*",
Namespace: "*",
},
},
},
}
restrictedProj := argoappv1.AppProject{
ObjectMeta: metav1.ObjectMeta{
Name: "restricted",
Namespace: test.FakeArgoCDNamespace,
},
Spec: argoappv1.AppProjectSpec{
SourceRepos: []string{"*"},
Destinations: []argoappv1.ApplicationDestination{
{
Server: "*",
Namespace: "my-app",
},
},
},
}
app := newFakeApp()
app.Spec.Destination.Namespace = test.FakeArgoCDNamespace
app.Spec.Project = "restricted"
appObj := kube.MustToUnstructured(&app)
cm := newFakeCM()
strayObj := kube.MustToUnstructured(&cm)
ctrl := newFakeController(&fakeData{
apps: []runtime.Object{app, &defaultProj, &restrictedProj},
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(appObj): appObj,
kube.GetResourceKey(strayObj): strayObj,
},
})
patched := false
fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset)
defaultReactor := fakeAppCs.ReactionChain[0]
fakeAppCs.ReactionChain = nil
fakeAppCs.AddReactor("get", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
return defaultReactor.React(action)
})
fakeAppCs.AddReactor("patch", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
patched = true
return true, nil, nil
})
objs, err := ctrl.finalizeApplicationDeletion(app)
assert.NoError(t, err)
assert.True(t, patched)
objsMap, err := ctrl.stateCache.GetManagedLiveObjs(app, []*unstructured.Unstructured{})
if err != nil {
assert.NoError(t, err)
}
// Managed objects must be empty
assert.Empty(t, objsMap)
// Loop through all deleted objects, ensure that test-cm is none of them
for _, o := range objs {
assert.NotEqual(t, "test-cm", o.GetName())
}
}
}
// TestNormalizeApplication verifies we normalize an application during reconciliation
@@ -556,8 +673,8 @@ func TestNeedRefreshAppStatus(t *testing.T) {
assert.False(t, needRefresh)
// refresh app using the 'deepest' requested comparison level
ctrl.requestAppRefresh(app.Name, CompareWithRecent)
ctrl.requestAppRefresh(app.Name, ComparisonWithNothing)
ctrl.requestAppRefresh(app.Name, CompareWithRecent.Pointer(), nil)
ctrl.requestAppRefresh(app.Name, ComparisonWithNothing.Pointer(), nil)
needRefresh, refreshType, compareWith := ctrl.needRefreshAppStatus(app, 1*time.Hour)
assert.True(t, needRefresh)
@@ -575,7 +692,7 @@ func TestNeedRefreshAppStatus(t *testing.T) {
{
// refresh app using the 'latest' level if comparison expired
app := app.DeepCopy()
ctrl.requestAppRefresh(app.Name, CompareWithRecent)
ctrl.requestAppRefresh(app.Name, CompareWithRecent.Pointer(), nil)
reconciledAt := metav1.NewTime(time.Now().UTC().Add(-1 * time.Hour))
app.Status.ReconciledAt = &reconciledAt
needRefresh, refreshType, compareWith = ctrl.needRefreshAppStatus(app, 1*time.Minute)
@@ -597,6 +714,24 @@ func TestNeedRefreshAppStatus(t *testing.T) {
assert.Equal(t, argoappv1.RefreshTypeHard, refreshType)
assert.Equal(t, CompareWithLatest, compareWith)
}
{
app := app.DeepCopy()
// ensure that CompareWithLatest level is used if application source has changed
ctrl.requestAppRefresh(app.Name, ComparisonWithNothing.Pointer(), nil)
// sample app source change
app.Spec.Source.Helm = &argoappv1.ApplicationSourceHelm{
Parameters: []argoappv1.HelmParameter{{
Name: "foo",
Value: "bar",
}},
}
needRefresh, refreshType, compareWith = ctrl.needRefreshAppStatus(app, 1*time.Hour)
assert.True(t, needRefresh)
assert.Equal(t, argoappv1.RefreshTypeNormal, refreshType)
assert.Equal(t, CompareWithLatest, compareWith)
}
}
func TestRefreshAppConditions(t *testing.T) {
@@ -620,7 +755,7 @@ func TestRefreshAppConditions(t *testing.T) {
app := newFakeApp()
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app, &defaultProj}})
hasErrors := ctrl.refreshAppConditions(app)
_, hasErrors := ctrl.refreshAppConditions(app)
assert.False(t, hasErrors)
assert.Len(t, app.Status.Conditions, 0)
})
@@ -631,7 +766,7 @@ func TestRefreshAppConditions(t *testing.T) {
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app, &defaultProj}})
hasErrors := ctrl.refreshAppConditions(app)
_, hasErrors := ctrl.refreshAppConditions(app)
assert.False(t, hasErrors)
assert.Len(t, app.Status.Conditions, 1)
assert.Equal(t, argoappv1.ApplicationConditionExcludedResourceWarning, app.Status.Conditions[0].Type)
@@ -644,10 +779,70 @@ func TestRefreshAppConditions(t *testing.T) {
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app, &defaultProj}})
hasErrors := ctrl.refreshAppConditions(app)
_, hasErrors := ctrl.refreshAppConditions(app)
assert.True(t, hasErrors)
assert.Len(t, app.Status.Conditions, 1)
assert.Equal(t, argoappv1.ApplicationConditionInvalidSpecError, app.Status.Conditions[0].Type)
assert.Equal(t, "Application referencing project wrong project which does not exist", app.Status.Conditions[0].Message)
})
}
func TestUpdateReconciledAt(t *testing.T) {
app := newFakeApp()
reconciledAt := metav1.NewTime(time.Now().Add(-1 * time.Second))
app.Status = argoappv1.ApplicationStatus{ReconciledAt: &reconciledAt}
app.Status.Sync = argoappv1.SyncStatus{ComparedTo: argoappv1.ComparedTo{Source: app.Spec.Source, Destination: app.Spec.Destination}}
ctrl := newFakeController(&fakeData{
apps: []runtime.Object{app, &defaultProj},
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
})
key, _ := cache.MetaNamespaceKeyFunc(app)
fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset)
fakeAppCs.ReactionChain = nil
receivedPatch := map[string]interface{}{}
fakeAppCs.AddReactor("patch", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
if patchAction, ok := action.(kubetesting.PatchAction); ok {
assert.NoError(t, json.Unmarshal(patchAction.GetPatch(), &receivedPatch))
}
return true, nil, nil
})
t.Run("UpdatedOnFullReconciliation", func(t *testing.T) {
receivedPatch = map[string]interface{}{}
ctrl.requestAppRefresh(app.Name, CompareWithLatest.Pointer(), nil)
ctrl.appRefreshQueue.Add(key)
ctrl.processAppRefreshQueueItem()
_, updated, err := unstructured.NestedString(receivedPatch, "status", "reconciledAt")
assert.NoError(t, err)
assert.True(t, updated)
_, updated, err = unstructured.NestedString(receivedPatch, "status", "observedAt")
assert.NoError(t, err)
assert.True(t, updated)
})
t.Run("NotUpdatedOnPartialReconciliation", func(t *testing.T) {
receivedPatch = map[string]interface{}{}
ctrl.appRefreshQueue.Add(key)
ctrl.requestAppRefresh(app.Name, CompareWithRecent.Pointer(), nil)
ctrl.processAppRefreshQueueItem()
_, updated, err := unstructured.NestedString(receivedPatch, "status", "reconciledAt")
assert.NoError(t, err)
assert.False(t, updated)
_, updated, err = unstructured.NestedString(receivedPatch, "status", "observedAt")
assert.NoError(t, err)
assert.True(t, updated)
})
}

View File

@@ -27,6 +27,9 @@ type cacheSettings struct {
}
type LiveStateCache interface {
// Returns k8s server version
GetServerVersion(serverURL string) (string, error)
// Returns true of given group kind is a namespaced resource
IsNamespaced(server string, gk schema.GroupKind) (bool, error)
// Executes give callback against resource specified by the key and all its children
IterateHierarchy(server string, key kube.ResourceKey, action func(child appv1.ResourceNode, appName string)) error
@@ -38,6 +41,8 @@ type LiveStateCache interface {
Run(ctx context.Context) error
// Invalidate invalidates the entire cluster state cache
Invalidate()
// Returns information about monitored clusters
GetClustersInfo() []metrics.ClusterInfo
}
type ObjectUpdatedHandler = func(managedByApp map[string]bool, ref v1.ObjectReference)
@@ -121,9 +126,11 @@ func (c *liveStateCache) getCluster(server string) (*clusterInfo, error) {
kubectl: c.kubectl,
cluster: cluster,
syncTime: nil,
syncLock: &sync.Mutex{},
log: log.WithField("server", cluster.Server),
cacheSettingsSrc: c.getCacheSettings,
onEventReceived: func(event watch.EventType, un *unstructured.Unstructured) {
c.metricsServer.IncClusterEventsCount(cluster.Server)
},
}
c.clusters[cluster.Server] = info
@@ -148,9 +155,7 @@ func (c *liveStateCache) Invalidate() {
c.lock.Lock()
defer c.lock.Unlock()
for _, clust := range c.clusters {
clust.lock.Lock()
clust.invalidate()
clust.lock.Unlock()
}
log.Info("live state cache invalidated")
}
@@ -187,6 +192,13 @@ func (c *liveStateCache) GetManagedLiveObjs(a *appv1.Application, targetObjs []*
}
return clusterInfo.getManagedLiveObjs(a, targetObjs, c.metricsServer)
}
func (c *liveStateCache) GetServerVersion(serverURL string) (string, error) {
clusterInfo, err := c.getSyncedCluster(serverURL)
if err != nil {
return "", err
}
return clusterInfo.serverVersion, nil
}
func isClusterHasApps(apps []interface{}, cluster *appv1.Cluster) bool {
for _, obj := range apps {
@@ -273,3 +285,13 @@ func (c *liveStateCache) Run(ctx context.Context) error {
<-ctx.Done()
return nil
}
func (c *liveStateCache) GetClustersInfo() []metrics.ClusterInfo {
c.lock.Lock()
defer c.lock.Unlock()
res := make([]metrics.ClusterInfo, 0)
for _, info := range c.clusters {
res = append(res, info.getClusterInfo())
}
return res
}

26
controller/cache/cache_test.go vendored Normal file
View File

@@ -0,0 +1,26 @@
package cache
import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestGetServerVersion(t *testing.T) {
now := time.Now()
cache := &liveStateCache{
lock: &sync.Mutex{},
clusters: map[string]*clusterInfo{
"http://localhost": {
syncTime: &now,
lock: &sync.Mutex{},
serverVersion: "123",
},
}}
version, err := cache.GetServerVersion("http://localhost")
assert.NoError(t, err)
assert.Equal(t, "123", version)
}

View File

@@ -9,6 +9,8 @@ import (
"sync"
"time"
"k8s.io/client-go/dynamic"
"k8s.io/apimachinery/pkg/types"
"github.com/argoproj/argo-cd/controller/metrics"
@@ -39,32 +41,32 @@ type apiMeta struct {
}
type clusterInfo struct {
syncLock *sync.Mutex
syncTime *time.Time
syncError error
apisMeta map[schema.GroupKind]*apiMeta
syncTime *time.Time
syncError error
apisMeta map[schema.GroupKind]*apiMeta
serverVersion string
lock *sync.Mutex
nodes map[kube.ResourceKey]*node
nsIndex map[string]map[kube.ResourceKey]*node
onObjectUpdated ObjectUpdatedHandler
onEventReceived func(event watch.EventType, un *unstructured.Unstructured)
kubectl kube.Kubectl
cluster *appv1.Cluster
log *log.Entry
cacheSettingsSrc func() *cacheSettings
}
func (c *clusterInfo) replaceResourceCache(gk schema.GroupKind, resourceVersion string, objs []unstructured.Unstructured) {
c.lock.Lock()
defer c.lock.Unlock()
func (c *clusterInfo) replaceResourceCache(gk schema.GroupKind, resourceVersion string, objs []unstructured.Unstructured, ns string) {
info, ok := c.apisMeta[gk]
if ok {
objByKind := make(map[kube.ResourceKey]*unstructured.Unstructured)
objByKey := make(map[kube.ResourceKey]*unstructured.Unstructured)
for i := range objs {
objByKind[kube.GetResourceKey(&objs[i])] = &objs[i]
objByKey[kube.GetResourceKey(&objs[i])] = &objs[i]
}
// update existing nodes
for i := range objs {
obj := &objs[i]
key := kube.GetResourceKey(&objs[i])
@@ -72,12 +74,13 @@ func (c *clusterInfo) replaceResourceCache(gk schema.GroupKind, resourceVersion
c.onNodeUpdated(exists, existingNode, obj, key)
}
// remove existing nodes that a no longer exist
for key, existingNode := range c.nodes {
if key.Kind != gk.Kind || key.Group != gk.Group {
if key.Kind != gk.Kind || key.Group != gk.Group || ns != "" && key.Namespace != ns {
continue
}
if _, ok := objByKind[key]; !ok {
if _, ok := objByKey[key]; !ok {
c.onNodeRemoved(key, existingNode)
}
}
@@ -166,8 +169,8 @@ func (c *clusterInfo) removeNode(key kube.ResourceKey) {
}
func (c *clusterInfo) invalidate() {
c.syncLock.Lock()
defer c.syncLock.Unlock()
c.lock.Lock()
defer c.lock.Unlock()
c.syncTime = nil
for i := range c.apisMeta {
c.apisMeta[i].watchCancel()
@@ -185,21 +188,26 @@ func (c *clusterInfo) synced() bool {
return time.Now().Before(c.syncTime.Add(clusterSyncTimeout))
}
func (c *clusterInfo) stopWatching(gk schema.GroupKind) {
c.syncLock.Lock()
defer c.syncLock.Unlock()
func (c *clusterInfo) stopWatching(gk schema.GroupKind, ns string) {
c.lock.Lock()
defer c.lock.Unlock()
if info, ok := c.apisMeta[gk]; ok {
info.watchCancel()
delete(c.apisMeta, gk)
c.replaceResourceCache(gk, "", []unstructured.Unstructured{})
c.replaceResourceCache(gk, "", []unstructured.Unstructured{}, ns)
log.Warnf("Stop watching %s not found on %s.", gk, c.cluster.Server)
}
}
// startMissingWatches lists supported cluster resources and start watching for changes unless watch is already running
func (c *clusterInfo) startMissingWatches() error {
config := c.cluster.RESTConfig()
apis, err := c.kubectl.GetAPIResources(c.cluster.RESTConfig(), c.cacheSettingsSrc().ResourcesFilter)
apis, err := c.kubectl.GetAPIResources(config, c.cacheSettingsSrc().ResourcesFilter)
if err != nil {
return err
}
client, err := c.kubectl.NewDynamicClient(config)
if err != nil {
return err
}
@@ -210,7 +218,14 @@ func (c *clusterInfo) startMissingWatches() error {
ctx, cancel := context.WithCancel(context.Background())
info := &apiMeta{namespaced: api.Meta.Namespaced, watchCancel: cancel}
c.apisMeta[api.GroupKind] = info
go c.watchEvents(ctx, api, info)
err = c.processApi(client, api, func(resClient dynamic.ResourceInterface, ns string) error {
go c.watchEvents(ctx, api, info, resClient, ns)
return nil
})
if err != nil {
return err
}
}
}
return nil
@@ -222,7 +237,7 @@ func runSynced(lock *sync.Mutex, action func() error) error {
return action()
}
func (c *clusterInfo) watchEvents(ctx context.Context, api kube.APIResourceInfo, info *apiMeta) {
func (c *clusterInfo) watchEvents(ctx context.Context, api kube.APIResourceInfo, info *apiMeta, resClient dynamic.ResourceInterface, ns string) {
util.RetryUntilSucceed(func() (err error) {
defer func() {
if r := recover(); r != nil {
@@ -230,13 +245,13 @@ func (c *clusterInfo) watchEvents(ctx context.Context, api kube.APIResourceInfo,
}
}()
err = runSynced(c.syncLock, func() error {
err = runSynced(c.lock, func() error {
if info.resourceVersion == "" {
list, err := api.Interface.List(metav1.ListOptions{})
list, err := resClient.List(metav1.ListOptions{})
if err != nil {
return err
}
c.replaceResourceCache(api.GroupKind, list.GetResourceVersion(), list.Items)
c.replaceResourceCache(api.GroupKind, list.GetResourceVersion(), list.Items, ns)
}
return nil
})
@@ -245,13 +260,13 @@ func (c *clusterInfo) watchEvents(ctx context.Context, api kube.APIResourceInfo,
return err
}
w, err := api.Interface.Watch(metav1.ListOptions{ResourceVersion: info.resourceVersion})
w, err := resClient.Watch(metav1.ListOptions{ResourceVersion: info.resourceVersion})
if errors.IsNotFound(err) {
c.stopWatching(api.GroupKind)
c.stopWatching(api.GroupKind, ns)
return nil
}
err = runSynced(c.syncLock, func() error {
err = runSynced(c.lock, func() error {
if errors.IsGone(err) {
info.resourceVersion = ""
log.Warnf("Resource version of %s on %s is too old.", api.GroupKind, c.cluster.Server)
@@ -279,10 +294,10 @@ func (c *clusterInfo) watchEvents(ctx context.Context, api kube.APIResourceInfo,
if groupOk && groupErr == nil && kindOk && kindErr == nil {
gk := schema.GroupKind{Group: group, Kind: kind}
c.stopWatching(gk)
c.stopWatching(gk, ns)
}
} else {
err = runSynced(c.syncLock, func() error {
err = runSynced(c.lock, func() error {
return c.startMissingWatches()
})
@@ -300,6 +315,25 @@ func (c *clusterInfo) watchEvents(ctx context.Context, api kube.APIResourceInfo,
}, fmt.Sprintf("watch %s on %s", api.GroupKind, c.cluster.Server), ctx, watchResourcesRetryTimeout)
}
func (c *clusterInfo) processApi(client dynamic.Interface, api kube.APIResourceInfo, callback func(resClient dynamic.ResourceInterface, ns string) error) error {
resClient := client.Resource(api.GroupVersionResource)
if len(c.cluster.Namespaces) == 0 {
return callback(resClient, "")
}
if !api.Meta.Namespaced {
return nil
}
for _, ns := range c.cluster.Namespaces {
err := callback(resClient.Namespace(ns), ns)
if err != nil {
return err
}
}
return nil
}
func (c *clusterInfo) sync() (err error) {
c.log.Info("Start syncing cluster")
@@ -309,25 +343,35 @@ func (c *clusterInfo) sync() (err error) {
}
c.apisMeta = make(map[schema.GroupKind]*apiMeta)
c.nodes = make(map[kube.ResourceKey]*node)
apis, err := c.kubectl.GetAPIResources(c.cluster.RESTConfig(), c.cacheSettingsSrc().ResourcesFilter)
config := c.cluster.RESTConfig()
version, err := c.kubectl.GetServerVersion(config)
if err != nil {
return err
}
c.serverVersion = version
apis, err := c.kubectl.GetAPIResources(config, c.cacheSettingsSrc().ResourcesFilter)
if err != nil {
return err
}
client, err := c.kubectl.NewDynamicClient(config)
if err != nil {
return err
}
lock := sync.Mutex{}
err = util.RunAllAsync(len(apis), func(i int) error {
api := apis[i]
list, err := api.Interface.List(metav1.ListOptions{})
if err != nil {
return err
}
return c.processApi(client, apis[i], func(resClient dynamic.ResourceInterface, _ string) error {
list, err := resClient.List(metav1.ListOptions{})
if err != nil {
return err
}
lock.Lock()
for i := range list.Items {
c.setNode(c.createObjInfo(&list.Items[i], c.cacheSettingsSrc().AppInstanceLabelKey))
}
lock.Unlock()
return nil
lock.Lock()
for i := range list.Items {
c.setNode(c.createObjInfo(&list.Items[i], c.cacheSettingsSrc().AppInstanceLabelKey))
}
lock.Unlock()
return nil
})
})
if err == nil {
@@ -344,8 +388,8 @@ func (c *clusterInfo) sync() (err error) {
}
func (c *clusterInfo) ensureSynced() error {
c.syncLock.Lock()
defer c.syncLock.Unlock()
c.lock.Lock()
defer c.lock.Unlock()
if c.synced() {
return c.syncError
}
@@ -483,6 +527,9 @@ func (c *clusterInfo) getManagedLiveObjs(a *appv1.Application, targetObjs []*uns
}
func (c *clusterInfo) processEvent(event watch.EventType, un *unstructured.Unstructured) {
if c.onEventReceived != nil {
c.onEventReceived(event, un)
}
c.lock.Lock()
defer c.lock.Unlock()
key := kube.GetResourceKey(un)
@@ -538,6 +585,18 @@ var (
}
)
func (c *clusterInfo) getClusterInfo() metrics.ClusterInfo {
c.lock.Lock()
defer c.lock.Unlock()
return metrics.ClusterInfo{
APIsCount: len(c.apisMeta),
K8SVersion: c.serverVersion,
ResourcesCount: len(c.nodes),
Server: c.cluster.Server,
LastCacheSyncTime: c.syncTime,
}
}
// skipAppRequeing checks if the object is an API type which we want to skip requeuing against.
// We ignore API types which have a high churn rate, and/or whose updates are irrelevant to the app
func skipAppRequeing(key kube.ResourceKey) bool {

View File

@@ -135,20 +135,20 @@ func newCluster(objs ...*unstructured.Unstructured) *clusterInfo {
client := fake.NewSimpleDynamicClient(scheme, runtimeObjs...)
apiResources := []kube.APIResourceInfo{{
GroupKind: schema.GroupKind{Group: "", Kind: "Pod"},
Interface: client.Resource(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}),
Meta: metav1.APIResource{Namespaced: true},
GroupKind: schema.GroupKind{Group: "", Kind: "Pod"},
GroupVersionResource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"},
Meta: metav1.APIResource{Namespaced: true},
}, {
GroupKind: schema.GroupKind{Group: "apps", Kind: "ReplicaSet"},
Interface: client.Resource(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "replicasets"}),
Meta: metav1.APIResource{Namespaced: true},
GroupKind: schema.GroupKind{Group: "apps", Kind: "ReplicaSet"},
GroupVersionResource: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "replicasets"},
Meta: metav1.APIResource{Namespaced: true},
}, {
GroupKind: schema.GroupKind{Group: "apps", Kind: "Deployment"},
Interface: client.Resource(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}),
Meta: metav1.APIResource{Namespaced: true},
GroupKind: schema.GroupKind{Group: "apps", Kind: "Deployment"},
GroupVersionResource: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
Meta: metav1.APIResource{Namespaced: true},
}}
return newClusterExt(&kubetest.MockKubectlCmd{APIResources: apiResources})
return newClusterExt(&kubetest.MockKubectlCmd{APIResources: apiResources, DynamicClient: client})
}
func newClusterExt(kubectl kube.Kubectl) *clusterInfo {
@@ -160,7 +160,6 @@ func newClusterExt(kubectl kube.Kubectl) *clusterInfo {
nsIndex: make(map[string]map[kube.ResourceKey]*node),
cluster: &appv1.Cluster{},
syncTime: nil,
syncLock: &sync.Mutex{},
apisMeta: make(map[schema.GroupKind]*apiMeta),
log: log.WithField("cluster", "test"),
cacheSettingsSrc: func() *cacheSettings {
@@ -177,6 +176,55 @@ func getChildren(cluster *clusterInfo, un *unstructured.Unstructured) []appv1.Re
return hierarchy[1:]
}
func TestEnsureSynced(t *testing.T) {
obj1 := strToUnstructured(`
apiVersion: apps/v1
kind: Deployment
metadata: {"name": "helm-guestbook1", "namespace": "default1"}
`)
obj2 := strToUnstructured(`
apiVersion: apps/v1
kind: Deployment
metadata: {"name": "helm-guestbook2", "namespace": "default2"}
`)
cluster := newCluster(obj1, obj2)
err := cluster.ensureSynced()
assert.Nil(t, err)
assert.Len(t, cluster.nodes, 2)
var names []string
for k := range cluster.nodes {
names = append(names, k.Name)
}
assert.ElementsMatch(t, []string{"helm-guestbook1", "helm-guestbook2"}, names)
}
func TestEnsureSyncedSingleNamespace(t *testing.T) {
obj1 := strToUnstructured(`
apiVersion: apps/v1
kind: Deployment
metadata: {"name": "helm-guestbook1", "namespace": "default1"}
`)
obj2 := strToUnstructured(`
apiVersion: apps/v1
kind: Deployment
metadata: {"name": "helm-guestbook2", "namespace": "default2"}
`)
cluster := newCluster(obj1, obj2)
cluster.cluster.Namespaces = []string{"default1"}
err := cluster.ensureSynced()
assert.Nil(t, err)
assert.Len(t, cluster.nodes, 1)
var names []string
for k := range cluster.nodes {
names = append(names, k.Name)
}
assert.ElementsMatch(t, []string{"helm-guestbook1"}, names)
}
func TestGetNamespaceResources(t *testing.T) {
defaultNamespaceTopLevel1 := strToUnstructured(`
apiVersion: apps/v1
@@ -456,7 +504,7 @@ func TestWatchCacheUpdated(t *testing.T) {
podGroupKind := testPod.GroupVersionKind().GroupKind()
cluster.replaceResourceCache(podGroupKind, "updated-list-version", []unstructured.Unstructured{*updated, *added})
cluster.replaceResourceCache(podGroupKind, "updated-list-version", []unstructured.Unstructured{*updated, *added}, "")
_, ok := cluster.nodes[kube.GetResourceKey(removed)]
assert.False(t, ok)
@@ -469,6 +517,28 @@ func TestWatchCacheUpdated(t *testing.T) {
assert.True(t, ok)
}
func TestNamespaceModeReplace(t *testing.T) {
ns1Pod := testPod.DeepCopy()
ns1Pod.SetNamespace("ns1")
ns1Pod.SetName("pod1")
ns2Pod := testPod.DeepCopy()
ns2Pod.SetNamespace("ns2")
podGroupKind := testPod.GroupVersionKind().GroupKind()
cluster := newCluster(ns1Pod, ns2Pod)
err := cluster.ensureSynced()
assert.Nil(t, err)
cluster.replaceResourceCache(podGroupKind, "", nil, "ns1")
_, ok := cluster.nodes[kube.GetResourceKey(ns1Pod)]
assert.False(t, ok)
_, ok = cluster.nodes[kube.GetResourceKey(ns2Pod)]
assert.True(t, ok)
}
func TestGetDuplicatedChildren(t *testing.T) {
extensionsRS := testRS.DeepCopy()
extensionsRS.SetGroupVersionKind(schema.GroupVersionKind{Group: "extensions", Kind: kube.ReplicaSetKind, Version: "v1beta1"})

View File

@@ -5,10 +5,11 @@ package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
metrics "github.com/argoproj/argo-cd/controller/metrics"
kube "github.com/argoproj/argo-cd/util/kube"
mock "github.com/stretchr/testify/mock"
schema "k8s.io/apimachinery/pkg/runtime/schema"
unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -21,6 +22,22 @@ type LiveStateCache struct {
mock.Mock
}
// GetClustersInfo provides a mock function with given fields:
func (_m *LiveStateCache) GetClustersInfo() []metrics.ClusterInfo {
ret := _m.Called()
var r0 []metrics.ClusterInfo
if rf, ok := ret.Get(0).(func() []metrics.ClusterInfo); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]metrics.ClusterInfo)
}
}
return r0
}
// GetManagedLiveObjs provides a mock function with given fields: a, targetObjs
func (_m *LiveStateCache) GetManagedLiveObjs(a *v1alpha1.Application, targetObjs []*unstructured.Unstructured) (map[kube.ResourceKey]*unstructured.Unstructured, error) {
ret := _m.Called(a, targetObjs)
@@ -67,6 +84,27 @@ func (_m *LiveStateCache) GetNamespaceTopLevelResources(server string, namespace
return r0, r1
}
// GetServerVersion provides a mock function with given fields: serverURL
func (_m *LiveStateCache) GetServerVersion(serverURL string) (string, error) {
ret := _m.Called(serverURL)
var r0 string
if rf, ok := ret.Get(0).(func(string) string); ok {
r0 = rf(serverURL)
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(serverURL)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Invalidate provides a mock function with given fields:
func (_m *LiveStateCache) Invalidate() {
_m.Called()

View File

@@ -0,0 +1,99 @@
package metrics
import (
"context"
"sync"
"time"
"github.com/prometheus/client_golang/prometheus"
)
const (
metricsCollectionInterval = 30 * time.Second
)
var (
descClusterDefaultLabels = []string{"server"}
descClusterInfo = prometheus.NewDesc(
"argocd_cluster_info",
"Information about cluster.",
append(descClusterDefaultLabels, "k8s_version"),
nil,
)
descClusterCacheResources = prometheus.NewDesc(
"argocd_cluster_api_resource_objects",
"Number of k8s resource objects in the cache.",
descClusterDefaultLabels,
nil,
)
descClusterAPIs = prometheus.NewDesc(
"argocd_cluster_api_resources",
"Number of monitored kubernetes API resources.",
descClusterDefaultLabels,
nil,
)
descClusterCacheAgeSeconds = prometheus.NewDesc(
"argocd_cluster_cache_age_seconds",
"Cluster cache age in seconds.",
descClusterDefaultLabels,
nil,
)
)
type ClusterInfo struct {
Server string
K8SVersion string
ResourcesCount int
APIsCount int
LastCacheSyncTime *time.Time
}
type HasClustersInfo interface {
GetClustersInfo() []ClusterInfo
}
type clusterCollector struct {
infoSource HasClustersInfo
info []ClusterInfo
lock sync.Mutex
}
func (c *clusterCollector) Run(ctx context.Context) {
tick := time.Tick(metricsCollectionInterval)
for {
select {
case <-ctx.Done():
break
case <-tick:
info := c.infoSource.GetClustersInfo()
c.lock.Lock()
c.info = info
c.lock.Unlock()
}
}
}
// Describe implements the prometheus.Collector interface
func (c *clusterCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- descClusterInfo
ch <- descClusterCacheResources
ch <- descClusterAPIs
ch <- descClusterCacheAgeSeconds
}
func (c *clusterCollector) Collect(ch chan<- prometheus.Metric) {
now := time.Now()
for _, c := range c.info {
defaultValues := []string{c.Server}
ch <- prometheus.MustNewConstMetric(descClusterInfo, prometheus.GaugeValue, 1, append(defaultValues, c.K8SVersion)...)
ch <- prometheus.MustNewConstMetric(descClusterCacheResources, prometheus.GaugeValue, float64(c.ResourcesCount), defaultValues...)
ch <- prometheus.MustNewConstMetric(descClusterAPIs, prometheus.GaugeValue, float64(c.APIsCount), defaultValues...)
cacheAgeSeconds := -1
if c.LastCacheSyncTime != nil {
cacheAgeSeconds = int(now.Sub(*c.LastCacheSyncTime).Seconds())
}
ch <- prometheus.MustNewConstMetric(descClusterCacheAgeSeconds, prometheus.GaugeValue, float64(cacheAgeSeconds), defaultValues...)
}
}

View File

@@ -1,6 +1,7 @@
package metrics
import (
"context"
"net/http"
"strconv"
"time"
@@ -19,10 +20,12 @@ import (
type MetricsServer struct {
*http.Server
syncCounter *prometheus.CounterVec
k8sRequestCounter *prometheus.CounterVec
kubectlExecCounter *prometheus.CounterVec
kubectlExecPendingGauge *prometheus.GaugeVec
k8sRequestCounter *prometheus.CounterVec
clusterEventsCounter *prometheus.CounterVec
reconcileHistogram *prometheus.HistogramVec
registry *prometheus.Registry
}
const (
@@ -64,10 +67,13 @@ var (
// NewMetricsServer returns a new prometheus server which collects application metrics
func NewMetricsServer(addr string, appLister applister.ApplicationLister, healthCheck func() error) *MetricsServer {
mux := http.NewServeMux()
appRegistry := NewAppRegistry(appLister)
appRegistry.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))
appRegistry.MustRegister(prometheus.NewGoCollector())
mux.Handle(MetricsPath, promhttp.HandlerFor(appRegistry, promhttp.HandlerOpts{}))
registry := NewAppRegistry(appLister)
mux.Handle(MetricsPath, promhttp.HandlerFor(prometheus.Gatherers{
// contains app controller specific metrics
registry,
// contains process, golang and controller workqueues metrics
prometheus.DefaultGatherer,
}, promhttp.HandlerOpts{}))
healthz.ServeHealthCheck(mux, healthCheck)
syncCounter := prometheus.NewCounterVec(
@@ -77,17 +83,8 @@ func NewMetricsServer(addr string, appLister applister.ApplicationLister, health
},
append(descAppDefaultLabels, "phase"),
)
appRegistry.MustRegister(syncCounter)
kubectlExecCounter := prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "argocd_kubectl_exec_total",
Help: "Number of kubectl executions",
}, []string{"command"})
appRegistry.MustRegister(kubectlExecCounter)
kubectlExecPendingGauge := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "argocd_kubectl_exec_pending",
Help: "Number of pending kubectl executions",
}, []string{"command"})
appRegistry.MustRegister(kubectlExecPendingGauge)
registry.MustRegister(syncCounter)
k8sRequestCounter := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "argocd_app_k8s_request_total",
@@ -95,7 +92,18 @@ func NewMetricsServer(addr string, appLister applister.ApplicationLister, health
},
append(descAppDefaultLabels, "response_code"),
)
appRegistry.MustRegister(k8sRequestCounter)
registry.MustRegister(k8sRequestCounter)
kubectlExecCounter := prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "argocd_kubectl_exec_total",
Help: "Number of kubectl executions",
}, []string{"command"})
registry.MustRegister(kubectlExecCounter)
kubectlExecPendingGauge := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "argocd_kubectl_exec_pending",
Help: "Number of pending kubectl executions",
}, []string{"command"})
registry.MustRegister(kubectlExecPendingGauge)
reconcileHistogram := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
@@ -107,21 +115,34 @@ func NewMetricsServer(addr string, appLister applister.ApplicationLister, health
descAppDefaultLabels,
)
appRegistry.MustRegister(reconcileHistogram)
registry.MustRegister(reconcileHistogram)
clusterEventsCounter := prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "argocd_cluster_events_total",
Help: "Number of processes k8s resource events.",
}, descClusterDefaultLabels)
registry.MustRegister(clusterEventsCounter)
return &MetricsServer{
registry: registry,
Server: &http.Server{
Addr: addr,
Handler: mux,
},
syncCounter: syncCounter,
k8sRequestCounter: k8sRequestCounter,
reconcileHistogram: reconcileHistogram,
kubectlExecCounter: kubectlExecCounter,
kubectlExecPendingGauge: kubectlExecPendingGauge,
reconcileHistogram: reconcileHistogram,
clusterEventsCounter: clusterEventsCounter,
}
}
func (m *MetricsServer) RegisterClustersInfoSource(ctx context.Context, source HasClustersInfo) {
collector := &clusterCollector{infoSource: source}
go collector.Run(ctx)
m.registry.MustRegister(collector)
}
// IncSync increments the sync counter for an application
func (m *MetricsServer) IncSync(app *argoappv1.Application, state *argoappv1.OperationState) {
if !state.Phase.Completed() {
@@ -130,16 +151,6 @@ func (m *MetricsServer) IncSync(app *argoappv1.Application, state *argoappv1.Ope
m.syncCounter.WithLabelValues(app.Namespace, app.Name, app.Spec.GetProject(), string(state.Phase)).Inc()
}
// IncKubernetesRequest increments the kubernetes requests counter for an application
func (m *MetricsServer) IncKubernetesRequest(app *argoappv1.Application, statusCode int) {
m.k8sRequestCounter.WithLabelValues(app.Namespace, app.Name, app.Spec.GetProject(), strconv.Itoa(statusCode)).Inc()
}
// IncReconcile increments the reconcile counter for an application
func (m *MetricsServer) IncReconcile(app *argoappv1.Application, duration time.Duration) {
m.reconcileHistogram.WithLabelValues(app.Namespace, app.Name, app.Spec.GetProject()).Observe(duration.Seconds())
}
func (m *MetricsServer) IncKubectlExec(command string) {
m.kubectlExecCounter.WithLabelValues(command).Inc()
}
@@ -152,6 +163,21 @@ func (m *MetricsServer) DecKubectlExecPending(command string) {
m.kubectlExecPendingGauge.WithLabelValues(command).Dec()
}
// IncClusterEventsCount increments the number of cluster events
func (m *MetricsServer) IncClusterEventsCount(server string) {
m.clusterEventsCounter.WithLabelValues(server).Inc()
}
// IncKubernetesRequest increments the kubernetes requests counter for an application
func (m *MetricsServer) IncKubernetesRequest(app *argoappv1.Application, statusCode int) {
m.k8sRequestCounter.WithLabelValues(app.Namespace, app.Name, app.Spec.GetProject(), strconv.Itoa(statusCode)).Inc()
}
// IncReconcile increments the reconcile counter for an application
func (m *MetricsServer) IncReconcile(app *argoappv1.Application, duration time.Duration) {
m.reconcileHistogram.WithLabelValues(app.Namespace, app.Name, app.Spec.GetProject()).Observe(duration.Seconds())
}
type appCollector struct {
store applister.ApplicationLister
}

View File

@@ -58,12 +58,11 @@ type ResourceInfoProvider interface {
// AppStateManager defines methods which allow to compare application spec and actual application state.
type AppStateManager interface {
CompareAppState(app *v1alpha1.Application, revision string, source v1alpha1.ApplicationSource, noCache bool, localObjects []string) *comparisonResult
CompareAppState(app *v1alpha1.Application, project *appv1.AppProject, revision string, source v1alpha1.ApplicationSource, noCache bool, localObjects []string) *comparisonResult
SyncAppState(app *v1alpha1.Application, state *v1alpha1.OperationState)
}
type comparisonResult struct {
reconciledAt metav1.Time
syncStatus *v1alpha1.SyncStatus
healthStatus *v1alpha1.HealthStatus
resources []v1alpha1.ResourceStatus
@@ -129,11 +128,7 @@ func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1
if err != nil {
return nil, nil, nil, err
}
cluster, err := m.db.GetCluster(context.Background(), app.Spec.Destination.Server)
if err != nil {
return nil, nil, nil, err
}
cluster.ServerVersion, err = m.kubectl.GetServerVersion(cluster.RESTConfig())
serverVersion, err := m.liveStateCache.GetServerVersion(app.Spec.Destination.Server)
if err != nil {
return nil, nil, nil, err
}
@@ -150,7 +145,7 @@ func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1
KustomizeOptions: &appv1.KustomizeOptions{
BuildOptions: buildOptions,
},
KubeVersion: cluster.ServerVersion,
KubeVersion: serverVersion,
})
if err != nil {
return nil, nil, nil, err
@@ -208,9 +203,11 @@ func DeduplicateTargetObjects(
result := make([]*unstructured.Unstructured, 0)
for key, targets := range targetByKey {
if len(targets) > 1 {
now := metav1.Now()
conditions = append(conditions, appv1.ApplicationCondition{
Type: appv1.ApplicationConditionRepeatedResourceWarning,
Message: fmt.Sprintf("Resource %s appeared %d times among application resources.", key.String(), len(targets)),
Type: appv1.ApplicationConditionRepeatedResourceWarning,
Message: fmt.Sprintf("Resource %s appeared %d times among application resources.", key.String(), len(targets)),
LastTransitionTime: &now,
})
}
result = append(result, targets[len(targets)-1])
@@ -274,14 +271,12 @@ func (m *appStateManager) getComparisonSettings(app *appv1.Application) (string,
// CompareAppState compares application git state to the live app state, using the specified
// revision and supplied source. If revision or overrides are empty, then compares against
// revision and overrides in the app spec.
func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision string, source v1alpha1.ApplicationSource, noCache bool, localManifests []string) *comparisonResult {
reconciledAt := metav1.Now()
func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *appv1.AppProject, revision string, source v1alpha1.ApplicationSource, noCache bool, localManifests []string) *comparisonResult {
appLabelKey, resourceOverrides, diffNormalizer, err := m.getComparisonSettings(app)
// return unknown comparison result if basic comparison settings cannot be loaded
if err != nil {
return &comparisonResult{
reconciledAt: reconciledAt,
syncStatus: &v1alpha1.SyncStatus{
ComparedTo: appv1.ComparedTo{Source: source, Destination: app.Spec.Destination},
Status: appv1.SyncStatusCodeUnknown,
@@ -300,19 +295,20 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
var targetObjs []*unstructured.Unstructured
var hooks []*unstructured.Unstructured
var manifestInfo *apiclient.ManifestResponse
now := metav1.Now()
if len(localManifests) == 0 {
targetObjs, hooks, manifestInfo, err = m.getRepoObjs(app, source, appLabelKey, revision, noCache)
if err != nil {
targetObjs = make([]*unstructured.Unstructured, 0)
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now})
failedToLoadObjs = true
}
} else {
targetObjs, hooks, err = unmarshalManifests(localManifests)
if err != nil {
targetObjs = make([]*unstructured.Unstructured, 0)
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now})
failedToLoadObjs = true
}
manifestInfo = nil
@@ -320,13 +316,13 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
targetObjs, dedupConditions, err := DeduplicateTargetObjects(app.Spec.Destination.Server, app.Spec.Destination.Namespace, targetObjs, m.liveStateCache)
if err != nil {
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now})
}
conditions = append(conditions, dedupConditions...)
resFilter, err := m.settingsMgr.GetResourcesFilter()
if err != nil {
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now})
} else {
for i := len(targetObjs) - 1; i >= 0; i-- {
targetObj := targetObjs[i]
@@ -334,8 +330,9 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
if resFilter.IsExcludedResource(gvk.Group, gvk.Kind, app.Spec.Destination.Server) {
targetObjs = append(targetObjs[:i], targetObjs[i+1:]...)
conditions = append(conditions, v1alpha1.ApplicationCondition{
Type: v1alpha1.ApplicationConditionExcludedResourceWarning,
Message: fmt.Sprintf("Resource %s/%s %s is excluded in the settings", gvk.Group, gvk.Kind, targetObj.GetName()),
Type: v1alpha1.ApplicationConditionExcludedResourceWarning,
Message: fmt.Sprintf("Resource %s/%s %s is excluded in the settings", gvk.Group, gvk.Kind, targetObj.GetName()),
LastTransitionTime: &now,
})
}
}
@@ -343,20 +340,28 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
logCtx.Debugf("Generated config manifests")
liveObjByKey, err := m.liveStateCache.GetManagedLiveObjs(app, targetObjs)
dedupLiveResources(targetObjs, liveObjByKey)
if err != nil {
liveObjByKey = make(map[kubeutil.ResourceKey]*unstructured.Unstructured)
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now})
failedToLoadObjs = true
}
dedupLiveResources(targetObjs, liveObjByKey)
// filter out all resources which are not permitted in the application project
for k, v := range liveObjByKey {
if !project.IsLiveResourcePermitted(v, app.Spec.Destination.Server) {
delete(liveObjByKey, k)
}
}
logCtx.Debugf("Retrieved lived manifests")
for _, liveObj := range liveObjByKey {
if liveObj != nil {
appInstanceName := kubeutil.GetAppInstanceLabel(liveObj, appLabelKey)
if appInstanceName != "" && appInstanceName != app.Name {
conditions = append(conditions, v1alpha1.ApplicationCondition{
Type: v1alpha1.ApplicationConditionSharedResourceWarning,
Message: fmt.Sprintf("%s/%s is part of a different application: %s", liveObj.GetKind(), liveObj.GetName(), appInstanceName),
Type: v1alpha1.ApplicationConditionSharedResourceWarning,
Message: fmt.Sprintf("%s/%s is part of a different application: %s", liveObj.GetKind(), liveObj.GetName(), appInstanceName),
LastTransitionTime: &now,
})
}
}
@@ -391,7 +396,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
if err != nil {
diffResults = &diff.DiffResultList{}
failedToLoadObjs = true
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now})
}
syncCode := v1alpha1.SyncStatusCodeSynced
@@ -435,6 +440,16 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
} else {
resState.Status = v1alpha1.SyncStatusCodeSynced
}
// set unknown status to all resource that are not permitted in the app project
isNamespaced, err := m.liveStateCache.IsNamespaced(app.Spec.Destination.Server, gvk.GroupKind())
if !project.IsGroupKindPermitted(gvk.GroupKind(), isNamespaced && err == nil) {
resState.Status = v1alpha1.SyncStatusCodeUnknown
}
// we can't say anything about the status if we were unable to get the target objects
if failedToLoadObjs {
resState.Status = v1alpha1.SyncStatusCodeUnknown
}
managedResources[i] = managedResource{
Name: resState.Name,
Namespace: resState.Namespace,
@@ -468,11 +483,10 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
})
if err != nil {
conditions = append(conditions, appv1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
conditions = append(conditions, appv1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now})
}
compRes := comparisonResult{
reconciledAt: reconciledAt,
syncStatus: &syncStatus,
healthStatus: healthStatus,
resources: resourceSummaries,
@@ -497,20 +511,18 @@ func (m *appStateManager) persistRevisionHistory(app *v1alpha1.Application, revi
if len(app.Status.History) > 0 {
nextID = app.Status.History[len(app.Status.History)-1].ID + 1
}
history := append(app.Status.History, v1alpha1.RevisionHistory{
app.Status.History = append(app.Status.History, v1alpha1.RevisionHistory{
Revision: revision,
DeployedAt: metav1.NewTime(time.Now().UTC()),
ID: nextID,
Source: source,
})
if len(history) > common.RevisionHistoryLimit {
history = history[1 : common.RevisionHistoryLimit+1]
}
app.Status.History = app.Status.History.Trunc(app.Spec.GetRevisionHistoryLimit())
patch, err := json.Marshal(map[string]map[string][]v1alpha1.RevisionHistory{
"status": {
"history": history,
"history": app.Status.History,
},
})
if err != nil {

View File

@@ -30,7 +30,7 @@ func TestCompareAppStateEmpty(t *testing.T) {
managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
}
ctrl := newFakeController(&data)
compRes := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false, nil)
compRes := ctrl.appStateManager.CompareAppState(app, &defaultProj, "", app.Spec.Source, false, nil)
assert.NotNil(t, compRes)
assert.NotNil(t, compRes.syncStatus)
assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
@@ -45,7 +45,7 @@ func TestCompareAppStateMissing(t *testing.T) {
data := fakeData{
apps: []runtime.Object{app},
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{string(test.PodManifest)},
Manifests: []string{test.PodManifest},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
@@ -53,7 +53,7 @@ func TestCompareAppStateMissing(t *testing.T) {
managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
}
ctrl := newFakeController(&data)
compRes := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false, nil)
compRes := ctrl.appStateManager.CompareAppState(app, &defaultProj, "", app.Spec.Source, false, nil)
assert.NotNil(t, compRes)
assert.NotNil(t, compRes.syncStatus)
assert.Equal(t, argoappv1.SyncStatusCodeOutOfSync, compRes.syncStatus.Status)
@@ -80,7 +80,7 @@ func TestCompareAppStateExtra(t *testing.T) {
},
}
ctrl := newFakeController(&data)
compRes := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false, nil)
compRes := ctrl.appStateManager.CompareAppState(app, &defaultProj, "", app.Spec.Source, false, nil)
assert.NotNil(t, compRes)
assert.Equal(t, argoappv1.SyncStatusCodeOutOfSync, compRes.syncStatus.Status)
assert.Equal(t, 1, len(compRes.resources))
@@ -106,7 +106,7 @@ func TestCompareAppStateHook(t *testing.T) {
managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
}
ctrl := newFakeController(&data)
compRes := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false, nil)
compRes := ctrl.appStateManager.CompareAppState(app, &defaultProj, "", app.Spec.Source, false, nil)
assert.NotNil(t, compRes)
assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
assert.Equal(t, 0, len(compRes.resources))
@@ -132,7 +132,7 @@ func TestCompareAppStateCompareOptionIgnoreExtraneous(t *testing.T) {
}
ctrl := newFakeController(&data)
compRes := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false, nil)
compRes := ctrl.appStateManager.CompareAppState(app, &defaultProj, "", app.Spec.Source, false, nil)
assert.NotNil(t, compRes)
assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
@@ -160,7 +160,7 @@ func TestCompareAppStateExtraHook(t *testing.T) {
},
}
ctrl := newFakeController(&data)
compRes := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false, nil)
compRes := ctrl.appStateManager.CompareAppState(app, &defaultProj, "", app.Spec.Source, false, nil)
assert.NotNil(t, compRes)
assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
@@ -197,13 +197,13 @@ func TestCompareAppStateDuplicatedNamespacedResources(t *testing.T) {
},
}
ctrl := newFakeController(&data)
compRes := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false, nil)
compRes := ctrl.appStateManager.CompareAppState(app, &defaultProj, "", app.Spec.Source, false, nil)
assert.NotNil(t, compRes)
assert.Contains(t, app.Status.Conditions, argoappv1.ApplicationCondition{
Message: "Resource /Pod/fake-dest-ns/my-pod appeared 2 times among application resources.",
Type: argoappv1.ApplicationConditionRepeatedResourceWarning,
})
assert.Equal(t, 1, len(app.Status.Conditions))
assert.NotNil(t, app.Status.Conditions[0].LastTransitionTime)
assert.Equal(t, argoappv1.ApplicationConditionRepeatedResourceWarning, app.Status.Conditions[0].Type)
assert.Equal(t, "Resource /Pod/fake-dest-ns/my-pod appeared 2 times among application resources.", app.Status.Conditions[0].Message)
assert.Equal(t, 2, len(compRes.resources))
}
@@ -248,7 +248,7 @@ func TestSetHealth(t *testing.T) {
},
})
compRes := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false, nil)
compRes := ctrl.appStateManager.CompareAppState(app, &defaultProj, "", app.Spec.Source, false, nil)
assert.Equal(t, compRes.healthStatus.Status, argoappv1.HealthStatusHealthy)
}
@@ -280,7 +280,7 @@ func TestSetHealthSelfReferencedApp(t *testing.T) {
},
})
compRes := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false, nil)
compRes := ctrl.appStateManager.CompareAppState(app, &defaultProj, "", app.Spec.Source, false, nil)
assert.Equal(t, compRes.healthStatus.Status, argoappv1.HealthStatusHealthy)
}
@@ -350,11 +350,10 @@ func TestReturnUnknownComparisonStateOnSettingLoadError(t *testing.T) {
},
})
compRes := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false, nil)
compRes := ctrl.appStateManager.CompareAppState(app, &defaultProj, "", app.Spec.Source, false, nil)
assert.Equal(t, argoappv1.HealthStatusUnknown, compRes.healthStatus.Status)
assert.Equal(t, argoappv1.SyncStatusCodeUnknown, compRes.syncStatus.Status)
assert.NotNil(t, compRes.reconciledAt)
}
func TestSetManagedResourcesKnownOrphanedResourceExceptions(t *testing.T) {
@@ -392,3 +391,50 @@ func Test_comparisonResult_obs(t *testing.T) {
assert.Len(t, (&comparisonResult{managedResources: []managedResource{{Target: test.NewPod()}}}).targetObjs(), 1)
assert.Len(t, (&comparisonResult{hooks: []*unstructured.Unstructured{{}}}).targetObjs(), 1)
}
func Test_appStateManager_persistRevisionHistory(t *testing.T) {
app := newFakeApp()
ctrl := newFakeController(&fakeData{
apps: []runtime.Object{app},
})
manager := ctrl.appStateManager.(*appStateManager)
setRevisionHistoryLimit := func(value int) {
i := int64(value)
app.Spec.RevisionHistoryLimit = &i
}
addHistory := func() {
err := manager.persistRevisionHistory(app, "my-revision", argoappv1.ApplicationSource{})
assert.NoError(t, err)
}
addHistory()
assert.Len(t, app.Status.History, 1)
addHistory()
assert.Len(t, app.Status.History, 2)
addHistory()
assert.Len(t, app.Status.History, 3)
addHistory()
assert.Len(t, app.Status.History, 4)
addHistory()
assert.Len(t, app.Status.History, 5)
addHistory()
assert.Len(t, app.Status.History, 6)
addHistory()
assert.Len(t, app.Status.History, 7)
addHistory()
assert.Len(t, app.Status.History, 8)
addHistory()
assert.Len(t, app.Status.History, 9)
addHistory()
assert.Len(t, app.Status.History, 10)
// default limit is 10
addHistory()
assert.Len(t, app.Status.History, 10)
// increase limit
setRevisionHistoryLimit(11)
addHistory()
assert.Len(t, app.Status.History, 11)
// decrease limit
setRevisionHistoryLimit(9)
addHistory()
assert.Len(t, app.Status.History, 9)
}

View File

@@ -17,6 +17,7 @@ import (
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
@@ -106,7 +107,14 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
revision = syncOp.Revision
}
compareResult := m.CompareAppState(app, revision, source, false, syncOp.Manifests)
proj, err := argo.GetAppProject(&app.Spec, listersv1alpha1.NewAppProjectLister(m.projInformer.GetIndexer()), m.namespace)
if err != nil {
state.Phase = v1alpha1.OperationError
state.Message = fmt.Sprintf("Failed to load application project: %v", err)
return
}
compareResult := m.CompareAppState(app, proj, revision, source, false, syncOp.Manifests)
// If there are any comparison or spec errors error conditions do not perform the operation
if errConditions := app.Status.GetConditions(map[v1alpha1.ApplicationConditionType]bool{
@@ -150,13 +158,6 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
return
}
proj, err := argo.GetAppProject(&app.Spec, listersv1alpha1.NewAppProjectLister(m.projInformer.GetIndexer()), m.namespace)
if err != nil {
state.Phase = v1alpha1.OperationError
state.Message = fmt.Sprintf("Failed to load application project: %v", err)
return
}
resourceOverrides, err := m.settingsMgr.GetResourceOverrides()
if err != nil {
state.Phase = v1alpha1.OperationError
@@ -235,16 +236,21 @@ func (sc *syncContext) sync() {
}) {
if task.isHook() {
// update the hook's result
operationState, message := getOperationPhase(task.liveObj)
sc.setResourceResult(task, "", operationState, message)
operationState, message, err := sc.getOperationPhase(task.liveObj)
if err != nil {
sc.setResourceResult(task, "", v1alpha1.OperationError, fmt.Sprintf("failed to get resource health: %v", err))
} else {
sc.setResourceResult(task, "", operationState, message)
// maybe delete the hook
if task.needsDeleting() {
err := sc.deleteResource(task)
if err != nil && !errors.IsNotFound(err) {
sc.setResourceResult(task, "", v1alpha1.OperationError, fmt.Sprintf("failed to delete resource: %v", err))
// maybe delete the hook
if task.needsDeleting() {
err := sc.deleteResource(task)
if err != nil && !errors.IsNotFound(err) {
sc.setResourceResult(task, "", v1alpha1.OperationError, fmt.Sprintf("failed to delete resource: %v", err))
}
}
}
} else {
// this must be calculated on the live object
healthStatus, err := health.GetResourceHealth(task.liveObj, sc.resourceOverrides)
@@ -480,7 +486,7 @@ func (sc *syncContext) getSyncTasks() (_ syncTasks, successful bool) {
successful = false
}
} else {
if !sc.proj.IsResourcePermitted(metav1.GroupKind{Group: task.group(), Kind: task.kind()}, serverRes.Namespaced) {
if !sc.proj.IsGroupKindPermitted(schema.GroupKind{Group: task.group(), Kind: task.kind()}, serverRes.Namespaced) {
sc.setResourceResult(task, v1alpha1.ResultCodeSyncFailed, "", fmt.Sprintf("Resource %s:%s is not permitted in project %s.", task.group(), task.kind(), sc.proj.Name))
successful = false
}
@@ -602,10 +608,15 @@ func (sc *syncContext) terminate() {
sc.log.Debug("terminating")
tasks, _ := sc.getSyncTasks()
for _, task := range tasks {
if !task.isHook() || !task.completed() {
if !task.isHook() || task.liveObj == nil {
continue
}
if isRunnable(task.groupVersionKind()) {
phase, msg, err := sc.getOperationPhase(task.liveObj)
if err != nil {
sc.setOperationPhase(v1alpha1.OperationError, fmt.Sprintf("Failed to get hook health: %v", err))
return
}
if phase == v1alpha1.OperationRunning {
err := sc.deleteResource(task)
if err != nil {
sc.setResourceResult(task, "", v1alpha1.OperationFailed, fmt.Sprintf("Failed to delete: %v", err))
@@ -613,6 +624,8 @@ func (sc *syncContext) terminate() {
} else {
sc.setResourceResult(task, "", v1alpha1.OperationSucceeded, fmt.Sprintf("Deleted"))
}
} else {
sc.setResourceResult(task, "", phase, msg)
}
}
if terminateSuccessful {
@@ -682,12 +695,14 @@ func (sc *syncContext) runTasks(tasks syncTasks, dryRun bool) runState {
wg.Add(1)
go func(t *syncTask) {
defer wg.Done()
sc.log.WithFields(log.Fields{"dryRun": dryRun, "task": t}).Debug("pruning")
logCtx := sc.log.WithFields(log.Fields{"dryRun": dryRun, "task": t})
logCtx.Debug("pruning")
result, message := sc.pruneObject(t.liveObj, sc.syncOp.Prune, dryRun)
if result == v1alpha1.ResultCodeSyncFailed {
runState = failed
logCtx.WithField("message", message).Info("pruning failed")
}
if !dryRun || result == v1alpha1.ResultCodeSyncFailed {
if !dryRun || sc.syncOp.DryRun || result == v1alpha1.ResultCodeSyncFailed {
sc.setResourceResult(t, result, operationPhases[result], message)
}
}(task)
@@ -733,12 +748,14 @@ func (sc *syncContext) runTasks(tasks syncTasks, dryRun bool) runState {
createWg.Add(1)
go func(t *syncTask) {
defer createWg.Done()
sc.log.WithFields(log.Fields{"dryRun": dryRun, "task": t}).Debug("applying")
logCtx := sc.log.WithFields(log.Fields{"dryRun": dryRun, "task": t})
logCtx.Debug("applying")
result, message := sc.applyObject(t.targetObj, dryRun, sc.syncOp.SyncStrategy.Force())
if result == v1alpha1.ResultCodeSyncFailed {
logCtx.WithField("message", message).Info("apply failed")
runState = failed
}
if !dryRun || result == v1alpha1.ResultCodeSyncFailed {
if !dryRun || sc.syncOp.DryRun || result == v1alpha1.ResultCodeSyncFailed {
sc.setResourceResult(t, result, operationPhases[result], message)
}
}(task)

View File

@@ -3,118 +3,33 @@ package controller
import (
"fmt"
"github.com/argoproj/argo-cd/util/health"
apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/apis/batch"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/health"
)
// getOperationPhase returns a hook status from an _live_ unstructured object
func getOperationPhase(hook *unstructured.Unstructured) (operation v1alpha1.OperationPhase, message string) {
gvk := hook.GroupVersionKind()
if isBatchJob(gvk) {
return getStatusFromBatchJob(hook)
} else if isArgoWorkflow(gvk) {
return health.GetStatusFromArgoWorkflow(hook)
} else if isPod(gvk) {
return getStatusFromPod(hook)
} else {
return v1alpha1.OperationSucceeded, fmt.Sprintf("%s created", hook.GetName())
}
}
func (sc *syncContext) getOperationPhase(hook *unstructured.Unstructured) (v1alpha1.OperationPhase, string, error) {
phase := v1alpha1.OperationSucceeded
message := fmt.Sprintf("%s created", hook.GetName())
// isRunnable returns if the resource object is a runnable type which needs to be terminated
func isRunnable(gvk schema.GroupVersionKind) bool {
return isBatchJob(gvk) || isArgoWorkflow(gvk) || isPod(gvk)
}
func isBatchJob(gvk schema.GroupVersionKind) bool {
return gvk.Group == "batch" && gvk.Kind == "Job"
}
// TODO this is a copy-and-paste of health.getJobHealth(), refactor out?
func getStatusFromBatchJob(hook *unstructured.Unstructured) (operation v1alpha1.OperationPhase, message string) {
var job batch.Job
err := runtime.DefaultUnstructuredConverter.FromUnstructured(hook.Object, &job)
resHealth, err := health.GetResourceHealth(hook, sc.resourceOverrides)
if err != nil {
return v1alpha1.OperationError, err.Error()
return "", "", err
}
failed := false
var failMsg string
complete := false
for _, condition := range job.Status.Conditions {
switch condition.Type {
case batch.JobFailed:
failed = true
complete = true
failMsg = condition.Message
case batch.JobComplete:
complete = true
message = condition.Message
if resHealth != nil {
switch resHealth.Status {
case v1alpha1.HealthStatusUnknown, v1alpha1.HealthStatusDegraded:
phase = v1alpha1.OperationFailed
message = resHealth.Message
case v1alpha1.HealthStatusProgressing, v1alpha1.HealthStatusSuspended:
phase = v1alpha1.OperationRunning
message = resHealth.Message
case v1alpha1.HealthStatusHealthy:
phase = v1alpha1.OperationSucceeded
message = resHealth.Message
}
}
if !complete {
return v1alpha1.OperationRunning, message
} else if failed {
return v1alpha1.OperationFailed, failMsg
} else {
return v1alpha1.OperationSucceeded, message
}
}
func isArgoWorkflow(gvk schema.GroupVersionKind) bool {
return gvk.Group == "argoproj.io" && gvk.Kind == "Workflow"
}
func isPod(gvk schema.GroupVersionKind) bool {
return gvk.Group == "" && gvk.Kind == "Pod"
}
// TODO - this is very similar to health.getPodHealth() should we use that instead?
func getStatusFromPod(hook *unstructured.Unstructured) (v1alpha1.OperationPhase, string) {
var pod apiv1.Pod
err := runtime.DefaultUnstructuredConverter.FromUnstructured(hook.Object, &pod)
if err != nil {
return v1alpha1.OperationError, err.Error()
}
getFailMessage := func(ctr *apiv1.ContainerStatus) string {
if ctr.State.Terminated != nil {
if ctr.State.Terminated.Message != "" {
return ctr.State.Terminated.Message
}
if ctr.State.Terminated.Reason == "OOMKilled" {
return ctr.State.Terminated.Reason
}
if ctr.State.Terminated.ExitCode != 0 {
return fmt.Sprintf("container %q failed with exit code %d", ctr.Name, ctr.State.Terminated.ExitCode)
}
}
return ""
}
switch pod.Status.Phase {
case apiv1.PodPending, apiv1.PodRunning:
return v1alpha1.OperationRunning, ""
case apiv1.PodSucceeded:
return v1alpha1.OperationSucceeded, ""
case apiv1.PodFailed:
if pod.Status.Message != "" {
// Pod has a nice error message. Use that.
return v1alpha1.OperationFailed, pod.Status.Message
}
for _, ctr := range append(pod.Status.InitContainerStatuses, pod.Status.ContainerStatuses...) {
if msg := getFailMessage(&ctr); msg != "" {
return v1alpha1.OperationFailed, msg
}
}
return v1alpha1.OperationFailed, ""
case apiv1.PodUnknown:
return v1alpha1.OperationError, ""
}
return v1alpha1.OperationRunning, ""
return phase, message, nil
}

View File

@@ -1,4 +1,5 @@
# Contributing
## Before You Start
You must install and run the ArgoCD using a local Kubernetes (e.g. Docker for Desktop or Minikube) first. This will help you understand the application, but also get your local environment set-up.
@@ -21,17 +22,17 @@ Install:
* [minikube](https://kubernetes.io/docs/setup/minikube/) or Docker for Desktop
Brew users can quickly install the lot:
```bash
brew install go git-lfs kubectl kubectx dep ksonnet/tap/ks kubernetes-helm kustomize kustomize
brew install go git-lfs kubectl kubectx dep ksonnet/tap/ks kubernetes-helm kustomize
```
Check the versions:
```
```bash
go version ;# must be v1.12.x
helm version ;# must be v2.13.x
kustomize version ;# must be v3.10.x
kustomize version ;# must be v3.1.x
```
Set up environment variables (e.g. is `~/.bashrc`):
@@ -54,8 +55,10 @@ Ensure dependencies are up to date first:
```shell
dep ensure
make dev-builder-image
make dev-tools-image
make install-lint-tools
go get github.com/mattn/goreman
go get github.com/jstemmer/go-junit-report
```
Common make targets:
@@ -84,7 +87,7 @@ kubectl -n argocd scale deployment/argocd-redis --replicas 0
Download Yarn dependencies and Compile:
```bash
~/go/src/github.com/argoproj/argo-cd/ui
~/go/src/github.com/argoproj/argo-cd/ui
yarn install
yarn build
```
@@ -102,7 +105,7 @@ You can now execute `argocd` command against your locally running ArgoCD by appe
argocd app create guestbook --path guestbook --repo https://github.com/argoproj/argocd-example-apps.git --dest-server https://kubernetes.default.svc --dest-namespace default --server localhost:8080 --plaintext --insecure
```
You can open the UI: http://localhost:4000
You can open the UI: [http://localhost:4000](http://localhost:4000)
As an alternative to using the above command line parameters each time you call `argocd` CLI, you can set the following environment variables:

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/assets/argo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

BIN
docs/assets/create-app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

BIN
docs/assets/destination.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
docs/assets/filter-apps.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
docs/assets/new-app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/assets/saml-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/assets/saml-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

BIN
docs/assets/saml-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
docs/assets/saml-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

BIN
docs/assets/sync-apps.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

56
docs/cli_installation.md Normal file
View File

@@ -0,0 +1,56 @@
# Installation
You can download the latest Argo CD version from [the latest release page of this repository](https://github.com/argoproj/argo-cd/releases/latest), which will include the `argocd` CLI.
## Linux
You can view the latest version of Argo CD at the link above or run the following command to grab the version:
```bash
VERSION=$(curl --silent "https://api.github.com/repos/argoproj/argo-cd/releases/latest" | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/')
```
Replace `VERSION` in the command below with the version of Argo CD you would like to download:
```bash
curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/$VERSION/argocd-linux-amd64
```
Make the `argocd` CLI executable:
```bash
chmod +x /usr/local/bin/argocd
```
You should now be able to run `argocd` commands.
## Mac
### Homebrew
```bash
brew tap argoproj/tap
brew install argoproj/tap/argocd
```
### Download With Curl
You can view the latest version of Argo CD at the link above or run the following command to grab the version:
```bash
VERSION=$(curl --silent "https://api.github.com/repos/argoproj/argo-cd/releases/latest" | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/')
```
Replace `VERSION` in the command below with the version of Argo CD you would like to download:
```bash
curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/$VERSION/argocd-darwin-amd64
```
Make the `argocd` CLI executable:
```bash
chmod +x /usr/local/bin/argocd
```
After finishing either of the instructions above, you should now be able to run `argocd` commands.

View File

@@ -4,13 +4,13 @@ Let's assume you're familiar with core Git, Docker, Kubernetes, Continuous Deliv
* **Application** A group of Kubernetes resources as defined by a manifest. This is a Custom Resource Definition (CRD).
* **Application source type** Which **Tool** is used to build the application.
* **Target state** The desired state of an application, as represented by files in a Git repository.
* **Target state** The desired state of an application, as represented by files in a Git repository.
* **Live state** The live state of that application. What pods etc are deployed.
* **Sync status** Whether or not the live state matches the target state. Is the deployed application the same as Git says it should be?
* **Sync** The process of making an application move to its target state. E.g. by applying changes to a Kubernetes cluster.
* **Sync status** Whether or not the live state matches the target state. Is the deployed application the same as Git says it should be?
* **Sync** The process of making an application move to its target state. E.g. by applying changes to a Kubernetes cluster.
* **Sync operation status** Whether or not a sync succeeded.
* **Refresh** Compare the latest code in Git with the live state. Figure out what is different.
* **Health** The health the application, is it running correctly? Can it serve requests?
* **Health** The health of the application, is it running correctly? Can it serve requests?
* **Tool** A tool to create manifests from a directory of files. E.g. Kustomize or Ksonnet. See **Application Source Type**.
* **Configuration management tool** See **Tool**.
* **Configuration management plugin** A custom tool.

View File

@@ -20,7 +20,7 @@ $ curl $ARGOCD_SERVER/api/v1/applications --cookie "argocd.token=$ARGOCD_TOKEN"
{"metadata":{"selfLink":"/apis/argoproj.io/v1alpha1/namespaces/argocd/applications","resourceVersion":"37755"},"items":...}
```
> >v1.3
> v1.3
Then pass using the HTTP `Authorization` header, prefixing with `Bearer `:
@@ -28,5 +28,4 @@ Then pass using the HTTP `Authorization` header, prefixing with `Bearer `:
$ curl $ARGOCD_SERVER/api/v1/applications -H "Authorization: Bearer $ARGOCD_TOKEN"
{"metadata":{"selfLink":"/apis/argoproj.io/v1alpha1/namespaces/argocd/applications","resourceVersion":"37755"},"items":...}
```
You sh

View File

@@ -10,16 +10,16 @@ Export the upstream repository and branch name, e.g.:
```bash
REPO=upstream ;# or origin
BRANCH=release-1.0
BRANCH=release-1.3
```
Set the `VERSION` environment variable:
```bash
# release candidate
VERSION=v1.0.0-rc1
VERSION=v1.3.0-rc1
# GA release
VERSION=v1.0.2
VERSION=v1.3.1
```
Update `VERSION` and manifests with new version:
@@ -27,41 +27,39 @@ Update `VERSION` and manifests with new version:
```bash
git checkout $BRANCH
echo ${VERSION:1} > VERSION
make dev-tools-image
make manifests IMAGE_TAG=$VERSION
git commit -am "Update manifests to $VERSION"
git push $REPO $BRANCH
git tag $VERSION
```
Tag, build, and push release to Docker Hub
Build, and push release to Docker Hub
```bash
git tag $VERSION
git clean -fd
make release IMAGE_NAMESPACE=argoproj IMAGE_TAG=$VERSION DOCKER_PUSH=true
git push $REPO $BRANCH
git push $REPO $VERSION
```
If GA, update `stable` tag:
```bash
git tag stable --force && git push $REPO stable --force
```
Update [Github releases](https://github.com/argoproj/argo-cd/releases) with:
* Getting started (copy from previous release)
* Changelog
* Binaries (e.g. `dist/argocd-darwin-amd64`).
If GA, update `stable` tag:
```bash
git tag stable --force && git push $REPO stable --force
```
If GA, update Brew formula:
```bash
git clone https://github.com/argoproj/homebrew-tap
git clone git@github.com:argoproj/homebrew-tap.git
cd homebrew-tap
git checkout master
git pull
./update.sh ~/go/src/github.com/argoproj/argo-cd/dist/argocd-darwin-amd64
./update.sh argocd $VERSION
git commit -am "Update argocd to $VERSION"
git push
```

View File

@@ -7,19 +7,19 @@ The web site is build using `mkdocs` and `mkdocs-material`.
To test:
```bash
mkdocs serve
make serve-docs
```
Check for broken external links:
```bash
find docs -name '*.md' -exec grep -l http {} + | xargs awesome_bot -t 3 --allow-dupe --allow-redirect -w argocd.example.com:443,argocd.example.com,kubernetes.default.svc:443,kubernetes.default.svc,mycluster.com,https://github.com/argoproj/my-private-repository,192.168.0.20,storage.googleapis.com,localhost:8080,localhost:6443,your-kubernetes-cluster-addr,10.97.164.88 --skip-save-results --
make lint-docs
```
## Deploying
```bash
mkdocs gh-deploy
make publish-docs
```
## Analytics

View File

@@ -129,10 +129,10 @@ See [#2165](https://github.com/argoproj/argo-cd/issues/2165).
## Why Am I Getting `rpc error: code = Unavailable desc = transport is closing` When Using The CLI?
Maybe you're behind a proxy that does not support HTTP 2? Try the `--grcp-web` flag.:
Maybe you're behind a proxy that does not support HTTP 2? Try the `--grpc-web` flag.:
```bash
argocd ... --grcp-web
argocd ... --grpc-web
```
## Why Am I Getting `x509: certificate signed by unknown authority` When Using The CLI?

View File

@@ -1,7 +1,7 @@
# Getting Started
!!! tip
This guide assumes you have a grounding in the tools that Argo CD is based on. Please read the [understanding the basics](understand_the_basics.md).
This guide assumes you have a grounding in the tools that Argo CD is based on. Please read [understanding the basics](understand_the_basics.md) to learn about these tools.
## Requirements
@@ -23,9 +23,16 @@ On GKE, you will need grant your account the ability to create new cluster roles
kubectl create clusterrolebinding YOURNAME-cluster-admin-binding --clusterrole=cluster-admin --user=YOUREMAIL@gmail.com
```
!!! note
If you are not interested in UI, SSO, multi-cluster management and just want to pull changes into the cluster then you can disable
authentication using `--disable-auth` flag and access Argo CD via CLI using `--port-forward` or `--port-forward-namespace` flags
and proceed to step [#6](#6-create-an-application-from-a-git-repository):
`kubectl patch deploy argocd-server -n argocd -p '[{"op": "add", "path": "/spec/template/spec/containers/0/command/-", "value": "--disable-auth"}]' --type json`
## 2. Download Argo CD CLI
Download the latest Argo CD version from [https://github.com/argoproj/argo-cd/releases/latest](https://github.com/argoproj/argo-cd/releases/latest).
Download the latest Argo CD version from [https://github.com/argoproj/argo-cd/releases/latest](https://github.com/argoproj/argo-cd/releases/latest). More detailed installation instructions can be found via the [CLI installation documentation](cli_installation.md).
Also available in Mac Homebrew:
@@ -34,7 +41,6 @@ brew tap argoproj/tap
brew install argoproj/tap/argocd
```
## 3. Access The Argo CD API Server
By default, the Argo CD API server is not exposed with an external IP. To access the API server,
@@ -62,14 +68,14 @@ The API server can then be accessed using the localhost:8080
## 4. Login Using The CLI
Login as the `admin` user. The initial password is autogenerated to be the pod name of the
The initial password is autogenerated to be the pod name of the
Argo CD API server. This can be retrieved with the command:
```bash
kubectl get pods -n argocd -l app.kubernetes.io/name=argocd-server -o name | cut -d'/' -f 2
```
Using the above password, login to Argo CD's IP or hostname:
Using the username `admin` and the password from above, login to Argo CD's IP or hostname:
```bash
argocd login <ARGOCD_SERVER>
@@ -114,25 +120,34 @@ An example repository containing a guestbook application is available at
### Creating Apps Via CLI
~~~bash
argocd app create guestbook \
--repo https://github.com/argoproj/argocd-example-apps.git \
--path guestbook \
--dest-server https://kubernetes.default.svc \
--dest-namespace default
~~~
!!! note
You can access Argo CD using port forwarding: add `--port-forward-namespace argocd` flag to every CLI command or set `ARGOCD_OPTS` environment variable: `export ARGOCD_OPTS='--port-forward-namespace argocd'`:
`argocd app create guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --dest-server https://kubernetes.default.svc --dest-namespace default`
### Creating Apps Via UI
Open a browser to the Argo CD external UI, and login using the credentials, IP/hostname set in step 4.
Connect the [https://github.com/argoproj/argocd-example-apps.git](https://github.com/argoproj/argocd-example-apps.git) repo to Argo CD:
Open a browser to the Argo CD external UI, and login by visiting the IP/hostname in a browser and use the credentials set in step 4.
![connect repo](assets/connect_repo.png)
After logging in, click the **+ New App** button as shown below:
After connecting a repository, select the guestbook application for creation:
![+ new app button](assets/new-app.png)
![select app](assets/select_app.png)
![create app](assets/create_app.png)
Give your app the name `guestbook`, use the project `default`, and leave the sync policy as `Manual`:
![app information](assets/app-ui-information.png)
Connect the [https://github.com/argoproj/argocd-example-apps.git](https://github.com/argoproj/argocd-example-apps.git) repo to Argo CD by setting repository url to the github repo url, leave revision as `HEAD`, and set the path to `guestbook`:
![connect repo](assets/connect-repo.png)
For **Destination**, set cluster to `in-cluster` and namespace to `default`:
![destination](assets/destination.png)
After filling out the information above, click **Create** at the top of the UI to create the `guestbook` application:
![destination](assets/create-app.png)
## 7. Sync (Deploy) The Application
@@ -157,7 +172,7 @@ apps Deployment default guestbook-ui OutOfSync Missing
Service default guestbook-ui OutOfSync Missing
```
The application status is initially `OutOfSync` state, since the application has yet to be
The application status is initially in `OutOfSync` state since the application has yet to be
deployed, and no Kubernetes resources have been created. To sync (deploy) the application, run:
```bash

View File

@@ -1,12 +1,16 @@
# Overview
<!-- markdownlint-disable MD026 -->
## What Is Argo CD?
<!-- markdownlint-enable MD026 -->
Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes.
![Argo CD UI](assets/argocd-ui.gif)
<!-- markdownlint-disable MD026 -->
## Why Argo CD?
<!-- markdownlint-enable MD026 -->
Application definitions, configurations, and environments should be declarative and version controlled.
Application deployment and lifecycle management should be automated, auditable, and easy to understand.
@@ -31,7 +35,7 @@ the desired application state. Kubernetes manifests can be specified in several
* [kustomize](https://kustomize.io) applications
* [helm](https://helm.sh) charts
* [ksonnet](https://ksonnet.io) applications
* [jsonnet](https://jsonnet.org) files
* [jsonnet](https://jsonnet.org) files
* Plain directory of YAML/json manifests
* Any custom config management tool configured as a config management plugin
@@ -44,7 +48,6 @@ For a quick 10 minute overview of Argo CD, check out the demo presented to the S
meeting:
[![Alt text](https://img.youtube.com/vi/aWDIQMbp1cc/0.jpg)](https://youtu.be/aWDIQMbp1cc?t=1m4s)
## Architecture
![Argo CD Architecture](assets/argocd_architecture.png)
@@ -82,4 +85,3 @@ For additional details, see [architecture overview](operator-manual/architecture
## Development Status
Argo CD is actively developed and is being used in production to deploy SaaS services at Intuit

View File

@@ -29,6 +29,22 @@ spec:
valueFiles:
- values-prod.yaml
# Values file as block file
values: |
ingress:
enabled: true
path: /
hosts:
- mydomain.example.com
annotations:
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: "true"
labels: {}
tls:
- secretName: mydomain-tls
hosts:
- mydomain.example.com
# kustomize specific config
kustomize:

View File

@@ -27,7 +27,7 @@ data:
help.chatText: 'Chat now!'
# A dex connector configuration (optional). See SSO configuration documentation:
# https://github.com/argoproj/argo-cd/blob/master/docs/operator-manual/sso.md
# https://github.com/argoproj/argo-cd/blob/master/docs/operator-manual/sso
# https://github.com/dexidp/dex/tree/master/Documentation/connectors
dex.config: |
connectors:
@@ -124,6 +124,29 @@ data:
hs.status = "Progressing"
hs.message = "Waiting for certificate"
return hs
cert-manager.io/Certificate:
# Lua script for customizing the health status assessment
health.lua: |
hs = {}
if obj.status ~= nil then
if obj.status.conditions ~= nil then
for i, condition in ipairs(obj.status.conditions) do
if condition.type == "Ready" and condition.status == "False" then
hs.status = "Degraded"
hs.message = condition.message
return hs
end
if condition.type == "Ready" and condition.status == "True" then
hs.status = "Healthy"
hs.message = condition.message
return hs
end
end
end
end
hs.status = "Progressing"
hs.message = "Waiting for certificate"
return hs
apps/Deployment:
# List of Lua Scripts to introduce custom actions
actions: |

View File

@@ -24,6 +24,13 @@ data:
# Shared secrets for authenticating GitHub, GitLab, BitBucket webhook events (optional).
# See https://github.com/argoproj/argo-cd/blob/master/docs/operator-manual/webhook.md for additional details.
github.webhook.secret:
gitlab.webhook.secret:
bitbucket.webhook.uuid:
# github webhook secret
webhook.github.secret: shhhh! it's a github secret
# gitlab webhook secret
webhook.gitlab.secret: shhhh! it's a gitlab secret
# bitbucket webhook secret
webhook.bitbucket.uuid: your-bitbucket-uuid
# bitbucket server webhook secret
webhook.bitbucketserver.secret: shhhh! it's a bitbucket server secret
# gogs server webhook secret
webhook.gogs.secret: shhhh! it's a gogs server secret

View File

@@ -46,13 +46,10 @@ spec:
source:
path: guestbook
repoURL: https://github.com/argoproj/argocd-example-apps
targetRevision: 08836bd970037ebcd14494831de4635bad961139
syncPolicy:
automated:
prune: true
targetRevision: HEAD
```
The sync policy to automated + prune, so that child app is are automatically created, synced, and deleted when the manifest is changed, but you may wish to disable this. I've also added the finalizer, which will ensure that you apps are deleted correctly.
The sync policy to automated + prune, so that child apps are automatically created, synced, and deleted when the manifest is changed, but you may wish to disable this. I've also added the finalizer, which will ensure that your apps are deleted correctly.
Fix the revision to a specific Git commit SHA to make sure that, even if the child apps repo changes, the app will only change when the parent app change that revision. Alternatively, you can set it to HEAD or a branch name.
@@ -66,17 +63,33 @@ spec:
server: https://kubernetes.default.svc
```
Finally, you need to create your parent app, e.g.:
Next, you need to create and sync your parent app, e.g. via the CLI:
```bash
argocd app create applications \
argocd app create apps \
--dest-namespace argocd \
--dest-server https://kubernetes.default.svc \
--repo https://github.com/argoproj/argocd-example-apps.git \
--path apps \
--sync-policy automated
--path apps
argocd app sync apps
```
In this example, I excluded auto-prune, as this would result in all apps being deleted if some accidentally deleted the *app of apps*.
The parent app will appear as in-sync but the child apps will be out of sync:
![New App Of Apps](../assets/new-app-of-apps.png)
You can either sync via the UI, firstly filter by the correct label:
![Filter Apps](../assets/filter-apps.png)
Then select the "out of sync" apps and sync:
![Sync Apps](../assets/sync-apps.png)
Or, via the CLI:
```bash
argocd app sync -l app.kubernetes.io/instance=apps
```
View [the example on Github](https://github.com/argoproj/argocd-example-apps/tree/master/apps).

View File

@@ -3,6 +3,7 @@
Argo CD applications, projects and settings can be defined declaratively using Kubernetes manifests.
## Quick Reference
| Name | Kind | Description |
|------|------|-------------|
| [`argocd-cm.yaml`](argocd-cm.yaml) | ConfigMap | General Argo CD configuration |
@@ -47,7 +48,7 @@ See [application.yaml](application.yaml) for additional fields
!!! warning
By default, deleting an application will not perform a cascade delete, thereby deleting its resources. You must add the finalizer if you want this behaviour - which you may well not want.
```yaml
metadata:
finalizers:
@@ -56,12 +57,13 @@ metadata:
### App of Apps
You can create an app that creates other apps, which in turn can create other apps.
You can create an app that creates other apps, which in turn can create other apps.
This allows you to declaratively manage a group of app that can be deployed and configured in concert.
See [cluster bootstrapping](cluster-bootstrapping.md).
## Projects
The AppProject CRD is the Kubernetes resource object representing a logical grouping of applications.
It is defined by the following key pieces of information:
@@ -168,12 +170,11 @@ data:
```
!!! tip
The Kubernetes documentation has [instructions for creating a secret containing a private key](https://kubernetes.io/docs/concepts/configuration/secret/#use-case-pod-with-ssh-keys).
The Kubernetes documentation has [instructions for creating a secret containing a private key](https://kubernetes.io/docs/concepts/configuration/secret/#use-case-pod-with-ssh-keys).
### Repository Credentials
>v1.1
> Earlier than v1.4
If you want to use the same credentials for multiple repositories, you can use `repository.credentials`:
@@ -205,7 +206,7 @@ Argo CD will only use the credentials if you omit `usernameSecret`, `passwordSec
A credential may be match if it's URL is the prefix of the repository's URL. The means that credentials may match, e.g in the above example both [https://github.com/argoproj](https://github.com/argoproj) and [https://github.com](https://github.com) would match. Argo CD selects the first one that matches.
!!! tip
Order your credentials with the most specific at the top and the least specific at the bottom.
Order your credentials with the most specific at the top and the least specific at the bottom.
A complete example.
@@ -258,6 +259,55 @@ data:
key: sshPrivateKey
```
> v1.4 or later
If you want to use the same credentials for multiple repositories, you can use `repository.credentials` to configure credential templates. Credential templates can carry the same credentials information as repositories.
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
labels:
app.kubernetes.io/name: argocd-cm
app.kubernetes.io/part-of: argocd
data:
repositories: |
- url: https://github.com/argoproj/private-repo
- url: https://github.com/argoproj/other-private-repo
repository.credentials: |
- url: https://github.com/argoproj
passwordSecret:
name: my-secret
key: password
usernameSecret:
name: my-secret
key: username
```
In the above example, every repository accessed via HTTPS whose URL is prefixed with `https://github.com/argoproj` would use a username stored in the key `username` and a password stored in the key `password` of the secret `my-secret` for connecting to Git.
In order for ArgoCD to use a credential template for any given repository, the following conditions must be met:
* The repository must either not be configured at all, or if configured, must not contain any credential information (i.e. contain none of `sshPrivateKeySecret`, `usernameSecret`, `passwordSecret` )
* The URL configured for a credential template (e.g. `https://github.com/argoproj`) must match as prefix for the repository URL (e.g. `https://github.com/argoproj/argocd-example-apps`).
!!! note
Matching credential template URL prefixes is done on a _best match_ effort, so the longest (best) match will take precedence. The order of definition is not important, as opposed to pre v1.4 configuration.
The following keys are valid to refer to credential secrets:
#### SSH repositories
* `sshPrivateKeySecret` refers to a secret where an SSH private key is stored for accessing the repositories
#### HTTPS repositories
* `usernameSecret` and `passwordSecret` refer to secrets where username and/or password are stored for accessing the repositories
* `tlsClientCertData` and `tlsClientCertKey` refer to secrets where a TLS client certificate (`tlsClientCertData`) and the corresponding private key `tlsClientCertKey` are stored for accessing the repositories
### Repositories using self-signed TLS certificates (or are signed by custom CA)
> v1.2 or later
@@ -317,7 +367,7 @@ data:
```
!!! note
The `argocd-tls-certs-cm` ConfigMap will be mounted as a volume at the mount path `/app/config/tls` in the pods of `argocd-server` and `argocd-repo-server`. It will create files for each data key in the mount path directory, so above example would leave the file `/app/config/tls/server.example.com`, which contains the certificate data. It might take a while for changes in the ConfigMap to be reflected in your pods, depending on your Kubernetes configuration.
The `argocd-tls-certs-cm` ConfigMap will be mounted as a volume at the mount path `/app/config/tls` in the pods of `argocd-server` and `argocd-repo-server`. It will create files for each data key in the mount path directory, so above example would leave the file `/app/config/tls/server.example.com`, which contains the certificate data. It might take a while for changes in the ConfigMap to be reflected in your pods, depending on your Kubernetes configuration.
### SSH known host public keys
@@ -346,8 +396,7 @@ data:
```
!!! note
The `argocd-ssh-known-hosts-cm` ConfigMap will be mounted as a volume at the mount path `/app/config/ssh` in the pods of `argocd-server` and `argocd-repo-server`. It will create a file `ssh_known_hosts` in that directory, which contains the SSH known hosts data used by ArgoCD for connecting to Git repositories via SSH. It might take a while for changes in the ConfigMap to be reflected in your pods, depending on your Kubernetes configuration.
The `argocd-ssh-known-hosts-cm` ConfigMap will be mounted as a volume at the mount path `/app/config/ssh` in the pods of `argocd-server` and `argocd-repo-server`. It will create a file `ssh_known_hosts` in that directory, which contains the SSH known hosts data used by ArgoCD for connecting to Git repositories via SSH. It might take a while for changes in the ConfigMap to be reflected in your pods, depending on your Kubernetes configuration.
## Clusters
@@ -386,7 +435,6 @@ tlsClientConfig:
serverName: string
```
Cluster secret example:
```yaml
@@ -468,8 +516,8 @@ Resources can be excluded from discovery and sync so that ArgoCD is unaware of t
To configure this, edit the `argcd-cm` config map:
```
kubectl edit configmap argocd-cm -n argocdconfigmap/argocd-cm edited
```shell
kubectl edit configmap argocd-cm -n argocd
```
Add `resource.exclusions`, e.g.:
@@ -489,9 +537,9 @@ kind: ConfigMap
The `resource.exclusions` node is a list of objects. Each object can have:
- `apiGroups` A list of globs to match the API group.
- `kinds` A list of kinds to match. Can be "*" to match all.
- `cluster` A list of globs to match the cluster.
* `apiGroups` A list of globs to match the API group.
* `kinds` A list of kinds to match. Can be "*" to match all.
* `cluster` A list of globs to match the cluster.
If all three match, then the resource is ignored.
@@ -503,13 +551,13 @@ Notes:
## SSO & RBAC
* SSO configuration details: [SSO](sso.md)
* RBAC configuration details: [RBAC](rbac.md)
* SSO configuration details: [SSO](./sso/index.md)
* RBAC configuration details: [RBAC](./rbac.md)
## Manage Argo CD Using Argo CD
Argo CD is able to manage itself since all settings are represented by Kubernetes manifests. The suggested way is to create [Kustomize](https://github.com/kubernetes-sigs/kustomize)
based application which uses base Argo CD manifests from [https://github.com/argoproj/argo-cd] and apply required changes on top.
based application which uses base Argo CD manifests from [https://github.com/argoproj/argo-cd](https://github.com/argoproj/argo-cd/tree/stable/manifests) and apply required changes on top.
Example of `kustomization.yaml`:
@@ -527,8 +575,8 @@ patchesStrategicMerge:
- overlays/argo-cd-cm.yaml
```
The live example of self managed Argo CD config is available at https://cd.apps.argoproj.io and with configuration
The live example of self managed Argo CD config is available at [https://cd.apps.argoproj.io](https://cd.apps.argoproj.io) and with configuration
stored at [argoproj/argoproj-deployments](https://github.com/argoproj/argoproj-deployments/tree/master/argocd).
!!! note
You will need to sign-in using your github account to get access to https://cd.apps.argoproj.io
You will need to sign-in using your github account to get access to [https://cd.apps.argoproj.io](https://cd.apps.argoproj.io)

View File

@@ -23,3 +23,6 @@ Import from a backup:
```bash
docker run -v ~/.kube:/home/argocd/.kube --rm argoproj/argocd:$VERSION argocd-util import - < backup.yaml
```
!!! note
If you are running Argo CD on a namespace different than default remember to pass the namespace parameter (-n <namespace>). 'argocd-util export' will not fail if you run it in the wrong namespace.

View File

@@ -30,12 +30,12 @@ There are two ways to configure a custom health check. The next two sections des
### Way 1. Define a Custom Health Check in `argocd-cm` ConfigMap
Custom health checks can be defined in `resource.customizations` field of `argocd-cm`. Following example demonstrates a health check for `certmanager.k8s.io/Certificate`.
Custom health checks can be defined in `resource.customizations` field of `argocd-cm`. Following example demonstrates a health check for `cert-manager.io/Certificate`.
```yaml
data:
resource.customizations: |
certmanager.k8s.io/Certificate:
cert-manager.io/Certificate:
health.lua: |
hs = {}
if obj.status ~= nil then

View File

@@ -29,6 +29,8 @@ or repositories has a lot of files. To avoid this problem mount persistent volum
* `argocd-repo-server` `git ls-remote` to resolve ambiguous revision such as `HEAD`, branch or tag name. This operation is happening pretty frequently
and might fail. To avoid failed syncs use `ARGOCD_GIT_ATTEMPTS_COUNT` environment variable to retry failed requests.
* `argocd-repo-server` Every 3m (by default) Argo CD checks for changes to the app manifests. Argo CD assumes by default that manifests only change when the repo changes, so it caches generated manifests (for 24h by default). With Kustomize remote bases, or Helm patch releases, the manifests can change even though the repo has not changed. By reducing the cache time, you can get the changes without waiting for 24h. Use `--repo-cache-expiration duration`, and we'd suggest in low volume environments you try '1h'. Bear in mind this will negate the benefit of caching if set too low.
**metrics:**
* `argocd_git_request_total` - Number of git requests. The metric provides two tags: `repo` - Git repo URL; `request_type` - `ls-remote` or `fetch`.
@@ -56,6 +58,8 @@ performance. For performance reasons controller monitors and caches only preferr
preferred version into a version of the resource stored in Git. If `kubectl convert` fails because conversion is not supported than controller fallback to Kubernetes API query which slows down
reconciliation. In this case advice user-preferred resource version in Git.
* The controller polls Git every 3m by default. You can increase this duration using `--app-resync seconds` to reduce polling.
**metrics**
* `argocd_app_reconcile` - reports application reconciliation duration. Can be used to build reconciliation duration heat map to get high-level reconciliation performance picture.

View File

@@ -45,6 +45,38 @@ and responds appropriately. Note that the `nginx.ingress.kubernetes.io/ssl-passt
requires that the `--enable-ssl-passthrough` flag be added to the command line arguments to
`nginx-ingress-controller`.
#### SSL-Passthrough with cert-manager and Let's Encrypt
```yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: argocd-server-ingress
namespace: argocd
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: "true"
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
# If you encounter a redirect loop or are getting a 307 response code
# then you need to force the nginx ingress to connect to the backend using HTTPS.
#
# nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
rules:
- host: argocd.example.com
http:
paths:
- backend:
serviceName: argocd-server
servicePort: https
path: /
tls:
- hosts:
- argocd.example.com
secretName: argocd-secret # do not change, this is provided by Argo CD
```
### Option 2: Multiple Ingress Objects And Hosts
Since ingress-nginx Ingress supports only a single protocol per Ingress object, an alternative
@@ -71,7 +103,7 @@ spec:
tls:
- hosts:
- argocd.example.com
secretName: argocd-secret
secretName: argocd-secret # do not change, this is provided by Argo CD
```
gRPC Ingress:
@@ -94,7 +126,7 @@ spec:
tls:
- hosts:
- grpc.argocd.example.com
secretName: argocd-secret
secretName: argocd-secret # do not change, this is provided by Argo CD
```
The API server should then be run with TLS disabled. Edit the `argocd-server` deployment to add the

View File

@@ -43,7 +43,7 @@ metadata:
spec:
selector:
matchLabels:
app.kubernetes.io/name: argocd-server
app.kubernetes.io/name: argocd-server-metrics
endpoints:
- port: metrics
```
@@ -65,6 +65,7 @@ spec:
## Dashboards
You can find an example Grafana dashboard [here](https://github.com/argoproj/argo-cd/blob/master/examples/dashboard.json)
You can find an example Grafana dashboard [here](https://github.com/argoproj/argo-cd/blob/master/examples/dashboard.json) or check demo instance
[dashboard](https://grafana.apps.argoproj.io).
![dashboard](../assets/dashboard.jpg)

View File

@@ -0,0 +1,14 @@
# Notifications
The notifications support is not bundled into the Argo CD itself. Instead of reinventing the wheel and implementing opinionated notifications system Argo CD leverages integrations
with the third-party notification system. Following integrations are recommended:
* To monitor Argo CD performance or health state of managed applications use [Prometheus Metrics](./metrics.md) in combination with [Grafana](https://grafana.com/),
[Alertmanager](https://prometheus.io/docs/alerting/alertmanager/).
* To notify the end-users of Argo CD about events like application upgrades, user errors in application definition, etc use one of the following projects:
* [ArgoCD Notifications](https://github.com/argoproj-labs/argocd-notifications) - Argo CD specific notification system that continuously monitors Argo CD applications
and aims to integrate with various notification services such as Slack, SMTP, Telegram, Discord, etc.
* [Argo Kube Notifier](https://github.com/argoproj-labs/argo-kube-notifier) - generic Kubernetes resource controller that allows monitoring any Kubernetes resource and sends a
notification when the configured rule is met.
* [Kube Watch](https://github.com/bitnami-labs/kubewatch) - a Kubernetes watcher that could publishes notification to Slack/hipchat/mattermost/flock channels. It watches the
cluster for resource changes and notifies them through webhooks.

View File

@@ -1,27 +1,45 @@
# RBAC
## Overview
# RBAC Configuration
The RBAC feature enables restriction of access to Argo CD resources. Argo CD does not have its own
user management system and has only one built-in user `admin`. The `admin` user is a superuser and
it has unrestricted access to the system. RBAC requires [SSO configuration](./sso.md). Once SSO is
it has unrestricted access to the system. RBAC requires [SSO configuration](sso/index.md). Once SSO is
configured, additional RBAC roles can be defined, and SSO groups can man be mapped to roles.
## Configure RBAC
## Basic Built-in Roles
RBAC configuration allows defining roles and groups. Argo CD has two pre-defined roles:
Argo CD has two pre-defined roles but RBAC configuration allows defining roles and groups (see below).
* `role:readonly` - read-only access to all resources
* `role:admin` - unrestricted access to all resources
These role definitions can be seen in [builtin-policy.csv](https://github.com/argoproj/argo-cd/blob/master/assets/builtin-policy.csv)
These default built-in role definitions can be seen in [builtin-policy.csv](https://github.com/argoproj/argo-cd/blob/master/assets/builtin-policy.csv)
### RBAC Permission Structure
Breaking down the permissions definition differs slightly between applications and every other resource type in Argo CD.
* All resources *except* applications permissions (see next bullet):
`p, <role/user/group>, <resource>, <action>, <object>`
* Applications (which belong to an AppProject):
`p, <role/user/group>, <resource>, <action>, <appproject>/<object>`
### RBAC Resources and Actions
Resources: `clusters`, `projects`, `applications`, `repositories`, `certificates`
Actions: `get`, `create`, `update`, `delete`, `sync`, `override`, `action`
## Tying It All Together
Additional roles and groups can be configured in `argocd-rbac-cm` ConfigMap. The example below
configures a custom role, named `org-admin`. The role is assigned to any user which belongs to
`your-github-org:your-team` group. All other users get the default policy of `role:readonly`,
which cannot modify Argo CD settings.
*ConfigMap `argocd-rbac-cm` example:*
*ArgoCD ConfigMap `argocd-rbac-cm` Example:*
```yaml
apiVersion: v1
@@ -41,8 +59,24 @@ data:
g, your-github-org:your-team, role:org-admin
```
----
Another `policy.csv` example might look as follows:
```csv
p, role:staging-db-admins, applications, create, staging-db-admins/*, allow
p, role:staging-db-admins, applications, delete, staging-db-admins/*, allow
p, role:staging-db-admins, applications, get, staging-db-admins/*, allow
p, role:staging-db-admins, applications, override, staging-db-admins/*, allow
p, role:staging-db-admins, applications, sync, staging-db-admins/*, allow
p, role:staging-db-admins, applications, update, staging-db-admins/*, allow
p, role:staging-db-admins, projects, get, staging-db-admins, allow
g, db-admins, role:staging-db-admins
```
This example defines a *role* called `staging-db-admins` with *seven permissions* that allow that role to perform the *actions* (`create`/`delete`/`get`/`override`/`sync`/`update` applications, and `get` appprojects) against `*` (all) objects in the `staging-db-admins` Argo CD AppProject.
## Anonymous Access
The anonymous access to Argo CD can be enabled using `users.anonymous.enabled` field in `argocd-cm` (see [./argocd-cm.yaml](argocd-cm.yaml)).
The anonymous users get default role permissions specified by `policy.default` in `argocd-rbac-cm.yaml. For read-only access you'll want `policy.default: role:readonly` as above
The anonymous access to Argo CD can be enabled using `users.anonymous.enabled` field in `argocd-cm` (see [argocd-cm.yaml](argocd-cm.yaml)).
The anonymous users get default role permissions specified by `policy.default` in `argocd-rbac-cm.yaml`. For read-only access you'll want `policy.default: role:readonly` as above

View File

@@ -0,0 +1,14 @@
# Secret Management
Argo CD is un-opinionated about how secrets are managed. There's many ways to do it and there's no one-size-fits-all solution. Here's some ways people are doing GitOps secrets:
* [Bitnami Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets)
* [Godaddy Kubernetes External Secrets](https://github.com/godaddy/kubernetes-external-secrets)
* [Hashicorp Vault](https://www.vaultproject.io)
* [Helm Secrets](https://github.com/futuresimple/helm-secrets)
* [Kustomize secret generator plugins](https://github.com/kubernetes-sigs/kustomize/blob/fd7a353df6cece4629b8e8ad56b71e30636f38fc/examples/kvSourceGoPlugin.md#secret-values-from-anywhere)
* [aws-secret-operator](https://github.com/mumoshu/aws-secret-operator)
* [KSOPS](https://github.com/viaduct-ai/kustomize-sops#argo-cd-integration)
For discussion, see [#1364](https://github.com/argoproj/argo-cd/issues/1364)

View File

@@ -4,7 +4,6 @@ Argo CD has undergone rigourous internal security reviews and penetration testin
compliance](https://www.pcisecuritystandards.org) requirements. The following are some security
topics and implementation details of Argo CD.
## Authentication
Authentication to Argo CD API server is performed exclusively using [JSON Web Tokens](https://jwt.io)
@@ -27,21 +26,18 @@ in one of the following ways:
JWTs have a configurable expiration and can be immediately revoked by deleting the JWT reference
ID from the project role.
## Authorization
Authorization is performed by iterating the list of group membership in a user's JWT groups claims,
and comparing each group against the roles/rules in the [RBAC](rbac.md) policy. Any matched rule
and comparing each group against the roles/rules in the [RBAC](../rbac) policy. Any matched rule
permits access to the API request.
## TLS
All network communication is performed over TLS including service-to-service communication between
the three components (argocd-server, argocd-repo-server, argocd-application-controller). The Argo CD
API server can enforce the use of TLS 1.2 using the flag: `--tlsminversion 1.2`.
## Sensitive Information
### Secrets
@@ -79,15 +75,16 @@ cluster, and remove the cluster entry from Argo CD:
```bash
# run using a kubeconfig for the externally managed cluster
kubectl delete sa argocd-manager -n kube-system
kubectl delete sa argocd-manager -n kube-system
kubectl delete clusterrole argocd-manager-role
kubectl delete clusterrolebinding argocd-manager-role-binding
argocd cluster rm https://your-kubernetes-cluster-addr
```
<!-- markdownlint-disable MD027 -->
> NOTE: for AWS EKS clusters, [aws-iam-authenticator](https://github.com/kubernetes-sigs/aws-iam-authenticator)
is used to authenticate to the external cluster, which uses IAM roles in lieu of locally stored
tokens, so token rotation is not needed, and revokation is handled through IAM.
<!-- markdownlint-enable MD027 -->
## Cluster RBAC
@@ -110,7 +107,7 @@ To fine-tune privileges of externally managed clusters, edit the ClusterRole of
kubectl edit clusterrole argocd-manager-role
```
To fine-tune privileges which Argo CD has against its own cluster (i.e. https://kubernetes.default.svc),
To fine-tune privileges which Argo CD has against its own cluster (i.e. `https://kubernetes.default.svc`),
edit the following cluster roles where Argo CD is running in:
```bash
@@ -120,7 +117,7 @@ kubectl edit clusterrole argocd-application-controller
```
!!! tip
If you want to deny ArgoCD access to a kind of resource then add it as an [excluded resource](declarative-setup.md#resource-exclusion).
If you want to deny ArgoCD access to a kind of resource then add it as an [excluded resource](declarative-setup.md#resource-exclusion).
## Auditing
@@ -150,7 +147,6 @@ These events can be then be persisted for longer periods of time using other too
[Event Exporter](https://github.com/GoogleCloudPlatform/k8s-stackdriver/tree/master/event-exporter) or
[Event Router](https://github.com/heptiolabs/eventrouter).
## WebHook Payloads
Payloads from webhook events are considered untrusted. Argo CD only examines the payload to infer
@@ -158,7 +154,6 @@ the involved applications of the webhook event (e.g. which repo was modified), t
the related application for reconciliation. This refresh is the same refresh which occurs regularly
at three minute intervals, just fast-tracked by the webhook event.
## Reporting Vulnerabilities
Please report security vulnerabilities by e-mailing:

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