Compare commits

..

184 Commits

Author SHA1 Message Date
Alexander Matyushentsev
f45ef020d6 fix cosign (#17656) (#17740)
Signed-off-by: Justin Marquis <justin@akuity.io>
Co-authored-by: Justin Marquis <76892343+34fathombelow@users.noreply.github.com>
2024-04-04 15:39:21 -07:00
github-actions[bot]
795bd9d7be Bump version to 2.7.18 (#17738)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: alexmt <alexmt@users.noreply.github.com>
2024-04-04 14:19:44 -07:00
Alexander Matyushentsev
ff439f6c60 fix: fix calculating patch for respect ignore diff feature (#17693) (#17733)
* test: unit test for respectIgnoreDifferences bug



* test: simplify unit test



* fix: fix calculating patch for respect ignore diff feature



---------

Signed-off-by: Jesse Suen <jesse@akuity.io>
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Co-authored-by: Jesse Suen <jesse@akuity.io>
2024-04-04 11:59:58 -07:00
github-actions[bot]
13d723df9c Bump version to 2.7.17 (#17080)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2024-02-02 13:12:40 -05:00
gcp-cherry-pick-bot[bot]
ceb0cada47 fix(ui): Badge for apps in any namespace (#16739) (#17010)
Signed-off-by: sshenoy6 <sonamkaup_shenoy@intuit.com>
Co-authored-by: Sonam <49382298+sonamkshenoy@users.noreply.github.com>
Co-authored-by: sshenoy6 <sonamkaup_shenoy@intuit.com>
2024-01-26 15:06:13 -05:00
Michael Crenshaw
e2da7ee34f fix(server): allow disabling content-type check (#16959) (#16979)
* fix(server): allow disabling content-type check



* fix spacing



---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-01-24 18:06:56 -05:00
github-actions[bot]
93680ca853 Bump version to 2.7.16 (#16940)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2024-01-19 14:09:12 -05:00
gcp-cherry-pick-bot[bot]
460c4cdef2 fix(ui): set content-type for certain UI requests (#16923) (#16930) (#16935)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-01-19 11:49:08 -05:00
Michael Crenshaw
fa0c504e4a chore(deps): bump github.com/go-git/go-git/v5 from 5.6.1 to 5.11.0 (#16914)
* chore(deps): bump github.com/go-git/go-git/v5 from 5.6.1 to 5.11.0

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

* tidy

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

---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-01-18 10:55:00 -05:00
Alexander Matyushentsev
13fe3ca589 fix: enforce content type header for API requests (#16860) (Cherry-pick release-2.7) (#16880)
* chore: skip server certificate verification for http requests in e2e tests (#15733)

* chore: skip verifying server certificate for http requests in e2e tests

Signed-off-by: Chris Fry <christopherfry@google.com>

* chore: skip certificate verification only for remote tests

Signed-off-by: Chris Fry <christopherfry@google.com>

---------

Signed-off-by: Chris Fry <christopherfry@google.com>

* fix: enforce content type header for API requests (#16860)

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>

---------

Signed-off-by: Chris Fry <christopherfry@google.com>
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Co-authored-by: Christopher Fry <ChristopherFry2008@gmail.com>
2024-01-16 13:10:43 -08:00
Dan Garfield
1b69ce6e0b fix(grpcproxy): add GRPCKeepAliveEnforcementMinimum (#16567)
the absence of the setting potentially causes ENHANCE_YOUR_CALM

Signed-off-by: phanama <yudiandreanp@gmail.com>
Co-authored-by: phanama <yudiandreanp@gmail.com>
2023-12-07 22:11:30 +05:30
gcp-cherry-pick-bot[bot]
c3f55ea04e docs: Fix format issue in rbac.md (#16521) (#16540)
Wrongly placed horizontal line (`----`) was formatting code-block as a header. Fixed it with a necessary line break

Signed-off-by: Elouan Keryell-Even <elouan.keryell@gmail.com>
Co-authored-by: Elouan Keryell-Even <elouan.keryell@gmail.com>
2023-12-06 11:47:51 -05:00
Michael Crenshaw
66a8905181 chore(deps): bump cosign-installer from 3.0.1 to 3.2.0 (#16537)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-12-05 16:09:41 -05:00
gcp-cherry-pick-bot[bot]
8da0d9e727 fix: Updated docs about using a slash in ignoreDifferences (#15144) (#15169)
Signed-off-by: Christian Hernandez <christian@chernand.io>
Co-authored-by: Christian Hernandez <christianh814@users.noreply.github.com>
2023-11-14 10:59:42 -05:00
github-actions[bot]
c9ae6a10c3 Bump version to 2.7.15 (#16224)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-11-02 15:48:35 -04:00
Shyukri Shyukriev
5e71872166 chore: Upgrade Redis to redis:7.0.14 (#16164) (#16221)
* chore: Upgrade Redis to redis:7.0.14 (#16164)

Fixes https://github.com/argoproj/argo-cd/issues/15448

Signed-off-by: Shyukri Shyukriev <shukera@gmail.com>
Co-authored-by: Shyukri Shyukriev <shyukri.shyukriev@mariadb.com>
(cherry picked from commit 570c24e38e)
Signed-off-by: Shyukri Shyukriev <shukera@gmail.com>

* chore: Redis HA securityContext

Signed-off-by: Shyukri Shyukriev <shukera@gmail.com>

---------

Signed-off-by: Shyukri Shyukriev <shukera@gmail.com>
Co-authored-by: Shyukri Shyukriev <shyukri.shyukriev@mariadb.com>
2023-11-02 15:32:19 -04:00
gcp-cherry-pick-bot[bot]
5d144112ec docs(cmp): fix CMP param getter example (#16077) (#16190) (#16199)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-11-01 10:22:40 -04:00
gcp-cherry-pick-bot[bot]
6ab8512998 fix: argocd notification controller app cluster permission issue (#16057) (#16161)
* if applicationNamespaces is not provided as input parameter, then use namespaced appClient



* fix go lint error



---------

Signed-off-by: May Zhang <may_zhang@intuit.com>
Co-authored-by: May Zhang <may_zhang@intuit.com>
2023-10-30 16:42:53 -04:00
Michael Crenshaw
e74c6ca460 fix(notifications): Allow notifications controller to notify on all namespaces (cherry-pick 2.7) (#15856)
* fix(notifications): Allow notifications controller to notify on all namespaces (#15702)

* Allow notifications controller to notify on all namespaces

This adds functionality to the notifications controller to be notified
of and send notifications for applications in any namespace. The
namespaces to watch are controlled by the same --application-namespaces
and ARGOCD_APPLICATION_NAMESPACES variables as in the application
controller.

Signed-off-by: Nikolas Skoufis <nskoufis@seek.com.au>

* Add SEEK to users.md

Signed-off-by: Nikolas Skoufis <nskoufis@seek.com.au>

* Remove unused fields

Signed-off-by: Nikolas Skoufis <nskoufis@seek.com.au>

* Revert changes to Procfile

Signed-off-by: Nik Skoufis <n.skoufis@gmail.com>

* Fix unit tests

Signed-off-by: Nikolas Skoufis <nskoufis@seek.com.au>

* - add argocd namespaces environment variable to notifications controller

Signed-off-by: Stewart Thomson <sthomson@wynshop.com>

* - add example cluster role rbac

Signed-off-by: Stewart Thomson <sthomson@wynshop.com>

* - only look for projects in the controller's namespace (argocd by default)

Signed-off-by: Stewart Thomson <sthomson@wynshop.com>

* - update base manifest

Signed-off-by: Stewart Thomson <sthomson@wynshop.com>

* - skip app processing in notification controller

Signed-off-by: Stewart Thomson <sthomson@wynshop.com>

* added unit test and updated doc

Signed-off-by: May Zhang <may_zhang@intuit.com>

* added unit test and updated doc

Signed-off-by: May Zhang <may_zhang@intuit.com>

* updated examples/k8s-rbac/argocd-server-applications/kustomization.yaml's resources

Signed-off-by: May Zhang <may_zhang@intuit.com>

---------

Signed-off-by: Nikolas Skoufis <nskoufis@seek.com.au>
Signed-off-by: Nik Skoufis <n.skoufis@gmail.com>
Signed-off-by: Stewart Thomson <sthomson@wynshop.com>
Signed-off-by: May Zhang <may_zhang@intuit.com>
Co-authored-by: Nikolas Skoufis <nskoufis@seek.com.au>
Co-authored-by: Nik Skoufis <n.skoufis@gmail.com>
Co-authored-by: Stewart Thomson <sthomson@wynshop.com>

undo unnecessary manifest changes

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

undo unnecessary manifest changes

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

* revert unnecessary changes

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

---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: May Zhang <may_zhang@intuit.com>
2023-10-26 11:33:16 -04:00
Abhishek Veeramalla
007dc54905 chore(deps): upgrade Go version to 1.20 (#16100)
Signed-off-by: iam-veeramalla <abhishek.veeramalla@gmail.com>
2023-10-25 12:13:08 -04:00
gcp-cherry-pick-bot[bot]
0b4d410a51 fix: helm set parameter to allow passing list parameters (#15978) (#15995)
Signed-off-by: kkk777-7 <kota.kimura0725@gmail.com>
Co-authored-by: Kota Kimura <86363983+kkk777-7@users.noreply.github.com>
2023-10-16 16:47:49 -04:00
gcp-cherry-pick-bot[bot]
1199f59410 fix(appset): performProgressiveSyncs only when the applicationset is using it (#15299) (#15992)
Signed-off-by: Eric Blackburn <eblackburn@indeed.com>
Co-authored-by: ericblackburn <eblackburn@indeed.com>
2023-10-16 16:44:59 -04:00
jannfis
d7b31248ad chore(deps): Bump gRPC to 1.58.3 and x/net to v0.17.0 [release-2.7] (#15918)
* chore(deps): Bump gRPC to 1.58.3 and x/net to v0.17.0

Signed-off-by: jannfis <jann@mistrust.net>

* go mod tidy

Signed-off-by: jannfis <jann@mistrust.net>

---------

Signed-off-by: jannfis <jann@mistrust.net>
2023-10-13 13:08:52 -04:00
gcp-cherry-pick-bot[bot]
86b382798b docs: 'action' RBAC example for Kind without group (#15589) (#15598)
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-09-20 11:14:28 -04:00
gcp-cherry-pick-bot[bot]
485731d605 fix(#12862): Update FlinkDeployment health check to support Flink v1.x (#15065) (#15559)
Signed-off-by: Dylan Slavin <dylan@sla.vin>
Co-authored-by: Dylan Slavin <dylan@sla.vin>
2023-09-19 09:06:00 -04:00
gcp-cherry-pick-bot[bot]
0ef9f0986f fix(repo-server): avoid fetching commit sha for multisource applications (#15037) (#15067) (#15556)
* Rebase Signed-off-by: ozlevka-work <lev@ozeryansky.com>



* Remove test file Signed-off-by: ozlevka-work <lev@ozeryansky.com>



* Go linter fmt Signed-off-by: ozlevka-work <lev@ozeryansky.com>



* Additional linter fmt Signed-off-by: ozlevka-work <lev@ozeryansky.com>




* Add unit test for changes Signed-off-by: Lev <lozeryan@akami.com>



* Update reposerver/repository/repository.go





* Trigger CI



---------

Signed-off-by: Lev <lozeryan@akami.com>
Signed-off-by: Lev <lev@ozeryansky.com>
Signed-off-by: Lev Ozeryansky <lozeryan@akamai.com>
Co-authored-by: Lev Ozeryansky <lozeryan@akamai.com>
Co-authored-by: Lev <lozeryan@akami.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-09-19 09:05:02 -04:00
gcp-cherry-pick-bot[bot]
51b1d50efd docs: Fix docs for destinations in AppProjects (#15153) (#15182) (#15539)
Signed-off-by: Andreas Lindhé <andreas@lindhe.io>
Co-authored-by: Andreas Lindhé <lindhe@users.noreply.github.com>
2023-09-18 09:13:37 -04:00
gcp-cherry-pick-bot[bot]
3cc00d2c77 fix: make WatchResourceTree use namespaced cache key (#15258) (#15523)
* fix: make WatchResourceTree use namespaced application cache key



* chore: add PGS to USERS.md



---------

Signed-off-by: Torbjørn Fjørtoft <torbjorn.fjortoft@pgs.com>
Co-authored-by: Torbjørn Fjørtoft <torbjorn.fjortoft@gmail.com>
2023-09-15 12:00:31 -04:00
jannfis
257be07d68 fix: Allow retrieving badges in other namespaces (#15468) (#15483)
Signed-off-by: jannfis <jann@mistrust.net>
2023-09-13 12:57:18 -04:00
jannfis
733bcab30d fix(server): not need send application if it is not under enabled namespaces (#14479) (#15474)
* fix: not need send application if it is not under enabled namespaces

* fix condition

* feat: Move application is permitted outside of watch function and cover with unit tests

* feat: Move application is permitted outside of watch function and cover with unit tests

Co-authored-by: pasha-codefresh <pavel@codefresh.io>
2023-09-13 01:56:36 +03:00
gcp-cherry-pick-bot[bot]
67734db60c fix: handle annotations for resources with ':' in the name (#15101) (#15380) (#15413)
* fix: handle annotations for resources with ':' in the name



* fix: switch to using splitN for handling annotation splitting



* fix: check len(parts) !=3



* Retrigger CI pipeline



---------

Signed-off-by: Oreon Lothamer <oreon.lothamer@softrams.com>
Co-authored-by: Oreon Lothamer <73498677+oreonl@users.noreply.github.com>
2023-09-08 13:55:53 -04:00
github-actions[bot]
a40c95a638 Bump version to 2.7.14 (#15406)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: jannfis <jannfis@users.noreply.github.com>
2023-09-07 12:09:09 -04:00
Alexander Matyushentsev
582a3f4816 Merge pull request from GHSA-fwr2-64vr-xv9m
* fix: prevent seeing/editing 'kubectl.kubernetes.io/last-applied-configuration' cluster annotation

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>

* Update util/db/cluster_test.go

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

---------

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-09-07 10:14:57 -04:00
pasha-codefresh
33e32d01da Merge pull request from GHSA-g687-f2gx-6wm8
* feat: use untar with limiter

Signed-off-by: pashakostohrys <pavel@codefresh.io>

* feat: use untar with limiter

Signed-off-by: pashakostohrys <pavel@codefresh.io>

---------

Signed-off-by: pashakostohrys <pavel@codefresh.io>
2023-09-07 10:12:15 -04:00
github-actions[bot]
14b3de9a0e Bump version to 2.7.13 (#15231)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-08-24 16:04:54 -04:00
gcp-cherry-pick-bot[bot]
a44a121da7 docs: document sourceNamespaces field (#15195) (#15213)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-08-24 11:31:26 -04:00
gcp-cherry-pick-bot[bot]
ed6186b75a chore: add example jq path expression (#15130) (#15210)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-08-24 11:30:14 -04:00
gcp-cherry-pick-bot[bot]
0044e79b38 fix: requeue ApplicationSet if there are validation errors (#14429) (#15207)
Signed-off-by: Chetan Banavikalmutt <chetanrns1997@gmail.com>
Co-authored-by: Chetan Banavikalmutt <chetanrns1997@gmail.com>
2023-08-24 11:29:22 -04:00
gcp-cherry-pick-bot[bot]
514ff2c525 fix(appset): bitbucket server scm provider EOF on empty repo (#14411) (#15204)
* fix bitbucket server scm provider EOF on empty repo default branch check



* add unit test for bitbucketServer empty repo



* check for EOF explicitly



---------

Signed-off-by: Jedrzej Kotkowski <jedrzejk143@gmail.com>
Co-authored-by: jjsiv <96917147+jjsiv@users.noreply.github.com>
2023-08-24 11:28:39 -04:00
gcp-cherry-pick-bot[bot]
5a1f5c653b fix(health): spec.executor.instances is Optional, Support a flexible number of executors (#11877) (#15201)
Support a flexible number of executors. For example with a mounted ConfigMap inside the Spark-Operator.

Signed-off-by: Philipp Dallig <philipp.dallig@gmail.com>
Co-authored-by: Philipp Dallig <philipp.dallig@gmail.com>
2023-08-24 11:28:05 -04:00
Marie Katrine Ekeberg
ece9d1b9f7 fix: Disable keep alives for helm https connections (#13849)
Signed-off-by: Marie Katrine Ekeberg <marie.ekeberg@tietoevry.com>
2023-08-23 16:13:06 -04:00
Justin Marquis
685040cf82 fix: upgrade slsa-github-generator (#15173)
Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
2023-08-22 18:17:50 -04:00
gcp-cherry-pick-bot[bot]
2333b048f9 fix(ui): code lint (#15150) (#15161)
Signed-off-by: ebuildy <ebuildy@gmail.com>
Co-authored-by: Thomas Decaux <ebuildy@gmail.com>
2023-08-22 14:20:53 -04:00
gcp-cherry-pick-bot[bot]
7a353a55c8 fix: windows build (#15154) (#15157)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-08-22 13:08:32 -04:00
github-actions[bot]
521c99e05f Bump version to 2.7.12 (#15140)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-08-21 16:29:04 -04:00
pasha-codefresh
003d6d1da5 Merge pull request from GHSA-c8xw-vjgf-94hr
Signed-off-by: pashakostohrys <pavel@codefresh.io>
2023-08-21 16:15:09 -04:00
gcp-cherry-pick-bot[bot]
7e3572f373 docs(progressive syncs): specify which ConfigMap to use (#15119) (#15134)
Signed-off-by: Gaël Jourdan-Weil <gjourdanweil@gmail.com>
Co-authored-by: Gaël Jourdan-Weil <gjourdanweil@gmail.com>
2023-08-21 16:13:48 -04:00
gcp-cherry-pick-bot[bot]
580966ce85 docs: ✏️ fix typo on configmap name for private certs (#9596) (#15132)
Signed-off-by: Gaël Jourdan-Weil <gael.jourdan-weil@kelkoogroup.com>
Co-authored-by: Gaël Jourdan-Weil <gael.jourdan-weil@kelkoogroup.com>
2023-08-21 16:13:19 -04:00
gcp-cherry-pick-bot[bot]
693ea664ff docs: add docs for various annotations and labels (#14020) (#15113)
* docs: add docs for various annotations



* more info



* more docs



---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-08-18 11:22:49 -04:00
gcp-cherry-pick-bot[bot]
2ff6e5178e docs: kubectl to synchronize argocd apps (#14881) (#15086)
We can use kubectl to synchronize argocd applications the same way we can use
the argocd cli or ui, however there's no documentation.

This PR adds documentation for kubectl.

Signed-off-by: Jordi Grant Esteve <jgrant.esteve@gmail.com>
Co-authored-by: selaci <selaci@users.noreply.github.com>
2023-08-18 11:19:53 -04:00
gcp-cherry-pick-bot[bot]
6e6d6e01e2 docs: fix typo (#15083) (#15089)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-08-18 11:18:44 -04:00
gcp-cherry-pick-bot[bot]
af202bc7ef docs: document permitOnlyProjectScopedClusters field (#15076) (#15081)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-08-16 15:44:13 -04:00
gcp-cherry-pick-bot[bot]
849b5a6344 fix: bump ubuntu base image (#15020) (#15021) (#15024)
Latest version of the ubuntu image addresses CVE-2023-38408.

https://ubuntu.com/security/notices/USN-6242-1
https://github.com/docker-library/repo-info/blob/master/repos/ubuntu/remote/22.04.md

resolves #15020

Signed-off-by: Mason Cole <macole@beyondtrust.com>
Co-authored-by: Mason Cole <117116981+bt-macole@users.noreply.github.com>
2023-08-11 15:47:35 -04:00
gcp-cherry-pick-bot[bot]
2d483a4c2f fix(cmp): send sigterm to cmp commands before sigkill to allow for potential cleanup (#9180) (#14955) (#14959)
* fix: send sigterm to cmp commands before sigkill to allow for potential cleanup



* fix: unit test for runCommand in cmpserver to test cleanup modified



* fix: change unit test for plugin/runCommand to avoid bad trap along with lint fix



---------

Signed-off-by: Ashin Sabu <ashin.sabu@harness.io>
Co-authored-by: Ashin Sabu <139749674+ashinsabu3@users.noreply.github.com>
2023-08-08 11:38:11 -04:00
gcp-cherry-pick-bot[bot]
fcdaf9b4b6 fix(ui): COPY JSON for ArgoCD version should include trailing newline (#5117) (#14917) (#14939)
Signed-off-by: Vipin M S <vipinachar2016@gmail.com>
Co-authored-by: Vipin M S <40431065+vipinachar@users.noreply.github.com>
2023-08-07 11:02:59 -04:00
gcp-cherry-pick-bot[bot]
990ab0ef81 docs: Update Generators-Git.md (#14921) (#14935)
Remove a misleading symbol from the pattern for the path.Match function. The pipe symbol doesn't have any special meaning.

Signed-off-by: German Lashevich <german.lashevich@gmail.com>
Co-authored-by: German Lashevich <design.ber@gmail.com>
2023-08-07 10:48:47 -04:00
github-actions[bot]
ec195adad8 Bump version to 2.7.11 (#14933)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-08-07 10:01:25 -04:00
gcp-cherry-pick-bot[bot]
743334ec1d docs: add ignoreDifferences name and namespace fields (#14741) (#14806)
* Update application.yaml



* Update docs/operator-manual/application.yaml



---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-08-03 10:28:35 -04:00
gcp-cherry-pick-bot[bot]
97ddc4b245 docs: Update application.yaml (#14742) (#14810)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-08-03 10:27:10 -04:00
gcp-cherry-pick-bot[bot]
89c30d8d34 docs: Change Generator docs for List Generator to note any key/value pairs can be used (#14825) (#14833)
This is no longer limited to cluster/url value pairs.

Signed-off-by: JesseBot <jessebot@linux.com>
Co-authored-by: JesseBot <jessebot@linux.com>
2023-08-01 13:56:11 -04:00
gcp-cherry-pick-bot[bot]
c56d7e7bc6 fix: ManagedResources API should not return diff for hooks (#14816) (#14829)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Co-authored-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2023-08-01 09:07:37 -07:00
gcp-cherry-pick-bot[bot]
9b6892c467 fix: Correct broken forever option in pod logs viewer. Fixes #14762 (#14763) (#14804)
Signed-off-by: Alex Collins <alex_collins@intuit.com>
Co-authored-by: Alex Collins <alexec@users.noreply.github.com>
2023-07-31 19:27:11 -04:00
github-actions[bot]
469f25753b Bump version to 2.7.10 (#14801)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-07-31 17:38:18 -04:00
gcp-cherry-pick-bot[bot]
057a39d962 docs: Clarify that security policy covers last 3 versions (#14786) (#14792)
* docs: Clarify that security policy covers last 3 versions



* Update SECURITY.md



---------

Signed-off-by: Kostis Kapelonis <kostis@codefresh.io>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Kostis (Codefresh) <39800303+kostis-codefresh@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-07-31 16:38:39 -04:00
gcp-cherry-pick-bot[bot]
ad006440f3 fix(controller): cache deadlock on delete and re-add cluster (#14780) (#14794)
Signed-off-by: Nathan Romriell <nateromriell@gmail.com>
Co-authored-by: Nathan Romriell <nathan@modsy.com>
2023-07-31 16:37:58 -04:00
gcp-cherry-pick-bot[bot]
1960da7e8f docs: Add missing value (#14538) (#14775)
Signed-off-by: felix <felix@psy-coding.com>
Co-authored-by: Felix <github@felixglaeske.de>
2023-07-28 15:18:50 -04:00
gcp-cherry-pick-bot[bot]
284c16f838 fix: OCI dependency url can't contain part of repository (#14699) (#14757)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Co-authored-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2023-07-27 16:19:10 -04:00
gcp-cherry-pick-bot[bot]
3e65ad2893 fix(sso): Set redirectURI for gitea, google, oauth Dex connectors (#11237) (#14737)
Signed-off-by: ylxianzhe <ylxianzhe@outlook.com>
Co-authored-by: XianzheTM <ylxianzhe@outlook.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-07-27 10:16:09 -04:00
gcp-cherry-pick-bot[bot]
84e2f77520 fix(server): handle PATCH in http/s server (#2677) (#14530) (#14732)
Signed-off-by: mmerrill3 <jjpaacks@gmail.com>
Co-authored-by: Michael Merrill <jjpaacks@gmail.com>
2023-07-27 10:14:56 -04:00
gcp-cherry-pick-bot[bot]
316be4eb27 fix(controller): log failed attempts to update operation state (#14273) (#14729)
* fix(controller): log failed attempts to update operation state



* new package name



* Update controller/appcontroller_test.go



---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-07-27 10:13:41 -04:00
Yuan Tang
0157f414f3 chore: Print in-cluster svr addr disabled warning when server starts (#14685)
* chore: Update log level to warn when in-cluster svr addr is disabled but internal addr is used (#14520)

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>

* chore: Print in-cluster svr addr disabled warning during ArgoDB initialization (#14539)

* chore: Print in-cluster svr addr disabled warning during ArgoDB initialization

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>

* fix: undo a change

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>

* chore: move to a function

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>

* chore: rename

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>

---------

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>

* chore: Print in-cluster svr addr disabled warning when server starts (#14553)

* chore: Print in-cluster svr addr disabled warning when server starts

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>

* fix: mock

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>

* no interface change

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

---------

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

---------

Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-07-24 17:25:48 -04:00
schakrad
fbe7f8f8d7 fix: ApplicationSet Controller crashes when tag is not closed; panic: Cannot find end tag="}}"(#14227) ( #14227) (#14689)
* appSet fix

Signed-off-by: schakrad <58915923+schakrad@users.noreply.github.com>

* Update applicationset/utils/utils_test.go

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

---------

Signed-off-by: schakrad <58915923+schakrad@users.noreply.github.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-07-24 17:24:32 -04:00
github-actions[bot]
0ee33e52dd Bump version to 2.7.9 (#14674)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-07-24 13:08:24 -04:00
gcp-cherry-pick-bot[bot]
ba44ddb9a1 fix: webhook handler fails to refresh when alternate application namespaces are configured (#13976) (#14653)
* fix: Add failing test for webhooks in all namespaces

This adds a failing test that properly exercises this functionality over
all namespaces. The issue with the code that is under test is that it
does not pass the namespace correctly to the patch of the application,
resulting in the patch not taking place in the correct namespace



* fix: queue webhook refresh for apps in all namespaces

This passes the test in the previous commit, to ensure that webhooks
correctly refresh applications across all namespaces.



* fix: Use existing NamespacedName type

Use the existing type instead of a custom type



---------

Signed-off-by: Nikolas Skoufis <nskoufis@seek.com.au>
Co-authored-by: Nik Skoufis <n.skoufis@gmail.com>
2023-07-21 14:31:07 -04:00
gcp-cherry-pick-bot[bot]
8249eddf75 docs(deep-links): Fix link to pkg.go.dev to not return 404 (#14595) (#14640)
Signed-off-by: Håkon Solbjørg <hakon@solbj.org>
Co-authored-by: Håkon Solbjørg <hakon@solbj.org>
2023-07-21 10:20:39 -04:00
gcp-cherry-pick-bot[bot]
0d24330298 docs: Skip export keyword in notification docs (#14633) (#14643)
This change does three things:

1. It removes the `export` keyword. It's not required since the example
   executes a script where the variables are evaluated as an inline
   string. One could even argue that there is a slight security issue
   with using `export` here, since that will expose the credentials to
   all applications started in the current context.
2. It adds a space (` `) before the `PASSWORD` variable. This will keep
   it out of the user's Bash history by default. See [HISTIGNORE][bash].
3. Add a newline for clarity.

[bash]: https://www.gnu.org/software/bash/manual/bash.html#index-HISTIGNORE

Signed-off-by: Andreas Lindhé <andreas@lindhe.io>
Co-authored-by: Andreas Lindhé <lindhe@users.noreply.github.com>
2023-07-21 10:17:29 -04:00
Zadkiel Aharonian
53eeed06b0 fix(ui): correctly align status column in application resource list (#14618)
Signed-off-by: Zadkiel Aharonian <hello@zadkiel.fr>
2023-07-21 09:51:23 -04:00
gcp-cherry-pick-bot[bot]
f1bfa8c655 fix(ui): Fix Destination Cluster URL/Name Drop down not updating destination field (#13813) (#14216) (#14627)
* fix(ui): Fix Destination Cluster URL/Name Drop down not updating destination field (fixes #13813)



* Address linting errors



---------

Signed-off-by: Kyle Purkiss <kyle.purkiss@procore.com>
Co-authored-by: Kyle Purkiss <kyle.purkiss@procore.com>
2023-07-20 14:54:28 -04:00
github-actions[bot]
92949f6033 Bump version to 2.7.8 (#14603)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-07-19 11:05:31 -04:00
gcp-cherry-pick-bot[bot]
c45665f039 fix(cli): allow argocd cli app command for multi source apps (#14256) (#14587)
Signed-off-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>
Co-authored-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>
2023-07-18 16:36:35 -04:00
gcp-cherry-pick-bot[bot]
62e9973074 fix(cli): argocd CLI RBAC validation doesn't work on actions (#13911) (#14578) (#14582)
* #11602 fix : Object options menu truncated when selected in ApplicationListView.



* #11602 fix : Object options menu truncated when selected in ApplicationListView.



* changes for argocd_rbac



---------

Signed-off-by: schakradari <saisindhu_chakradari@intuit.com>
Signed-off-by: schakrad <chakradari.sindhu@gmail.com>
Co-authored-by: schakrad <58915923+schakrad@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-07-18 16:22:16 -04:00
Michael Crenshaw
7d67b4d498 fix: warn instead of error on failure to get plugin app details (#14430)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-07-18 13:27:18 -04:00
gcp-cherry-pick-bot[bot]
4f1d876426 fix: Correct pod log viewer to support short log lines. Fixes #14402 (#14543) (#14562)
Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>
Co-authored-by: Yuan Tang <terrytangyuan@gmail.com>
2023-07-18 08:49:38 -04:00
gcp-cherry-pick-bot[bot]
697f2d403e fix(appset): normalize app spec before applying (#14481) (#14555)
* fix(appset): normalize app spec before applying



* fix nil ref, add test



* fix another test



---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-07-17 14:22:51 -04:00
gcp-cherry-pick-bot[bot]
da5dab7f2f docs: Correct example ClusterRole to allow Events in any namespace (#14544) (#14551)
Fixes: #14477

Signed-off-by: Dimitar Georgievski <dgeorgievski@gmail.com>
Co-authored-by: Dimitar Georgievski <dgeorgievski@gmail.com>
2023-07-17 12:56:44 -04:00
gcp-cherry-pick-bot[bot]
687323fece fix: 'argocd-server-tls' Secret should be loaded from informer (#14522) (#14547)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Co-authored-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2023-07-17 12:02:08 -04:00
Alexander Matyushentsev
490fb79090 Cherry[2.7] Helm repository might affect manifest generation of not related helm charts (#14528)
* Merge pull request from GHSA-94mc-2ch7-r5r5

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>

* fix: fix broken helm repo alias/name support (#13647)

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>

---------

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2023-07-15 09:55:50 -04:00
Maxime Brunet
fe4ba2399e fix(cli): fix tracking annotation diff for non-namespaced resources (#14508)
Signed-off-by: Maxime Brunet <max@brnt.mx>
2023-07-14 10:16:38 -04:00
gcp-cherry-pick-bot[bot]
63c45b3625 docs: Update SLSA verification commands (#14437) (#14455)
* update



* update



* update



* update



* update



* update



* update



---------

Signed-off-by: laurentsimon <laurentsimon@google.com>
Co-authored-by: laurentsimon <64505099+laurentsimon@users.noreply.github.com>
2023-07-11 10:54:28 -04:00
gcp-cherry-pick-bot[bot]
2b326dcd2f fix: Fix pod log viewer scrollbars (#14199) (#14419)
* fix: Fix pod log viewer scrollbars



* fix scrolling



---------

Signed-off-by: Alex Collins <alex_collins@intuit.com>
Co-authored-by: Alex Collins <alexec@users.noreply.github.com>
2023-07-11 10:47:22 -04:00
gcp-cherry-pick-bot[bot]
85e5b0b102 fix: Update bitbucket.org rsa ssh key (#14392) (#14397)
The key change announcement at https://bitbucket.org/blog/ssh-host-key-changes

Signed-off-by: Haitao Li <hli@atlassian.com>
Co-authored-by: Haitao Li <39936070+hligit@users.noreply.github.com>
2023-07-07 10:24:24 -04:00
gcp-cherry-pick-bot[bot]
cb06d7d789 docs: managedFieldsManagers example in docs needs double-quotes (#14324) (#14372)
* is a Yaml special character and must be quoted, otherwise ArgoCD fails to parse and shows an error.

Signed-off-by: Paul Martin <paul.martin@gmail.com>
Co-authored-by: Paul Martin <paul.martin@gmail.com>
2023-07-06 13:49:59 -04:00
gcp-cherry-pick-bot[bot]
d9dfdaed22 docs: typofix (#14344) (#14356)
Signed-off-by: Julien Bouquillon <julien.bouquillon@sg.social.gouv.fr>
Co-authored-by: Julien Bouquillon <contact@revolunet.com>
2023-07-05 21:41:36 -04:00
github-actions[bot]
4650bb2817 Bump version to 2.7.7 (#14353)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-07-05 15:25:01 -04:00
Venkata Mutyala
0a368c2835 feat: upgrade dexIDP from 2.36.0 -> 2.37.0 (#14305) (#14310) (#14322)
* feat: update dexidp image tag from v2.36.0 -> v2.37.0



* chore: adding GlueOps to USERS.md



---------

Signed-off-by: Venkata Mutyala <venkata@venkatamutyala.com>
2023-07-03 12:55:45 -07:00
gcp-cherry-pick-bot[bot]
6bd5bd0ddd docs: Adding explanation for CMP yaml/json generation (must be K8S object) (#9471) (#14295) (#14302)
Signed-off-by: Christian Hernandez <christian@chernand.io>
Co-authored-by: Christian Hernandez <christianh814@users.noreply.github.com>
2023-06-30 19:38:36 -04:00
gcp-cherry-pick-bot[bot]
58686278f3 docs: Clarify "SSH known host public keys" text (#13537) (#14298)
Add `ssh_keyscan` example usage

Signed-off-by: Daniel Perevalov <daniel.perevalov@gmail.com>
Co-authored-by: Daniel Perevalov <daniel.perevalov@gmail.com>
2023-06-30 19:35:37 -04:00
gcp-cherry-pick-bot[bot]
ba2982e69d docs: explicit that ArgoCD hooks replaces the Helm ones (#14283) (#14288)
* docs: explicit that ArgoCD hooks replace the Helm ones

After digging a bit in the code, I've found this comment that confirms
that if we define some ArgoCD hooks the Helm ones are ignored.
425d65e076/pkg/sync/hook/hook.go (L36C2-L36C46)



* docs: add Back Market in the user list



* Update docs/user-guide/helm.md



---------

Signed-off-by: Benoît Sauvère <benoit.sauvere@backmarket.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Benoît Sauvère <benoit@sauve.re>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-06-30 11:38:50 -04:00
jannfis
dd70d97825 fix: Correctly verify signatures when targetRevision is a branch name (cherry-pick #14214) (#14236)
* fix: Correctly verify signatures when targetRevision is a branch name (#14214)

* fix: Correctly verify signatures when targetRevision is a branch name

Signed-off-by: jannfis <jann@mistrust.net>

* Add more e2e tests

Signed-off-by: jannfis <jann@mistrust.net>

* Fix a bug and add unit test

Signed-off-by: jannfis <jann@mistrust.net>

---------

Signed-off-by: jannfis <jann@mistrust.net>

* Update go.mod

Signed-off-by: jannfis <jann@mistrust.net>

* go mod tidy

Signed-off-by: jannfis <jann@mistrust.net>

---------

Signed-off-by: jannfis <jann@mistrust.net>
2023-06-29 17:37:35 -04:00
gcp-cherry-pick-bot[bot]
3424fa4676 fix(appset): handles a race condition where RollingSync does not properly detect a successful sync (#13926) (#14200)
Signed-off-by: wmgroot <wmgroot@gmail.com>
Co-authored-by: wmgroot <wmgroot@gmail.com>
2023-06-25 19:12:25 -04:00
gcp-cherry-pick-bot[bot]
93b22286ee fix(ui): Polish pod logs viewer (#14050) (#14107) (#14193)
* fix(ui): Polish pod logs viewer. Fixes #14050



* lint



* lint



* lint



---------

Signed-off-by: Alex Collins <alex_collins@intuit.com>
Co-authored-by: Alex Collins <alexec@users.noreply.github.com>
2023-06-24 18:47:10 -07:00
gcp-cherry-pick-bot[bot]
1611ca5667 docs: howto configure clusters with iam (#14187) (#14190)
There is quite a lot of boilerplate in configuring Argo CD with EKS,
hopefully this sheds a bit of light on how that's actually done.

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
2023-06-23 15:50:13 -04:00
gcp-cherry-pick-bot[bot]
e48496cd4a docs: Add documentation on background cascading deletion (#12229) (#14149)
Signed-off-by: toyamagu2021@gmail.com <toyamagu2021@gmail.com>
Co-authored-by: toyamagu <83329336+toyamagu-2021@users.noreply.github.com>
2023-06-20 21:29:33 -04:00
gcp-cherry-pick-bot[bot]
26a40aa741 fix(ui): Add newlines to copy and paste of logs (#14019) (#14103) (#14144)
Signed-off-by: Alex Collins <alex_collins@intuit.com>
Co-authored-by: Alex Collins <alexec@users.noreply.github.com>
2023-06-20 16:35:02 -04:00
github-actions[bot]
00c914a948 Bump version to 2.7.6 (#14143)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-06-20 16:28:10 -04:00
Michael Crenshaw
3404109e89 fix: retain count and order of revisions for multi source apps (#14108) (#14113) (#14135)
* fix: retain order of revisions for multi source apps (#14108)



* fix: retain revision for multi source app with ref-repos



* calculate commitSHA before quitting manifest generation



---------

Signed-off-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Lukas Wöhrl <lukas@woehrl.net>
2023-06-20 15:53:32 -04:00
gcp-cherry-pick-bot[bot]
7e42030e72 docs: Update release info for 2.8 (#14077) (#14121)
* docs: update release dates



* update



* Leo



* 2.10



---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: pasha-codefresh <pavel@codefresh.io>
2023-06-19 10:40:14 -04:00
github-actions[bot]
a2430af1c3 Bump version to 2.7.5 (#14100)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-06-16 10:10:44 -04:00
Michael Crenshaw
dbb488a607 fix(cmp): discover plugins relative to app path (#13940) (#13946) (#14084)
* fix(cmp): discover plugins relative to app path (#13940)



* securejoin



* intuitive constant names



* comments



* add missing import



---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-06-16 09:43:25 -04:00
gcp-cherry-pick-bot[bot]
dd565e782e fix: Multi source apps resolve revision in .status field (#14081) (#14086)
Signed-off-by: Jorge Turrado <jorge.turrado@scrm.lidl>
Co-authored-by: Jorge Turrado Ferrero <Jorge_turrado@hotmail.es>
2023-06-15 17:44:01 -04:00
Michael Crenshaw
0df409394d fix(ui): soften readiness gate failure message (#13972) (#14076) (#14079)
* fix(ui): soften readiness gate failure message (#13972)



* null check everything



---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-06-15 13:21:27 -04:00
gcp-cherry-pick-bot[bot]
d7f67a17d6 chore(deps): bump ubuntu base image version (#14024) (#14066)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-06-14 17:32:53 -04:00
gcp-cherry-pick-bot[bot]
cbed3d4846 fix(cli): support application in any namespace with argocd app diff (#13978) (#13980) (#14061)
Closes https://github.com/argoproj/argo-cd/issues/13978.

Signed-off-by: mugioka <okamugi0722@gmail.com>
Co-authored-by: mugi <62197019+mugioka@users.noreply.github.com>
2023-06-14 15:21:29 -04:00
gcp-cherry-pick-bot[bot]
a911b005bd docs: note CLI incompatibilities (#14049) (#14060)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-06-14 15:08:30 -04:00
gcp-cherry-pick-bot[bot]
d5a3ae3dec docs: add golang upgrade note for 2.5 (#14048) (#14057)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-06-14 15:07:45 -04:00
gcp-cherry-pick-bot[bot]
fe276bce55 docs: add documentation for child elements of path (#14044) (#14054)
* docs: add documentation for child elements of path

When using go templating, the parent `{{ path }}` becomes `{{ .path.path }}, however, the other values are not at `{{ .path.path.* }}`, but at `{{ .path.* }}`.

This documentation update seeks to make this easier to understand since we just ran into this.



* Update docs/operator-manual/applicationset/GoTemplate.md




---------

Signed-off-by: Morre <mmeyer@anaconda.com>
Signed-off-by: Morre <morre@mor.re>
Co-authored-by: Morre <morre@mor.re>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-06-14 15:06:57 -04:00
gcp-cherry-pick-bot[bot]
6ed3d619a6 chore(deps): upgrade haproxy to 2.6.14-alpine (#14018) (#14051)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-06-14 12:35:52 -04:00
Michael Crenshaw
e4aa7b61bc chore(deps): upgrade go to 1.19.10 (#13942)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-06-10 15:21:42 -04:00
gcp-cherry-pick-bot[bot]
2552c546a3 fix(ui): Fix Patch and Get Manifests (#13949) (#13952)
Signed-off-by: Geoffrey Muselli <geoffrey.muselli@gmail.com>
Co-authored-by: Geoffrey MUSELLI <geoffrey.muselli@gmail.com>
2023-06-08 08:11:35 -04:00
github-actions[bot]
a33baa301f Bump version to 2.7.4 (#13910)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-06-05 14:46:09 -04:00
Jason Meridth
ca5aced6d3 fix: CVE-2023-1732 (#13748) (#13893)
Upgrade cloudflare pkg to minimum version required to resolve https://nvd.nist.gov/vuln/detail/CVE-2023-1732

Signed-off-by: Mark Shields <4237425+beejiujitsu@users.noreply.github.com>
Co-authored-by: Mark Shields <4237425+beejiujitsu@users.noreply.github.com>
2023-06-02 17:17:31 -04:00
gcp-cherry-pick-bot[bot]
7c9b1c5d78 fix(ui): Fix Link warnings (#13694) (#13854) (#13890)
Signed-off-by: Geoffrey Muselli <geoffrey.muselli@gmail.com>
Co-authored-by: Geoffrey MUSELLI <geoffrey.muselli@gmail.com>
2023-06-02 09:37:32 -04:00
gcp-cherry-pick-bot[bot]
6f4b4d3212 docs: redirect old CMP page to new one (#13877) (#13878)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-06-01 16:43:42 -04:00
gcp-cherry-pick-bot[bot]
f1c4ed2d7b fix(ui): Patch Resource missing appNamespace (#13839) (#13840)
Signed-off-by: Geoffrey Muselli <geoffrey.muselli@gmail.com>
Co-authored-by: Geoffrey MUSELLI <geoffrey.muselli@gmail.com>
2023-06-01 09:57:42 -04:00
gcp-cherry-pick-bot[bot]
25d5ad97a0 fix(doc): deep links example fix (#13855) (#13857)
Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>
Co-authored-by: Soumya Ghosh Dastidar <44349253+gdsoumya@users.noreply.github.com>
2023-05-31 13:34:55 -04:00
Lewis Marsden-Lambert
7562607e2b fix(appset): Post selector with Go templates in ApplicationSet (cherry-pick #13584) (#13822)
* fix(appset): Post selector with Go templates in ApplicationSet (#13584)

* fixes #12524

Signed-off-by: Lewis Marsden-Lambert <lewis.lambert@zserve.co.uk>

* refactor keepOnlyStringLabels function into more generic map flattening function

Signed-off-by: Lewis Marsden-Lambert <lewis.lambert@zserve.co.uk>

* updated USERS.md

Signed-off-by: Lewis Marsden-Lambert <lewis.marsden-lambert@smartpension.co.uk>

* use flatten library to replace custom flatten function

Signed-off-by: Lewis Marsden-Lambert <lewis.marsden-lambert@smartpension.co.uk>

---------

Signed-off-by: Lewis Marsden-Lambert <lewis.lambert@zserve.co.uk>
Signed-off-by: Lewis Marsden-Lambert <lewis.marsden-lambert@smartpension.co.uk>

* fix merge

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

* re-add deleted test

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

---------

Signed-off-by: Lewis Marsden-Lambert <lewis.lambert@zserve.co.uk>
Signed-off-by: Lewis Marsden-Lambert <lewis.marsden-lambert@smartpension.co.uk>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-05-30 14:27:41 -04:00
gcp-cherry-pick-bot[bot]
8d23f51340 docs: fix deep links docs formatting (#13747) (#13837)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-05-30 14:18:56 -04:00
gcp-cherry-pick-bot[bot]
fb2e33ba3a docs: update openunison authChainName (#13531) (#13793)
Signed-off-by: Samir-NT <133138781+Samir-NT@users.noreply.github.com>
Co-authored-by: Samir-NT <133138781+Samir-NT@users.noreply.github.com>
2023-05-28 15:50:50 -04:00
gcp-cherry-pick-bot[bot]
97cf0bf736 docs: Update disaster_recovery.md to reflect quay.io as docker container registry (#13520) (#13790)
ArgoCD docker images are being used from `quay.io` registry.
Updated document to reflect that in the `bash` commands.

Signed-off-by: Divyang Patel <divyang.jp@gmail.com>
Co-authored-by: Divyang Patel <divyang.jp@gmail.com>
2023-05-28 15:48:12 -04:00
gcp-cherry-pick-bot[bot]
8c2b863477 fix: ensure repositories are correctly marked with inherited creds in CLI output (#13428) (#13789)
* tests: ensure `InheritedCreds` is propagated via repo API endpoints



* fix: ensure `InheritedCreds` is propagated via repo API endpoints



* tests: add e2e test for `argocd repo get` with inherited credentials



* fix(cli): prioritise value of `InheritedCreds` over `HasCredentials()`

Since the API does not return sensitive information `HasCredentials()` will return false for all scenarios except when username/password is used as credentials. Given the current logic this means that the code will never even check `InheritedCreds` resulting in an output of `false` for `CREDS` column (in the case of inherited credentials).

Note: There remains a bug in this code in that any repo that has explicit (sensitive) credentials (e.g. SSH private key) will still be displayed as `CREDS = false`.


---------

Signed-off-by: OneMatchFox <878612+onematchfox@users.noreply.github.com>
Co-authored-by: Brian Fox <878612+onematchfox@users.noreply.github.com>
2023-05-28 08:26:57 -04:00
Blake Pettersson
6c916dcf65 test: remove testmatchvaluesgotemplate (#13788)
This test came with the previous cherry-pick, but should not be present
for 2.5 - 2.7.

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
2023-05-28 08:23:06 -04:00
gcp-cherry-pick-bot[bot]
7b428e9ff7 docs: Fixed titles in app deletion doc (#13469) (#13782)
Signed-off-by: michaelkot97 <michael.kot97@gmail.com>
Co-authored-by: Michael Kotelnikov <36506417+michaelkotelnikov@users.noreply.github.com>
2023-05-27 21:47:23 -04:00
gcp-cherry-pick-bot[bot]
0bf029a0e3 fix: Regression in signature verification for git tags (#12797) (#13113)
Signed-off-by: jannfis <jann@mistrust.net>
Co-authored-by: jannfis <jann@mistrust.net>
2023-05-27 21:25:21 -04:00
gcp-cherry-pick-bot[bot]
80c2912805 docs: add helm values declarative syntax (#13661) (#13778)
The Helm section of the user guide is missing an example of using `source.helm.values`.

Signed-off-by: Nicholas Morey <nicholas@morey.tech>
Co-authored-by: Nicholas Morey <nicholas@morey.tech>
2023-05-27 20:56:06 -04:00
Blake Pettersson
f74235726a fix: do not replace namespaces (#13758) (#13769)
* fix: do not replace namespaces

When doing kubectl replace, namespaces should not be affected. This
applies the fix from argoproj/gitops-engine#524. Fixes
argoproj/argo-cd#12810 and argoproj/argo-cd#12539.



* chore: go mod tidy



---------

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
2023-05-27 17:30:13 -04:00
gcp-cherry-pick-bot[bot]
0e7ce4d432 docs: fix incorrect instructions for site documentation (#13209) (#13773)
* fix: incorrect instructions for site documentation



* drop checking external links



---------

Signed-off-by: Regina Scott <rescott@redhat.com>
Co-authored-by: Regina Scott <50851526+reginapizza@users.noreply.github.com>
2023-05-27 16:45:50 -04:00
Blake Pettersson
9697cbf1e9 fix(appset): allow cluster urls to be matched (#13715) (#13770)
* fix: allow cluster urls to be matched

Related to #13646, and after discussion with @crenshaw-dev, it turns
out that matching on cluster urls is not possible. This is due to the
fact that the implementation of `LabelSelectorAsSelector` from
`k8s.io/apimachinery` validates that a label value is no longer than 63
characters, and validates that it's alphanumeric. In order to work
around that, we'll create our own implementation of
`LabelSelectorAsSelector`.

This implementation has been copied verbatim, with the difference that
in `isValidLabelValue`, we first check if the label value is a valid
url. If it is not, we proceed with the label checks as with the
original implementation.

Apart from that, the only other differences are making as much as
possible to be package-private; the intent is to only make `Matches`
and `LabelSelectorAsSelector` available from outside the package.



* chore: drop all label value restrictions

We want to be more flexible in what we accept in post-selectors, mainly
that we want to allow other values than only server urls. For this, we
will drop all restrictions that a typical "label value" would typically
have.



---------

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
2023-05-27 16:08:24 -04:00
gcp-cherry-pick-bot[bot]
2db86afb78 fix(ui): Stop using the deprecated url format for gitlab instances (#13687) (#13767)
* fix: Stop using the deprecated url format for gitlab instances

The legacy URLs format has been deprecated since february 2023 and
now gitlab is make these urls invalid.

Ref: https://docs.gitlab.com/ee/update/deprecations.html#legacy-urls-replaced-or-removed


* docs: Add Urbantz to the list of organizations using argo-cd



---------

Signed-off-by: Miguel Sacristán Izcue <miguel_tete17@hotmail.com>
Co-authored-by: Tete17 <miguel_tete17@hotmail.com>
2023-05-27 15:17:45 -04:00
gcp-cherry-pick-bot[bot]
ed828e9ddb feat: add ssh.github.com to SSH known hosts (#13592) (#13765)
* feat: add ssh.github.com to SSH known hosts



* fix: update hack/ssh_known_hosts



* fix: sync known hosts ConfigMap with hack/ssh_known_hosts



---------

Signed-off-by: Takashi Kusumi <tkusumi@zlab.co.jp>
Co-authored-by: Takashi Kusumi <tkusumi@zlab.co.jp>
2023-05-27 12:45:29 -04:00
gcp-cherry-pick-bot[bot]
07f9c542aa fix: argocd app sync/wait falsely failed with completed with phase: Running (#13637) (#13672)
Signed-off-by: Jesse Suen <jesse@akuity.io>
Co-authored-by: Jesse Suen <jessesuen@users.noreply.github.com>
2023-05-27 12:32:10 -04:00
gcp-cherry-pick-bot[bot]
b3f0979e71 fix: CMPv2 does not allow symlinks to adjacent files in same git repo. Fixes #13342 (#13360) (#13669)
fix: CMPv2 does not allow symlinks to adjacent files in same git repo. Fixes #13342 (#13360)

Signed-off-by: Jiacheng Xu <xjcmaxwellcjx@gmail.com>
Co-authored-by: Jiacheng Xu <xjcmaxwellcjx@gmail.com>
2023-05-27 12:31:57 -04:00
gcp-cherry-pick-bot[bot]
3f33817b6a docs: correct indentation for gke ingress (#13680) (#13761)
Signed-off-by: Carlos Sanchez <carlos@apache.org>
Co-authored-by: Carlos Sanchez <carlos@apache.org>
2023-05-27 12:31:21 -04:00
gcp-cherry-pick-bot[bot]
f4848bbaaa fix(appset): add ApplicationSet ProgressiveSync handling to clean up old appStatus entries when Applications are removed or RollingSync is disabled (#13419) (#13759)
Signed-off-by: wmgroot <wmgroot@gmail.com>
Co-authored-by: wmgroot <wmgroot@gmail.com>
2023-05-27 11:42:21 -04:00
gcp-cherry-pick-bot[bot]
afe3b81fe1 fix(appset) [ISSUE-13501] adjust NotIn operator behavior of AppSet Progressive Syncs to match documentation (#13551) (#13760)
Signed-off-by: wmgroot <wmgroot@gmail.com>
Co-authored-by: wmgroot <wmgroot@gmail.com>
2023-05-27 11:36:29 -04:00
gcp-cherry-pick-bot[bot]
419ac0e753 fix(kustomize): allow using build env in images (#13745) (#13756)
This allows substituting the target revision using
image:${ARGOCD_APP_REVISION}

Signed-off-by: Robin Gloster <mail@glob.in>
Co-authored-by: Robin Gloster <mail@glob.in>
2023-05-26 13:04:56 -04:00
gcp-cherry-pick-bot[bot]
23a0799134 feat: add css to support external custom style (#13279) (#13746)
* feat: add external css to customize banner



* fix lint



---------

Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>
Co-authored-by: asingh <11219262+ashutosh16@users.noreply.github.com>
2023-05-25 13:55:17 -04:00
github-actions[bot]
e7891b899a Bump version to 2.7.3 (#13719)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-05-24 09:36:32 -04:00
gcp-cherry-pick-bot[bot]
ecbb7aa074 fix: prevent concurrent processing if kustomize commonAnnotations exist (#13697) (#13702)
Signed-off-by: yilmazo <onuryilmaz93@yandex.com>
Co-authored-by: Onur Yilmaz <onuryilmaz93@yandex.com>
2023-05-23 11:34:52 -07:00
gcp-cherry-pick-bot[bot]
6d85d6f85f fix: deeplinks API checking wrong permissions (#13664) (#13670)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Co-authored-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2023-05-19 16:10:47 -07:00
gcp-cherry-pick-bot[bot]
3778173c05 Revert "fix: interpolate gen fix (#12716) (#13061)" (#13666) (#13668)
This reverts commit 1a4a878703.

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-05-19 16:16:59 -04:00
gcp-cherry-pick-bot[bot]
b0df91d209 fix(appset): handle templating of raw JSON fields (#12947) (#12949) (#13654)
* fix(appset): handle templating of raw JSON fields (#12947)



* revert unnecessary changes



---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-05-18 19:58:17 -04:00
gcp-cherry-pick-bot[bot]
a565d7d3a4 fix: avoid acquiring lock on two mutexes at the same time to prevent deadlock (#13636) (#13648)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Co-authored-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2023-05-18 13:07:06 -07:00
gcp-cherry-pick-bot[bot]
0794a14400 fix: Reduce of empty array with no initial value for LogsView (#13620) (#13651)
* if not log lines are received (yet) the reduce raises an error and crashes the complete ArgoCDUI

Refs: #13447

Signed-off-by: patst <patrick.steinig@googlemail.com>
Co-authored-by: patst <patrick.steinig@googlemail.com>
2023-05-18 13:05:47 -07:00
Michael Crenshaw
fb2ae1198d fix: append elementsYaml in order to keep fields order (#13335) (#13508)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Laurentiu Soica <laurentiu@soica.ro>
2023-05-18 09:42:06 -04:00
gcp-cherry-pick-bot[bot]
ba17262c96 docs: Update kustomize resource to correct path (#13196) (#13632)
This commit updates the kustomize section to include the correct path.  Without the `\base` at the end of the path kustomize errors out trying to find a `kustomization.yaml` since there isn't one in the `ha` directory.

Signed-off-by: Chris Wiggins <5607419+cwiggs@users.noreply.github.com>
Co-authored-by: Chris Wiggins <5607419+cwiggs@users.noreply.github.com>
2023-05-18 09:13:22 -04:00
gcp-cherry-pick-bot[bot]
9599930611 fix: set containerName value for log view selector (#13587) (#13638)
Signed-off-by: Shawn Toffel <shawn.toffel@gmail.com>
Co-authored-by: Shawn Toffel <shawn.toffel@gmail.com>
2023-05-18 09:12:06 -04:00
Jaideep Rao
3f8bd11786 fix: consume cluster cache deadlock fix from gitops-engine (#13612)
Signed-off-by: Jaideep Rao <jaideep.r97@gmail.com>
2023-05-18 09:11:40 -04:00
gcp-cherry-pick-bot[bot]
d80a5d1a27 fix: deeplinks fails when app uses cluster name (#13564) (#13569)
Co-authored-by: Soumya Ghosh Dastidar <44349253+gdsoumya@users.noreply.github.com>
2023-05-12 10:09:54 -07:00
github-actions[bot]
cbee7e6011 Bump version to 2.7.2 (#13562)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: leoluz <leoluz@users.noreply.github.com>
2023-05-12 09:26:59 -04:00
gcp-cherry-pick-bot[bot]
8e61f64cc9 fix: update log view on container select (#13474) (#13546)
Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>
Co-authored-by: asingh <11219262+ashutosh16@users.noreply.github.com>
2023-05-11 09:15:34 -04:00
gcp-cherry-pick-bot[bot]
e413db45b1 fix: surface errors when compressing files (#13491) (#13494)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-05-09 12:37:32 -04:00
gcp-cherry-pick-bot[bot]
26cf7d95b7 fix: interpolate gen fix (#12716) (#13061) (#13485)
* Finalizing Appset Interpolation Changes



* Pushing up changes for matrix_test.go



* A now incredibly simple solution



* Updating matrix_test.go to master



* One more fix



* Changes up to now



* Currently working test (Rough)



* Cleanly working across 2 test cases!



* Merged into single test case



---------

Signed-off-by: jkulkarn <jay.p.kulkarni@blackrock.com>
Co-authored-by: Jay P Kulkarni <jkulkarni@ucla.edu>
Co-authored-by: jkulkarn <jay.p.kulkarni@blackrock.com>
2023-05-09 12:36:30 -04:00
gcp-cherry-pick-bot[bot]
24bd4aee70 docs: fix typo (#12960) (#13436)
Signed-off-by: mikutas <23391543+mikutas@users.noreply.github.com>
Co-authored-by: Takumi Sue <23391543+mikutas@users.noreply.github.com>
2023-05-04 18:21:33 -04:00
github-actions[bot]
5e543518db Bump version to 2.7.1 (#13418)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-05-02 12:19:11 -04:00
gcp-cherry-pick-bot[bot]
72a69e2f16 fix(manifests): use params CM and env var for redis server (#13214) (#13396) (#13417)
* fix(manifests): use params CM and env var for redis server (#13214)



* add release note



* rephrase



* rephrase



---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-05-02 12:15:41 -04:00
github-actions[bot]
c592219140 Bump version to 2.7.0 (#13404)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-05-01 20:01:14 -04:00
gcp-cherry-pick-bot[bot]
155b6a9c10 chore: upgrade redis to 7.0.11 to avoid CVE-2023-0464 (#13389) (#13402)
Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
Co-authored-by: Justin Marquis <34fathombelow@protonmail.com>
2023-05-01 17:48:32 -04:00
gcp-cherry-pick-bot[bot]
29c485778a chore: upgrade haproxy to 2.6.12 to avoid CVE-2023-0464 (#13388) (#13401)
Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
Co-authored-by: Justin Marquis <34fathombelow@protonmail.com>
2023-05-01 16:43:43 -04:00
gcp-cherry-pick-bot[bot]
a707ab6b0e docs: Application Info field documentation (#10814) (#13351) (#13377)
* add Application info field documentation



* Extra Application info docs



* Added info field documentation



* Add space to comment




* docs: Add extra_info.md to table of contents



---------

Signed-off-by: Hapshanko <112761282+Hapshanko@users.noreply.github.com>
Co-authored-by: Hapshanko <112761282+Hapshanko@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-05-01 15:42:22 -04:00
gcp-cherry-pick-bot[bot]
d6e5768417 fix: Disable scrollbars on pod logs viewer. Fixes #13266 (#13294) (#13397)
Signed-off-by: Alex Collins <alex_collins@intuit.com>
Co-authored-by: Alex Collins <alexec@users.noreply.github.com>
2023-05-01 15:09:39 -04:00
Alexander Matyushentsev
428d47ba8a feat: support 'helm.sh/resource-policy: keep' helm annotation (#13157)
* feat: support 'helm.sh/resource-policy: keep' helm annotation

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>

* document  annotation

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>

---------

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2023-04-24 16:00:54 -07:00
gcp-cherry-pick-bot[bot]
1adbebf888 fix(ui): use name instead of title for CMP parameters (#13250) (#13337)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-04-24 15:13:02 -04:00
gcp-cherry-pick-bot[bot]
6ec093dcb6 fix: remove false positive for no-discovery cmp; log string, not bytes (#13251) (#13336)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-04-24 15:12:52 -04:00
gcp-cherry-pick-bot[bot]
daa9d4e13e fix: Update .goreleaser.yaml (#13260) (#13263)
Signed-off-by: Kiruthikameena <meenasuja16@gmail.com>
Co-authored-by: Kiruthikameena <meenasuja16@gmail.com>
2023-04-17 15:31:31 +02:00
gcp-cherry-pick-bot[bot]
bd9ef3fbde docs: s/No supported/Not supported (#13189) (#13253)
Signed-off-by: Vincent Verleye <124772102+smals-vinve@users.noreply.github.com>
Co-authored-by: Vincent Verleye <124772102+smals-vinve@users.noreply.github.com>
2023-04-16 01:34:08 -04:00
gcp-cherry-pick-bot[bot]
a29a2b13d1 docs: Fix wrong link to non existing page for applicationset reference (#13207) (#13247)
Signed-off-by: TheDatabaseMe <philip.haberkern@googlemail.com>
Co-authored-by: Philip Haberkern <59010269+thedatabaseme@users.noreply.github.com>
2023-04-15 14:33:27 -04:00
github-actions[bot]
483d26b113 Bump version to 2.7.0-rc2 (#13192)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-04-11 11:36:59 -04:00
Alexander Matyushentsev
21e2400b83 fix: --file usage is broken for 'argocd proj create' command (#13130)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2023-04-07 09:51:23 -07:00
gcp-cherry-pick-bot[bot]
52de54a799 fix(cli): add redis-compress flag to argocd admin dashboard command (#13055) (#13056) (#13114)
* add `redis-compress` flag to `argocd admin dashboard` command

Previously, gzip compression was disabled and not configurable,
which made it impossible to work with gzipped Redis cache.
This commit adds support for gzip compression to the ArgoCD admin dashboard.



* update dashboard docs for --redis-compress flag



* add support for REDIS_COMRESSION env in cli admin dashboard



* update flag description




* update dashboard docs



---------

Signed-off-by: Pavel Aborilov <aborilov@gmail.com>
Signed-off-by: Pavel <aborilov@gmail.com>
Co-authored-by: Pavel <aborilov@gmail.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-04-06 16:09:16 -07:00
gcp-cherry-pick-bot[bot]
f35c127e2c docs: fix broken version selector (#13102) (#13105)
Signed-off-by: Harold Cheng <niuchangcun@gmail.com>
Co-authored-by: cjc7373 <niuchangcun@gmail.com>
2023-04-04 16:21:48 -04:00
gcp-cherry-pick-bot[bot]
0edc7c5ef1 fix: Add more context to the sync failed message when resource kind doesn't exist (#12980) (#13090)
* fix: add more context to k8s message



* fix: add more context to k8s message



* fix: add more context to k8s message



* fix: add more context to k8s message



* fix: add more context to k8s message



* fix: add more context to k8s message



* Update util/argo/argo.go




* Update util/argo/argo.go




* improvements, maybe



* remove unnecessary end quote



* avoid conflicts with other tests



---------

Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>
Signed-off-by: asingh <11219262+ashutosh16@users.noreply.github.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: asingh <11219262+ashutosh16@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-04-03 11:51:55 -04:00
gcp-cherry-pick-bot[bot]
d232635ebe fix(perf): filtering process in application-list api (#12985) (#12999) (#13057)
* perf: fix filtering process in application-list api (fixes: #12985)



* fix function for filtering by name



* add nil check in filtering by name



* add benchmark test for application list func



* add err check for benchmark



* fix test func for source soundness



---------

Signed-off-by: tken2039 <tken2039@gmail.com>
Signed-off-by: tken2039 <ken.takahashi@linecorp.com>
Co-authored-by: tken2039 <57531594+tken2039@users.noreply.github.com>
2023-03-30 10:47:45 -04:00
gcp-cherry-pick-bot[bot]
5f1fc31ed0 fix: applicationset reduce redundant reconciles (#12457) (#12480) (#13029)
* fix: applicationset reduce redundant reconciles



* fix: applicationset reduce redundant reconciles



* adding tests



* every line counts



* deep copy applications from event object



* update from code review



* check progressive sync fields



* check progressive sync fields



* selective checks for progressive syncs



* selective checks for progressive syncs



* pural



---------

Signed-off-by: rumstead <37445536+rumstead@users.noreply.github.com>
Co-authored-by: rumstead <37445536+rumstead@users.noreply.github.com>
2023-03-28 12:06:39 -04:00
github-actions[bot]
0d0d2a97bb Bump version to 2.7.0-rc1 (#13020)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: pasha-codefresh <pasha-codefresh@users.noreply.github.com>
2023-03-27 16:37:42 +03:00
342 changed files with 11862 additions and 30747 deletions

View File

@@ -1,15 +0,0 @@
{
"LABEL": {
"name": "title needs formatting",
"color": "EEEEEE"
},
"CHECKS": {
"prefixes": ["[Bot] docs: "],
"regexp": "^(feat|fix|docs|test|ci|chore)!?(\\(.*\\))?!?:.*"
},
"MESSAGES": {
"success": "PR title is valid",
"failure": "PR title is invalid",
"notice": "PR Title needs to pass regex '^(feat|fix|docs|test|ci|chore)!?(\\(.*\\))?!?:.*"
}
}

View File

@@ -6,7 +6,6 @@ Checklist:
* [ ] Either (a) I've created an [enhancement proposal](https://github.com/argoproj/argo-cd/issues/new/choose) and discussed it with the community, (b) this is a bug fix, or (c) this does not need to be in the release notes.
* [ ] The title of the PR states what changed and the related issues number (used for the release note).
* [ ] The title of the PR conforms to the [Toolchain Guide](https://argo-cd.readthedocs.io/en/latest/developer-guide/toolchain-guide/#title-of-the-pr)
* [ ] I've included "Closes [ISSUE #]" or "Fixes [ISSUE #]" in the description to automatically close the associated issue.
* [ ] I've updated both the CLI and UI to expose my feature, or I plan to submit a second PR with them.
* [ ] Does this PR require documentation updates?

View File

@@ -16,7 +16,7 @@
## image-reuse.yaml
- The resuable workflow can be used to publish or build images with multiple container registries(Quay,GHCR, dockerhub), and then sign them with cosign when an image is published.
- A GO version `must` be specified e.g. 1.19
- A GO version `must` be specified e.g. 1.20
- The image name for each registry *must* contain the tag. Note: multiple tags are allowed for each registry using a CSV type.
- Multiple platforms can be specified e.g. linux/amd64,linux/arm64
- Images are not published by default. A boolean value must be set to `true` to push images.

View File

@@ -13,7 +13,7 @@ on:
env:
# Golang version to use across CI steps
GOLANG_VERSION: '1.19'
GOLANG_VERSION: '1.20'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -425,9 +425,9 @@ jobs:
git config --global user.email "john.doe@example.com"
- name: Pull Docker image required for tests
run: |
docker pull ghcr.io/dexidp/dex:v2.36.0
docker pull ghcr.io/dexidp/dex:v2.37.0
docker pull argoproj/argo-cd-ci-builder:v1.0.0
docker pull redis:7.0.11-alpine
docker pull redis:7.0.14-alpine
- name: Create target directory for binaries in the build-process
run: |
mkdir -p dist

View File

@@ -74,9 +74,7 @@ jobs:
go-version: ${{ inputs.go-version }}
- name: Install cosign
uses: sigstore/cosign-installer@c3667d99424e7e6047999fb6246c0da843953c65 # v3.0.1
with:
cosign-release: 'v2.0.0'
uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 # v3.4.0
- uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0
- uses: docker/setup-buildx-action@4b4e9c3e2d4531116a6f8ba8e71fc6e2cb6e6c8c # v2.5.0

View File

@@ -52,7 +52,7 @@ jobs:
uses: ./.github/workflows/image-reuse.yaml
with:
# Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations)
go-version: 1.19
go-version: 1.20
platforms: ${{ needs.set-vars.outputs.platforms }}
push: false
@@ -68,7 +68,7 @@ jobs:
quay_image_name: quay.io/argoproj/argocd:latest
ghcr_image_name: ghcr.io/argoproj/argo-cd/argocd:${{ needs.set-vars.outputs.image-tag }}
# Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations)
go-version: 1.19
go-version: 1.20
platforms: ${{ needs.set-vars.outputs.platforms }}
push: true
secrets:
@@ -77,22 +77,21 @@ jobs:
ghcr_username: ${{ github.actor }}
ghcr_password: ${{ secrets.GITHUB_TOKEN }}
build-and-publish-provenance: # Push attestations to GHCR, latest image is polluting quay.io
needs:
- build-and-publish
build-and-publish-provenance:
needs: [build-and-publish]
permissions:
actions: read # for detecting the Github Actions environment.
id-token: write # for creating OIDC tokens for signing.
packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.5.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.10.0
with:
image: ghcr.io/argoproj/argo-cd/argocd
image: quay.io/argoproj/argocd
digest: ${{ needs.build-and-publish.outputs.image-digest }}
registry-username: ${{ github.actor }}
secrets:
registry-password: ${{ secrets.GITHUB_TOKEN }}
registry-username: ${{ secrets.RELEASE_QUAY_USERNAME }}
registry-password: ${{ secrets.RELEASE_QUAY_TOKEN }}
Deploy:
needs:

View File

@@ -2,11 +2,15 @@ name: "Lint PR"
on:
pull_request_target:
types: [opened, edited, reopened, synchronize]
types:
- opened
- edited
- synchronize
# IMPORTANT: No checkout actions, scripts, or builds should be added to this workflow. Permissions should always be used
# with extreme caution. https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target
permissions: {}
# with extreme caution.
permissions:
contents: read
# PR updates can happen in quick succession leading to this
# workflow being trigger a number of times. This limits it
@@ -14,16 +18,24 @@ permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
validate:
main:
permissions:
contents: read
pull-requests: read
name: Validate PR Title
pull-requests: read # for amannn/action-semantic-pull-request to analyze PRs
statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR
name: Validate PR title
runs-on: ubuntu-latest
steps:
- uses: thehanimo/pr-title-checker@cdafc664bf9b25678d4e6df76ff67b2fe21bb5d2 # v1.3.7
# IMPORTANT: Carefully review changes when updating this action. Using the pull_request_target event requires caution.
- uses: amannn/action-semantic-pull-request@b6bca70dcd3e56e896605356ce09b76f7e1e0d39 # v5.1.0
with:
types: |
feat
fix
docs
test
ci
chore
[Bot] docs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
configuration_path: ".github/pr-title-checker-config.json"

View File

@@ -10,7 +10,7 @@ on:
permissions: {}
env:
GOLANG_VERSION: '1.19' # Note: go-version must also be set in job argocd-image.with.go-version
GOLANG_VERSION: '1.20' # Note: go-version must also be set in job argocd-image.with.go-version
jobs:
argocd-image:
@@ -23,7 +23,7 @@ jobs:
with:
quay_image_name: quay.io/argoproj/argocd:${{ github.ref_name }}
# Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations)
go-version: 1.19
go-version: 1.20
platforms: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
push: true
secrets:
@@ -38,7 +38,7 @@ jobs:
packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
if: github.repository == 'argoproj/argo-cd'
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.5.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.10.0
with:
image: quay.io/argoproj/argocd
digest: ${{ needs.argocd-image.outputs.image-digest }}
@@ -120,7 +120,7 @@ jobs:
contents: write # Needed for release uploads
if: github.repository == 'argoproj/argo-cd'
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.5.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0
with:
base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
provenance-name: "argocd-cli.intoto.jsonl"
@@ -149,9 +149,9 @@ jobs:
go-version: ${{ env.GOLANG_VERSION }}
- name: Install cosign
uses: sigstore/cosign-installer@c3667d99424e7e6047999fb6246c0da843953c65 # v3.0.1
uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 # v3.2.0
with:
cosign-release: 'v2.0.0'
cosign-release: 'v2.2.1'
- name: Generate SBOM (spdx)
id: spdx-builder
@@ -197,7 +197,21 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: |
/tmp/sbom.tar.*
/tmp/sbom.tar.gz
sbom-provenance:
needs: [generate-sbom]
permissions:
actions: read # for detecting the Github Actions environment
id-token: write # Needed for provenance signing and ID
contents: write # Needed for release uploads
if: github.repository == 'argoproj/argo-cd'
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0
with:
base64-subjects: "${{ needs.generate-sbom.outputs.hashes }}"
provenance-name: "argocd-sbom.intoto.jsonl"
upload-assets: true
post-release:
needs:

View File

@@ -35,7 +35,7 @@ jobs:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@80e868c13c90f172d68d1f4501dee99e2479f7af # v2.1.3
uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2
with:
results_file: results.sarif
results_format: sarif

View File

@@ -1,10 +1,10 @@
ARG BASE_IMAGE=docker.io/library/ubuntu:22.04@sha256:9a0bdde4188b896a372804be2384015e90e3f84906b750c1a53539b585fbbe7f
ARG BASE_IMAGE=docker.io/library/ubuntu:22.04@sha256:0bced47fffa3361afa981854fcabcd4577cd43cebbb808cea2b1f33a3dd7f508
####################################################################################################
# Builder image
# Initial stage which pulls prepares build dependencies and CLI tooling we need for our final image
# Also used as the image in CI jobs so needs all dependencies
####################################################################################################
FROM docker.io/library/golang:1.19.6@sha256:7ce31d15a3a4dbf20446cccffa4020d3a2974ad2287d96123f55caf22c7adb71 AS builder
FROM docker.io/library/golang:1.20.10@sha256:077ff85b374b23916b4b41835e242e5a3ddad9fc537ea7e980f230431747d245 AS builder
RUN echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list
@@ -101,7 +101,7 @@ RUN HOST_ARCH=$TARGETARCH NODE_ENV='production' NODE_ONLINE_ENV='online' NODE_OP
####################################################################################################
# Argo CD Build stage which performs the actual build of Argo CD binaries
####################################################################################################
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.19.6@sha256:7ce31d15a3a4dbf20446cccffa4020d3a2974ad2287d96123f55caf22c7adb71 AS argocd-build
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.20.10@sha256:077ff85b374b23916b4b41835e242e5a3ddad9fc537ea7e980f230431747d245 AS argocd-build
WORKDIR /go/src/github.com/argoproj/argo-cd

View File

@@ -146,8 +146,7 @@ override LDFLAGS += \
-X ${PACKAGE}.buildDate=${BUILD_DATE} \
-X ${PACKAGE}.gitCommit=${GIT_COMMIT} \
-X ${PACKAGE}.gitTreeState=${GIT_TREE_STATE}\
-X ${PACKAGE}.kubectlVersion=${KUBECTL_VERSION}\
-X "${PACKAGE}.extraBuildInfo=${EXTRA_BUILD_INFO}"
-X ${PACKAGE}.kubectlVersion=${KUBECTL_VERSION}
ifeq (${STATIC_BUILD}, true)
override LDFLAGS += -extldflags "-static"

View File

@@ -8,5 +8,5 @@ ui: sh -c 'cd ui && ${ARGOCD_E2E_YARN_CMD:-yarn} start'
git-server: test/fixture/testrepos/start-git.sh
helm-registry: test/fixture/testrepos/start-helm-registry.sh
dev-mounter: [[ "$ARGOCD_E2E_TEST" != "true" ]] && go run hack/dev-mounter/main.go --configmap argocd-ssh-known-hosts-cm=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} --configmap argocd-tls-certs-cm=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} --configmap argocd-gpg-keys-cm=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source}
applicationset-controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-applicationset-controller $COMMAND --loglevel debug --metrics-addr localhost:12345 --probe-addr localhost:12346 --argocd-repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
notification: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_BINARY_NAME=argocd-notifications $COMMAND --loglevel debug"
applicationset-controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_ASK_PASS_SOCK=/tmp/applicationset-ask-pass.sock ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-applicationset-controller $COMMAND --loglevel debug --metrics-addr localhost:12345 --probe-addr localhost:12346 --argocd-repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
notification: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_BINARY_NAME=argocd-notifications $COMMAND --loglevel debug"

View File

@@ -1,7 +1,6 @@
**Releases:**
[![Release Version](https://img.shields.io/github/v/release/argoproj/argo-cd?label=argo-cd)](https://github.com/argoproj/argo-cd/releases/latest)
[![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/argo-cd)](https://artifacthub.io/packages/helm/argo/argo-cd)
[![SLSA 3](https://slsa.dev/images/gh-badge-level3.svg)](https://slsa.dev)
**Code:**
[![Integration tests](https://github.com/argoproj/argo-cd/workflows/Integration%20tests/badge.svg?branch=master)](https://github.com/argoproj/argo-cd/actions?query=workflow%3A%22Integration+tests%22)

View File

@@ -35,9 +35,7 @@ impact on Argo CD before opening an issue at least roughly.
## Supported Versions
We currently support the most recent release (`N`, e.g. `1.8`) and the release
previous to the most recent one (`N-1`, e.g. `1.7`). With the release of
`N+1`, `N-1` drops out of support and `N` becomes `N-1`.
We currently support the last 3 minor versions of Argo CD with security and bug fixes.
We regularly perform patch releases (e.g. `1.8.5` and `1.7.12`) for the
supported versions, which will contain fixes for security vulnerabilities and

View File

@@ -14,7 +14,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Adyen](https://www.adyen.com)
1. [AirQo](https://airqo.net/)
1. [Akuity](https://akuity.io/)
1. [Albert Heijn](https://ah.nl/)
1. [Alibaba Group](https://www.alibabagroup.com/)
1. [Allianz Direct](https://www.allianzdirect.de/)
1. [Amadeus IT Group](https://amadeus.com/)
@@ -25,6 +24,7 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Arctiq Inc.](https://www.arctiq.ca)
1. [ARZ Allgemeines Rechenzentrum GmbH](https://www.arz.at/)
1. [Axual B.V.](https://axual.com)
1. [Back Market](https://www.backmarket.com)
1. [Baloise](https://www.baloise.com)
1. [BCDevExchange DevOps Platform](https://bcdevexchange.org/DevOpsPlatform)
1. [Beat](https://thebeat.co/en/)
@@ -96,6 +96,7 @@ Currently, the following organizations are **officially** using Argo CD:
1. [gloat](https://gloat.com/)
1. [GLOBIS](https://globis.com)
1. [Glovo](https://www.glovoapp.com)
1. [GlueOps](https://glueops.dev)
1. [GMETRI](https://gmetri.com/)
1. [Gojek](https://www.gojek.io/)
1. [Greenpass](https://www.greenpass.com.br/)
@@ -126,7 +127,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Karrot](https://www.daangn.com/)
1. [Kasa](https://kasa.co.kr/)
1. [Keeeb](https://www.keeeb.com/)
1. [KelkooGroup](https://www.kelkoogroup.com)
1. [Keptn](https://keptn.sh)
1. [Kinguin](https://www.kinguin.net/)
1. [KintoHub](https://www.kintohub.com/)
@@ -139,7 +139,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Lightricks](https://www.lightricks.com/)
1. [LINE](https://linecorp.com/en/)
1. [Loom](https://www.loom.com/)
1. [Lucid Motors](https://www.lucidmotors.com/)
1. [Lytt](https://www.lytt.co/)
1. [Magic Leap](https://www.magicleap.com/)
1. [Majid Al Futtaim](https://www.majidalfuttaim.com/)
@@ -154,7 +153,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Metanet](http://www.metanet.co.kr/en/)
1. [MindSpore](https://mindspore.cn)
1. [Mirantis](https://mirantis.com/)
1. [Mission Lane](https://missionlane.com)
1. [mixi Group](https://mixi.co.jp/)
1. [Moengage](https://www.moengage.com/)
1. [Money Forward](https://corp.moneyforward.com/en/)
@@ -189,6 +187,7 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Patreon](https://www.patreon.com/)
1. [PayPay](https://paypay.ne.jp/)
1. [Peloton Interactive](https://www.onepeloton.com/)
1. [PGS](https://www.pgs.com)
1. [Pigment](https://www.gopigment.com/)
1. [Pipefy](https://www.pipefy.com/)
1. [Pismo](https://pismo.io/)
@@ -196,7 +195,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Polarpoint.io](https://polarpoint.io)
1. [PostFinance](https://github.com/postfinance)
1. [Preferred Networks](https://preferred.jp/en/)
1. [Previder BV](https://previder.nl)
1. [Productboard](https://www.productboard.com/)
1. [Prudential](https://prudential.com.sg)
1. [PUBG](https://www.pubg.com)
@@ -222,6 +220,7 @@ Currently, the following organizations are **officially** using Argo CD:
1. [SI Analytics](https://si-analytics.ai)
1. [Skit](https://skit.ai/)
1. [Skyscanner](https://www.skyscanner.net/)
1. [Smart Pension](https://www.smartpension.co.uk/)
1. [Smilee.io](https://smilee.io)
1. [Smood.ch](https://www.smood.ch/)
1. [Snapp](https://snapp.ir/)
@@ -260,6 +259,7 @@ Currently, the following organizations are **officially** using Argo CD:
1. [ungleich.ch](https://ungleich.ch/)
1. [Unifonic Inc](https://www.unifonic.com/)
1. [Universidad Mesoamericana](https://www.umes.edu.gt/)
1. [Urbantz](https://urbantz.com/)
1. [Vectra](https://www.vectra.ai)
1. [Viaduct](https://www.viaduct.ai/)
1. [Vinted](https://vinted.com/)

View File

@@ -1 +1 @@
2.8.0
2.7.18

View File

@@ -148,19 +148,30 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
// appSyncMap tracks which apps will be synced during this reconciliation.
appSyncMap := map[string]bool{}
if r.EnableProgressiveSyncs && applicationSetInfo.Spec.Strategy != nil {
applications, err := r.getCurrentApplications(ctx, applicationSetInfo)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to get current applications for application set: %w", err)
}
if r.EnableProgressiveSyncs {
if applicationSetInfo.Spec.Strategy == nil && len(applicationSetInfo.Status.ApplicationStatus) > 0 {
// If appset used progressive sync but stopped, clean up the progressive sync application statuses
log.Infof("Removing %v unnecessary AppStatus entries from ApplicationSet %v", len(applicationSetInfo.Status.ApplicationStatus), applicationSetInfo.Name)
for _, app := range applications {
appMap[app.Name] = app
}
err := r.setAppSetApplicationStatus(ctx, &applicationSetInfo, []argov1alpha1.ApplicationSetApplicationStatus{})
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to clear previous AppSet application statuses for %v: %w", applicationSetInfo.Name, err)
}
} else if applicationSetInfo.Spec.Strategy != nil {
// appset uses progressive sync
applications, err := r.getCurrentApplications(ctx, applicationSetInfo)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to get current applications for application set: %w", err)
}
appSyncMap, err = r.performProgressiveSyncs(ctx, applicationSetInfo, applications, desiredApplications, appMap)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to perform progressive sync reconciliation for application set: %w", err)
for _, app := range applications {
appMap[app.Name] = app
}
appSyncMap, err = r.performProgressiveSyncs(ctx, applicationSetInfo, applications, desiredApplications, appMap)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to perform progressive sync reconciliation for application set: %w", err)
}
}
}
@@ -277,7 +288,6 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
}
requeueAfter := r.getMinRequeueAfter(&applicationSetInfo)
logCtx.WithField("requeueAfter", requeueAfter).Info("end reconcile")
if len(validateErrors) == 0 {
if err := r.setApplicationSetStatusCondition(ctx,
@@ -291,8 +301,13 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
); err != nil {
return ctrl.Result{}, err
}
} else if requeueAfter == time.Duration(0) {
// Ensure that the request is requeued if there are validation errors.
requeueAfter = ReconcileRequeueOnValidationError
}
logCtx.WithField("requeueAfter", requeueAfter).Info("end reconcile")
return ctrl.Result{
RequeueAfter: requeueAfter,
}, nil
@@ -565,6 +580,9 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
appLog := log.WithFields(log.Fields{"app": generatedApp.Name, "appSet": applicationSet.Name})
generatedApp.Namespace = applicationSet.Namespace
// Normalize to avoid fighting with the application controller.
generatedApp.Spec = *argoutil.NormalizeApplicationSpec(&generatedApp.Spec)
found := &argov1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: generatedApp.Name,
@@ -851,45 +869,21 @@ func (r *ApplicationSetReconciler) buildAppDependencyList(ctx context.Context, a
selected := true // default to true, assuming the current Application is a match for the given step matchExpression
allNotInMatched := true // needed to support correct AND behavior between multiple NotIn MatchExpressions
notInUsed := false // since we default to allNotInMatched == true, track whether a NotIn expression was actually used
for _, matchExpression := range step.MatchExpressions {
if matchExpression.Operator == "In" {
if val, ok := app.Labels[matchExpression.Key]; ok {
valueMatched := labelMatchedExpression(val, matchExpression)
if val, ok := app.Labels[matchExpression.Key]; ok {
valueMatched := labelMatchedExpression(val, matchExpression)
if !valueMatched { // none of the matchExpression values was a match with the Application'ss labels
selected = false
break
}
} else {
selected = false // no matching label key with In means this Application will not be included in the current step
if !valueMatched { // none of the matchExpression values was a match with the Application'ss labels
selected = false
break
}
} else if matchExpression.Operator == "NotIn" {
notInUsed = true // a NotIn selector was used in this matchExpression
if val, ok := app.Labels[matchExpression.Key]; ok {
valueMatched := labelMatchedExpression(val, matchExpression)
if !valueMatched { // none of the matchExpression values was a match with the Application's labels
allNotInMatched = false
}
} else {
allNotInMatched = false // no matching label key with NotIn means this Application may still be included in the current step
}
} else { // handle invalid operator selection
log.Warnf("skipping AppSet rollingUpdate step Application selection for %q, invalid matchExpression operator provided: %q ", applicationSet.Name, matchExpression.Operator)
selected = false
} else if matchExpression.Operator == "In" {
selected = false // no matching label key with "In" operator means this Application will not be included in the current step
break
}
}
if notInUsed && allNotInMatched { // check if all NotIn Expressions matched, if so exclude this Application
selected = false
}
if selected {
appDependencyList[i] = append(appDependencyList[i], app.Name)
if val, ok := appStepMap[app.Name]; ok {
@@ -905,11 +899,20 @@ func (r *ApplicationSetReconciler) buildAppDependencyList(ctx context.Context, a
}
func labelMatchedExpression(val string, matchExpression argov1alpha1.ApplicationMatchExpression) bool {
valueMatched := false
if matchExpression.Operator != "In" && matchExpression.Operator != "NotIn" {
log.Errorf("skipping AppSet rollingUpdate step Application selection, invalid matchExpression operator provided: %q ", matchExpression.Operator)
return false
}
// if operator == In, default to false
// if operator == NotIn, default to true
valueMatched := matchExpression.Operator == "NotIn"
for _, value := range matchExpression.Values {
if val == value {
valueMatched = true
break
// first "In" match returns true
// first "NotIn" match returns false
return matchExpression.Operator == "In"
}
}
return valueMatched
@@ -1042,7 +1045,12 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
}
if currentAppStatus.Status == "Pending" {
if operationPhaseString == "Succeeded" && app.Status.OperationState.StartedAt.After(currentAppStatus.LastTransitionTime.Time) {
// check for successful syncs started less than 10s before the Application transitioned to Pending
// this covers race conditions where syncs initiated by RollingSync miraculously have a sync time before the transition to Pending state occurred (could be a few seconds)
if operationPhaseString == "Succeeded" && app.Status.OperationState.StartedAt.Add(time.Duration(10)*time.Second).After(currentAppStatus.LastTransitionTime.Time) {
if !app.Status.OperationState.StartedAt.After(currentAppStatus.LastTransitionTime.Time) {
log.Warnf("Application %v was synced less than 10s prior to entering Pending status, we'll assume the AppSet controller triggered this sync and update its status to Progressing", app.Name)
}
log.Infof("Application %v has completed a sync successfully, updating its ApplicationSet status to Progressing", app.Name)
currentAppStatus.LastTransitionTime = &now
currentAppStatus.Status = "Progressing"
@@ -1220,30 +1228,30 @@ func findApplicationStatusIndex(appStatuses []argov1alpha1.ApplicationSetApplica
// with any new/changed Application statuses.
func (r *ApplicationSetReconciler) setAppSetApplicationStatus(ctx context.Context, applicationSet *argov1alpha1.ApplicationSet, applicationStatuses []argov1alpha1.ApplicationSetApplicationStatus) error {
needToUpdateStatus := false
for i := range applicationStatuses {
appStatus := applicationStatuses[i]
idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, appStatus.Application)
if idx == -1 {
needToUpdateStatus = true
break
}
currentStatus := applicationSet.Status.ApplicationStatus[idx]
if currentStatus.Message != appStatus.Message || currentStatus.Status != appStatus.Status {
needToUpdateStatus = true
break
if len(applicationStatuses) != len(applicationSet.Status.ApplicationStatus) {
needToUpdateStatus = true
} else {
for i := range applicationStatuses {
appStatus := applicationStatuses[i]
idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, appStatus.Application)
if idx == -1 {
needToUpdateStatus = true
break
}
currentStatus := applicationSet.Status.ApplicationStatus[idx]
if currentStatus.Message != appStatus.Message || currentStatus.Status != appStatus.Status || currentStatus.Step != appStatus.Step {
needToUpdateStatus = true
break
}
}
}
if needToUpdateStatus {
// fetch updated Application Set object before updating it
namespacedName := types.NamespacedName{Namespace: applicationSet.Namespace, Name: applicationSet.Name}
if err := r.Get(ctx, namespacedName, applicationSet); err != nil {
if client.IgnoreNotFound(err) != nil {
return nil
}
return fmt.Errorf("error fetching updated application set: %v", err)
}
// rebuild ApplicationStatus from scratch, we don't need any previous status history
applicationSet.Status.ApplicationStatus = []argov1alpha1.ApplicationSetApplicationStatus{}
for i := range applicationStatuses {
applicationSet.Status.SetApplicationStatus(applicationStatuses[i])
}
@@ -1349,7 +1357,7 @@ func getOwnsHandlerPredicates(enableProgressiveSyncs bool) predicate.Funcs {
return false
}
requeue := shouldRequeueApplicationSet(appOld, appNew, enableProgressiveSyncs)
log.Debugf("requeue: %t caused by application %s", requeue, appNew.Name)
log.Debugf("requeue: %t caused by application %s\n", requeue, appNew.Name)
return requeue
},
GenericFunc: func(e event.GenericEvent) bool {

View File

@@ -365,6 +365,7 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
Namespace: "namespace",
ResourceVersion: "1",
},
Spec: v1alpha1.ApplicationSpec{Project: "default"},
},
},
},
@@ -892,6 +893,60 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
},
},
},
}, {
name: "Ensure that the app spec is normalized before applying",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
Spec: v1alpha1.ApplicationSetSpec{
Template: v1alpha1.ApplicationSetTemplate{
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Source: &v1alpha1.ApplicationSource{
Directory: &v1alpha1.ApplicationSourceDirectory{
Jsonnet: v1alpha1.ApplicationSourceJsonnet{},
},
},
},
},
},
},
desiredApps: []v1alpha1.Application{
{
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
},
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Source: &v1alpha1.ApplicationSource{
Directory: &v1alpha1.ApplicationSourceDirectory{
Jsonnet: v1alpha1.ApplicationSourceJsonnet{},
},
},
},
},
},
expected: []v1alpha1.Application{
{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
Namespace: "namespace",
ResourceVersion: "1",
},
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Source: &v1alpha1.ApplicationSource{
// Directory and jsonnet block are removed
},
},
},
},
},
} {
@@ -1223,13 +1278,15 @@ func TestCreateApplications(t *testing.T) {
err = v1alpha1.AddToScheme(scheme)
assert.Nil(t, err)
for _, c := range []struct {
testCases := []struct {
name string
appSet v1alpha1.ApplicationSet
existsApps []v1alpha1.Application
apps []v1alpha1.Application
expected []v1alpha1.Application
}{
{
name: "no existing apps",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
@@ -1255,10 +1312,14 @@ func TestCreateApplications(t *testing.T) {
Namespace: "namespace",
ResourceVersion: "1",
},
Spec: v1alpha1.ApplicationSpec{
Project: "default",
},
},
},
},
{
name: "existing apps",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
@@ -1316,6 +1377,7 @@ func TestCreateApplications(t *testing.T) {
},
},
{
name: "existing apps with different project",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
@@ -1372,39 +1434,42 @@ func TestCreateApplications(t *testing.T) {
},
},
},
} {
initObjs := []crtclient.Object{&c.appSet}
for _, a := range c.existsApps {
err = controllerutil.SetControllerReference(&c.appSet, &a, scheme)
assert.Nil(t, err)
initObjs = append(initObjs, &a)
}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).Build()
r := ApplicationSetReconciler{
Client: client,
Scheme: scheme,
Recorder: record.NewFakeRecorder(len(initObjs) + len(c.expected)),
}
err = r.createInCluster(context.TODO(), c.appSet, c.apps)
assert.Nil(t, err)
for _, obj := range c.expected {
got := &v1alpha1.Application{}
_ = client.Get(context.Background(), crtclient.ObjectKey{
Namespace: obj.Namespace,
Name: obj.Name,
}, got)
err = controllerutil.SetControllerReference(&c.appSet, &obj, r.Scheme)
assert.Nil(t, err)
assert.Equal(t, obj, *got)
}
}
for _, c := range testCases {
t.Run(c.name, func(t *testing.T) {
initObjs := []crtclient.Object{&c.appSet}
for _, a := range c.existsApps {
err = controllerutil.SetControllerReference(&c.appSet, &a, scheme)
assert.Nil(t, err)
initObjs = append(initObjs, &a)
}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).Build()
r := ApplicationSetReconciler{
Client: client,
Scheme: scheme,
Recorder: record.NewFakeRecorder(len(initObjs) + len(c.expected)),
}
err = r.createInCluster(context.TODO(), c.appSet, c.apps)
assert.Nil(t, err)
for _, obj := range c.expected {
got := &v1alpha1.Application{}
_ = client.Get(context.Background(), crtclient.ObjectKey{
Namespace: obj.Namespace,
Name: obj.Name,
}, got)
err = controllerutil.SetControllerReference(&c.appSet, &obj, r.Scheme)
assert.Nil(t, err)
assert.Equal(t, obj, *got)
}
})
}
}
func TestDeleteInCluster(t *testing.T) {
@@ -1928,7 +1993,7 @@ func TestReconcilerValidationErrorBehaviour(t *testing.T) {
// Verify that on validation error, no error is returned, but the object is requeued
res, err := r.Reconcile(context.Background(), req)
assert.Nil(t, err)
assert.True(t, res.RequeueAfter == 0)
assert.True(t, res.RequeueAfter == ReconcileRequeueOnValidationError)
var app v1alpha1.Application
@@ -2277,55 +2342,104 @@ func TestSetApplicationSetApplicationStatus(t *testing.T) {
err = v1alpha1.AddToScheme(scheme)
assert.Nil(t, err)
appSet := v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "argocd",
},
Spec: v1alpha1.ApplicationSetSpec{
Generators: []v1alpha1.ApplicationSetGenerator{
{List: &v1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{{
Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
}},
}},
},
Template: v1alpha1.ApplicationSetTemplate{},
},
}
appStatuses := []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "my-application",
LastTransitionTime: &metav1.Time{},
Message: "testing SetApplicationSetApplicationStatus to Healthy",
Status: "Healthy",
},
}
kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
argoDBMock := dbmocks.ArgoDB{}
argoObjs := []runtime.Object{}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).Build()
r := ApplicationSetReconciler{
Client: client,
Scheme: scheme,
Renderer: &utils.Render{},
Recorder: record.NewFakeRecorder(1),
Generators: map[string]generators.Generator{
"List": generators.NewListGenerator(),
for _, cc := range []struct {
name string
appSet v1alpha1.ApplicationSet
appStatuses []v1alpha1.ApplicationSetApplicationStatus
expectedAppStatuses []v1alpha1.ApplicationSetApplicationStatus
}{
{
name: "sets a single appstatus",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "argocd",
},
Spec: v1alpha1.ApplicationSetSpec{
Generators: []v1alpha1.ApplicationSetGenerator{
{List: &v1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{{
Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
}},
}},
},
Template: v1alpha1.ApplicationSetTemplate{},
},
},
appStatuses: []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "app1",
Message: "testing SetApplicationSetApplicationStatus to Healthy",
Status: "Healthy",
},
},
expectedAppStatuses: []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "app1",
Message: "testing SetApplicationSetApplicationStatus to Healthy",
Status: "Healthy",
},
},
},
ArgoDB: &argoDBMock,
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
KubeClientset: kubeclientset,
{
name: "removes an appstatus",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "argocd",
},
Spec: v1alpha1.ApplicationSetSpec{
Generators: []v1alpha1.ApplicationSetGenerator{
{List: &v1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{{
Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
}},
}},
},
Template: v1alpha1.ApplicationSetTemplate{},
},
Status: v1alpha1.ApplicationSetStatus{
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "app1",
Message: "testing SetApplicationSetApplicationStatus to Healthy",
Status: "Healthy",
},
},
},
},
appStatuses: []v1alpha1.ApplicationSetApplicationStatus{},
expectedAppStatuses: nil,
},
} {
t.Run(cc.name, func(t *testing.T) {
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&cc.appSet).Build()
r := ApplicationSetReconciler{
Client: client,
Scheme: scheme,
Renderer: &utils.Render{},
Recorder: record.NewFakeRecorder(1),
Generators: map[string]generators.Generator{
"List": generators.NewListGenerator(),
},
ArgoDB: &argoDBMock,
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
KubeClientset: kubeclientset,
}
err = r.setAppSetApplicationStatus(context.TODO(), &cc.appSet, cc.appStatuses)
assert.Nil(t, err)
assert.Equal(t, cc.expectedAppStatuses, cc.appSet.Status.ApplicationStatus)
})
}
err = r.setAppSetApplicationStatus(context.TODO(), &appSet, appStatuses)
assert.Nil(t, err)
assert.Len(t, appSet.Status.ApplicationStatus, 1)
}
func TestBuildAppDependencyList(t *testing.T) {
@@ -2792,7 +2906,7 @@ func TestBuildAppDependencyList(t *testing.T) {
},
},
{
name: "multiple 'NotIn' selectors only match Applications with all labels",
name: "multiple 'NotIn' selectors remove Applications with mising labels on any match",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
@@ -2846,10 +2960,88 @@ func TestBuildAppDependencyList(t *testing.T) {
},
},
expectedList: [][]string{
{"app-qa1"},
{},
},
expectedStepMap: map[string]int{},
},
{
name: "multiple 'NotIn' selectors filter all matching Applications",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "argocd",
},
Spec: v1alpha1.ApplicationSetSpec{
Strategy: &v1alpha1.ApplicationSetStrategy{
Type: "RollingSync",
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
Steps: []v1alpha1.ApplicationSetRolloutStep{
{
MatchExpressions: []v1alpha1.ApplicationMatchExpression{
{
Key: "region",
Operator: "NotIn",
Values: []string{
"us-east-2",
},
},
{
Key: "env",
Operator: "NotIn",
Values: []string{
"qa",
},
},
},
},
},
},
},
},
},
apps: []v1alpha1.Application{
{
ObjectMeta: metav1.ObjectMeta{
Name: "app-qa1",
Labels: map[string]string{
"env": "qa",
"region": "us-east-1",
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "app-qa2",
Labels: map[string]string{
"env": "qa",
"region": "us-east-2",
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "app-prod1",
Labels: map[string]string{
"env": "prod",
"region": "us-east-1",
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "app-prod2",
Labels: map[string]string{
"env": "prod",
"region": "us-east-2",
},
},
},
},
expectedList: [][]string{
{"app-prod1"},
},
expectedStepMap: map[string]int{
"app-qa1": 0,
"app-prod1": 0,
},
},
{
@@ -4065,6 +4257,63 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
},
},
},
{
name: "progresses a pending application with a successful sync <1s ago to progressing",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "argocd",
},
Spec: v1alpha1.ApplicationSetSpec{
Strategy: &v1alpha1.ApplicationSetStrategy{
Type: "RollingSync",
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
},
},
Status: v1alpha1.ApplicationSetStatus{
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "app1",
LastTransitionTime: &metav1.Time{
Time: time.Now(),
},
Message: "",
Status: "Pending",
Step: "1",
},
},
},
},
apps: []v1alpha1.Application{
{
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
},
Status: v1alpha1.ApplicationStatus{
Health: v1alpha1.HealthStatus{
Status: health.HealthStatusDegraded,
},
OperationState: &v1alpha1.OperationState{
Phase: common.OperationSucceeded,
StartedAt: metav1.Time{
Time: time.Now().Add(time.Duration(-1) * time.Second),
},
},
Sync: v1alpha1.SyncStatus{
Status: v1alpha1.SyncStatusCodeSynced,
},
},
},
},
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "app1",
Message: "Application resource completed a sync successfully, updating status from Pending to Progressing.",
Status: "Progressing",
Step: "1",
},
},
},
{
name: "does not progresses a pending application with an old successful sync to progressing",
appSet: v1alpha1.ApplicationSet{
@@ -4083,7 +4332,7 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
{
Application: "app1",
LastTransitionTime: &metav1.Time{
Time: time.Now().Add(time.Duration(-1) * time.Minute),
Time: time.Now(),
},
Message: "Application moved to Pending status, watching for the Application resource to start Progressing.",
Status: "Pending",
@@ -4104,7 +4353,7 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
OperationState: &v1alpha1.OperationState{
Phase: common.OperationSucceeded,
StartedAt: metav1.Time{
Time: time.Now().Add(time.Duration(-2) * time.Minute),
Time: time.Now().Add(time.Duration(-11) * time.Second),
},
},
Sync: v1alpha1.SyncStatus{
@@ -4122,6 +4371,63 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
},
},
},
{
name: "removes the appStatus for applications that no longer exist",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "argocd",
},
Spec: v1alpha1.ApplicationSetSpec{
Strategy: &v1alpha1.ApplicationSetStrategy{
Type: "RollingSync",
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
},
},
Status: v1alpha1.ApplicationSetStatus{
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "app1",
Message: "Application has pending changes, setting status to Waiting.",
Status: "Waiting",
Step: "1",
},
{
Application: "app2",
Message: "Application has pending changes, setting status to Waiting.",
Status: "Waiting",
Step: "1",
},
},
},
},
apps: []v1alpha1.Application{
{
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
},
Status: v1alpha1.ApplicationStatus{
Health: v1alpha1.HealthStatus{
Status: health.HealthStatusHealthy,
},
OperationState: &v1alpha1.OperationState{
Phase: common.OperationSucceeded,
},
Sync: v1alpha1.SyncStatus{
Status: v1alpha1.SyncStatusCodeSynced,
},
},
},
},
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "app1",
Message: "Application resource is already Healthy, updating status from Waiting to Healthy.",
Status: "Healthy",
Step: "1",
},
},
},
} {
t.Run(cc.name, func(t *testing.T) {

View File

@@ -139,11 +139,7 @@ func nestedGeneratorHasClusterGenerator(nested argoprojiov1alpha1.ApplicationSet
return false, fmt.Errorf("unable to get nested matrix generator: %w", err)
}
if nestedMatrix != nil {
hasClusterGenerator, err := nestedGeneratorsHaveClusterGenerator(nestedMatrix.ToMatrixGenerator().Generators)
if err != nil {
return false, fmt.Errorf("error evaluating nested matrix generator: %w", err)
}
return hasClusterGenerator, nil
return nestedGeneratorsHaveClusterGenerator(nestedMatrix.ToMatrixGenerator().Generators)
}
}
@@ -153,11 +149,7 @@ func nestedGeneratorHasClusterGenerator(nested argoprojiov1alpha1.ApplicationSet
return false, fmt.Errorf("unable to get nested merge generator: %w", err)
}
if nestedMerge != nil {
hasClusterGenerator, err := nestedGeneratorsHaveClusterGenerator(nestedMerge.ToMergeGenerator().Generators)
if err != nil {
return false, fmt.Errorf("error evaluating nested merge generator: %w", err)
}
return hasClusterGenerator, nil
return nestedGeneratorsHaveClusterGenerator(nestedMerge.ToMergeGenerator().Generators)
}
}

View File

@@ -573,68 +573,3 @@ type mockAddRateLimitingInterface struct {
errorOccurred bool
addedItems []ctrl.Request
}
func TestNestedGeneratorHasClusterGenerator_NestedClusterGenerator(t *testing.T) {
nested := argov1alpha1.ApplicationSetNestedGenerator{
Clusters: &argov1alpha1.ClusterGenerator{},
}
hasClusterGenerator, err := nestedGeneratorHasClusterGenerator(nested)
assert.Nil(t, err)
assert.True(t, hasClusterGenerator)
}
func TestNestedGeneratorHasClusterGenerator_NestedMergeGenerator(t *testing.T) {
nested := argov1alpha1.ApplicationSetNestedGenerator{
Merge: &apiextensionsv1.JSON{
Raw: []byte(
`{
"generators": [
{
"clusters": {
"selector": {
"matchLabels": {
"argocd.argoproj.io/secret-type": "cluster"
}
}
}
}
]
}`,
),
},
}
hasClusterGenerator, err := nestedGeneratorHasClusterGenerator(nested)
assert.Nil(t, err)
assert.True(t, hasClusterGenerator)
}
func TestNestedGeneratorHasClusterGenerator_NestedMergeGeneratorWithInvalidJSON(t *testing.T) {
nested := argov1alpha1.ApplicationSetNestedGenerator{
Merge: &apiextensionsv1.JSON{
Raw: []byte(
`{
"generators": [
{
"clusters": {
"selector": {
"matchLabels": {
"argocd.argoproj.io/secret-type": "cluster"
}
}
}
}
]
`,
),
},
}
hasClusterGenerator, err := nestedGeneratorHasClusterGenerator(nested)
assert.NotNil(t, err)
assert.False(t, hasClusterGenerator)
}

View File

@@ -6,9 +6,9 @@ import (
"time"
"github.com/argoproj/argo-cd/v2/applicationset/generators"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
@@ -20,7 +20,7 @@ import (
)
func TestRequeueAfter(t *testing.T) {
mockServer := &mocks.Repos{}
mockServer := argoCDServiceMock{}
ctx := context.Background()
scheme := runtime.NewScheme()
err := argov1alpha1.AddToScheme(scheme)
@@ -150,3 +150,30 @@ func TestRequeueAfter(t *testing.T) {
})
}
}
type argoCDServiceMock struct {
mock *mock.Mock
}
func (a argoCDServiceMock) GetApps(ctx context.Context, repoURL string, revision string) ([]string, error) {
args := a.mock.Called(ctx, repoURL, revision)
return args.Get(0).([]string), args.Error(1)
}
func (a argoCDServiceMock) GetFiles(ctx context.Context, repoURL string, revision string, pattern string) (map[string][]byte, error) {
args := a.mock.Called(ctx, repoURL, revision, pattern)
return args.Get(0).(map[string][]byte), args.Error(1)
}
func (a argoCDServiceMock) GetFileContent(ctx context.Context, repoURL string, revision string, path string) ([]byte, error) {
args := a.mock.Called(ctx, repoURL, revision, path)
return args.Get(0).([]byte), args.Error(1)
}
func (a argoCDServiceMock) GetDirectories(ctx context.Context, repoURL string, revision string) ([]string, error) {
args := a.mock.Called(ctx, repoURL, revision)
return args.Get(0).([]string), args.Error(1)
}

View File

@@ -5,8 +5,8 @@ import (
"reflect"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
"github.com/jeremywohl/flatten"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
@@ -26,7 +26,10 @@ type TransformResult struct {
// Transform a spec generator to list of paramSets and a template
func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, allGenerators map[string]Generator, baseTemplate argoprojiov1alpha1.ApplicationSetTemplate, appSet *argoprojiov1alpha1.ApplicationSet, genParams map[string]interface{}) ([]TransformResult, error) {
selector, err := metav1.LabelSelectorAsSelector(requestedGenerator.Selector)
// This is a custom version of the `LabelSelectorAsSelector` that is in k8s.io/apimachinery. This has been copied
// verbatim from that package, with the difference that we do not have any restrictions on label values. This is done
// so that, among other things, we can match on cluster urls.
selector, err := utils.LabelSelectorAsSelector(requestedGenerator.Selector)
if err != nil {
return nil, fmt.Errorf("error parsing label selector: %w", err)
}
@@ -71,8 +74,17 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
}
var filterParams []map[string]interface{}
for _, param := range params {
flatParam, err := flattenParameters(param)
if err != nil {
log.WithError(err).WithField("generator", g).
Error("error flattening params")
if firstError == nil {
firstError = err
}
continue
}
if requestedGenerator.Selector != nil && !selector.Matches(labels.Set(keepOnlyStringValues(param))) {
if requestedGenerator.Selector != nil && !selector.Matches(labels.Set(flatParam)) {
continue
}
filterParams = append(filterParams, param)
@@ -87,18 +99,6 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
return res, firstError
}
func keepOnlyStringValues(in map[string]interface{}) map[string]string {
var out map[string]string = map[string]string{}
for key, value := range in {
if _, ok := value.(string); ok {
out[key] = value.(string)
}
}
return out
}
func GetRelevantGenerators(requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, generators map[string]Generator) []Generator {
var res []Generator
@@ -121,6 +121,20 @@ func GetRelevantGenerators(requestedGenerator *argoprojiov1alpha1.ApplicationSet
return res
}
func flattenParameters(in map[string]interface{}) (map[string]string, error) {
flat, err := flatten.Flatten(in, "", flatten.DotStyle)
if err != nil {
return nil, err
}
out := make(map[string]string, len(flat))
for k, v := range flat {
out[k] = fmt.Sprintf("%v", v)
}
return out, nil
}
func mergeGeneratorTemplate(g Generator, requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetTemplate argoprojiov1alpha1.ApplicationSetTemplate) (argoprojiov1alpha1.ApplicationSetTemplate, error) {
// Make a copy of the value from `GetTemplate()` before merge, rather than copying directly into
// the provided parameter (which will touch the original resource object returned by client-go)

View File

@@ -4,13 +4,13 @@ import (
"context"
"testing"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
testutils "github.com/argoproj/argo-cd/v2/applicationset/utils/test"
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/stretchr/testify/mock"
@@ -19,6 +19,8 @@ import (
kubefake "k8s.io/client-go/kubernetes/fake"
crtclient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func TestMatchValues(t *testing.T) {
@@ -62,6 +64,89 @@ func TestMatchValues(t *testing.T) {
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
var listGenerator = NewListGenerator()
var data = map[string]Generator{
"List": listGenerator,
}
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
GoTemplate: false,
},
}
results, err := Transform(argoprojiov1alpha1.ApplicationSetGenerator{
Selector: testCase.selector,
List: &argoprojiov1alpha1.ListGenerator{
Elements: testCase.elements,
Template: emptyTemplate(),
}},
data,
emptyTemplate(),
&applicationSetInfo, nil)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, results[0].Params)
})
}
}
func TestMatchValuesGoTemplate(t *testing.T) {
testCases := []struct {
name string
elements []apiextensionsv1.JSON
selector *metav1.LabelSelector
expected []map[string]interface{}
}{
{
name: "no filter",
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
selector: &metav1.LabelSelector{},
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url"}},
},
{
name: "nil",
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
selector: nil,
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url"}},
},
{
name: "values.foo should be foo but is ignore element",
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}},
selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"values.foo": "foo",
},
},
expected: []map[string]interface{}{},
},
{
name: "values.foo should be bar",
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}},
selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"values.foo": "bar",
},
},
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url", "values": map[string]interface{}{"foo": "bar"}}},
},
{
name: "values.0 should be bar",
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":["bar"]}`)}},
selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"values.0": "bar",
},
},
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url", "values": []interface{}{"bar"}}},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
var listGenerator = NewListGenerator()
@@ -73,7 +158,9 @@ func TestMatchValues(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argov1alpha1.ApplicationSetSpec{},
Spec: argov1alpha1.ApplicationSetSpec{
GoTemplate: true,
},
}
results, err := Transform(argov1alpha1.ApplicationSetGenerator{
@@ -85,6 +172,74 @@ func TestMatchValues(t *testing.T) {
data,
emptyTemplate(),
&applicationSetInfo, nil)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, results[0].Params)
})
}
}
func TestTransForm(t *testing.T) {
testCases := []struct {
name string
selector *metav1.LabelSelector
expected []map[string]interface{}
}{
{
name: "server filter",
selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"server": "https://production-01.example.com"},
},
expected: []map[string]interface{}{{
"metadata.annotations.foo.argoproj.io": "production",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster",
"metadata.labels.environment": "production",
"metadata.labels.org": "bar",
"name": "production_01/west",
"nameNormalized": "production-01-west",
"server": "https://production-01.example.com",
}},
},
{
name: "server filter with long url",
selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"server": "https://some-really-long-url-that-will-exceed-63-characters.com"},
},
expected: []map[string]interface{}{{
"metadata.annotations.foo.argoproj.io": "production",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster",
"metadata.labels.environment": "production",
"metadata.labels.org": "bar",
"name": "some-really-long-server-url",
"nameNormalized": "some-really-long-server-url",
"server": "https://some-really-long-url-that-will-exceed-63-characters.com",
}},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
testGenerators := map[string]Generator{
"Clusters": getMockClusterGenerator(),
}
applicationSetInfo := argov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argov1alpha1.ApplicationSetSpec{},
}
results, err := Transform(
argov1alpha1.ApplicationSetGenerator{
Selector: testCase.selector,
Clusters: &argov1alpha1.ClusterGenerator{
Selector: metav1.LabelSelector{},
Template: argov1alpha1.ApplicationSetTemplate{},
Values: nil,
}},
testGenerators,
emptyTemplate(),
&applicationSetInfo, nil)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, results[0].Params)
@@ -150,8 +305,35 @@ func getMockClusterGenerator() Generator {
},
Type: corev1.SecretType("Opaque"),
},
&corev1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: "Secret",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "some-really-long-server-url",
Namespace: "namespace",
Labels: map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
"environment": "production",
"org": "bar",
},
Annotations: map[string]string{
"foo.argoproj.io": "production",
},
},
Data: map[string][]byte{
"config": []byte("{}"),
"name": []byte("some-really-long-server-url"),
"server": []byte("https://some-really-long-url-that-will-exceed-63-characters.com"),
},
Type: corev1.SecretType("Opaque"),
},
}
runtimeClusters := []runtime.Object{}
for _, clientCluster := range clusters {
runtimeClusters = append(runtimeClusters, clientCluster)
}
appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
@@ -159,9 +341,9 @@ func getMockClusterGenerator() Generator {
}
func getMockGitGenerator() Generator {
argoCDServiceMock := mocks.Repos{}
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return([]string{"app1", "app2", "app_3", "p1/app4"}, nil)
var gitGenerator = NewGitGenerator(&argoCDServiceMock)
argoCDServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
argoCDServiceMock.Mock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return([]string{"app1", "app2", "app_3", "p1/app4"}, nil)
var gitGenerator = NewGitGenerator(argoCDServiceMock)
return gitGenerator
}
@@ -176,8 +358,8 @@ func TestGetRelevantGenerators(t *testing.T) {
testGenerators["Merge"] = NewMergeGenerator(testGenerators)
testGenerators["List"] = NewListGenerator()
requestedGenerator := &argov1alpha1.ApplicationSetGenerator{
List: &argov1alpha1.ListGenerator{
requestedGenerator := &argoprojiov1alpha1.ApplicationSetGenerator{
List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}},
}}
@@ -185,10 +367,10 @@ func TestGetRelevantGenerators(t *testing.T) {
assert.Len(t, relevantGenerators, 1)
assert.IsType(t, &ListGenerator{}, relevantGenerators[0])
requestedGenerator = &argov1alpha1.ApplicationSetGenerator{
Clusters: &argov1alpha1.ClusterGenerator{
requestedGenerator = &argoprojiov1alpha1.ApplicationSetGenerator{
Clusters: &argoprojiov1alpha1.ClusterGenerator{
Selector: metav1.LabelSelector{},
Template: argov1alpha1.ApplicationSetTemplate{},
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
Values: nil,
},
}
@@ -197,14 +379,14 @@ func TestGetRelevantGenerators(t *testing.T) {
assert.Len(t, relevantGenerators, 1)
assert.IsType(t, &ClusterGenerator{}, relevantGenerators[0])
requestedGenerator = &argov1alpha1.ApplicationSetGenerator{
Git: &argov1alpha1.GitGenerator{
requestedGenerator = &argoprojiov1alpha1.ApplicationSetGenerator{
Git: &argoprojiov1alpha1.GitGenerator{
RepoURL: "",
Directories: nil,
Files: nil,
Revision: "",
RequeueAfterSeconds: nil,
Template: argov1alpha1.ApplicationSetTemplate{},
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
},
}
@@ -214,8 +396,8 @@ func TestGetRelevantGenerators(t *testing.T) {
}
func TestInterpolateGenerator(t *testing.T) {
requestedGenerator := &argov1alpha1.ApplicationSetGenerator{
Clusters: &argov1alpha1.ClusterGenerator{
requestedGenerator := &argoprojiov1alpha1.ApplicationSetGenerator{
Clusters: &argoprojiov1alpha1.ClusterGenerator{
Selector: metav1.LabelSelector{
MatchLabels: map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
@@ -241,17 +423,17 @@ func TestInterpolateGenerator(t *testing.T) {
assert.Equal(t, "p1", interpolatedGenerator.Clusters.Selector.MatchLabels["path-zero"])
assert.Equal(t, "p1/p2/app3", interpolatedGenerator.Clusters.Selector.MatchLabels["path-full"])
fileNamePath := argov1alpha1.GitFileGeneratorItem{
fileNamePath := argoprojiov1alpha1.GitFileGeneratorItem{
Path: "{{name}}",
}
fileServerPath := argov1alpha1.GitFileGeneratorItem{
fileServerPath := argoprojiov1alpha1.GitFileGeneratorItem{
Path: "{{server}}",
}
requestedGenerator = &argov1alpha1.ApplicationSetGenerator{
Git: &argov1alpha1.GitGenerator{
Files: append([]argov1alpha1.GitFileGeneratorItem{}, fileNamePath, fileServerPath),
Template: argov1alpha1.ApplicationSetTemplate{},
requestedGenerator = &argoprojiov1alpha1.ApplicationSetGenerator{
Git: &argoprojiov1alpha1.GitGenerator{
Files: append([]argoprojiov1alpha1.GitFileGeneratorItem{}, fileNamePath, fileServerPath),
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
},
}
clusterGeneratorParams := map[string]interface{}{
@@ -267,8 +449,8 @@ func TestInterpolateGenerator(t *testing.T) {
}
func TestInterpolateGenerator_go(t *testing.T) {
requestedGenerator := &argov1alpha1.ApplicationSetGenerator{
Clusters: &argov1alpha1.ClusterGenerator{
requestedGenerator := &argoprojiov1alpha1.ApplicationSetGenerator{
Clusters: &argoprojiov1alpha1.ClusterGenerator{
Selector: metav1.LabelSelector{
MatchLabels: map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
@@ -295,17 +477,17 @@ func TestInterpolateGenerator_go(t *testing.T) {
assert.Equal(t, "p1", interpolatedGenerator.Clusters.Selector.MatchLabels["path-zero"])
assert.Equal(t, "p1/p2/app3", interpolatedGenerator.Clusters.Selector.MatchLabels["path-full"])
fileNamePath := argov1alpha1.GitFileGeneratorItem{
fileNamePath := argoprojiov1alpha1.GitFileGeneratorItem{
Path: "{{.name}}",
}
fileServerPath := argov1alpha1.GitFileGeneratorItem{
fileServerPath := argoprojiov1alpha1.GitFileGeneratorItem{
Path: "{{.server}}",
}
requestedGenerator = &argov1alpha1.ApplicationSetGenerator{
Git: &argov1alpha1.GitGenerator{
Files: append([]argov1alpha1.GitFileGeneratorItem{}, fileNamePath, fileServerPath),
Template: argov1alpha1.ApplicationSetTemplate{},
requestedGenerator = &argoprojiov1alpha1.ApplicationSetGenerator{
Git: &argoprojiov1alpha1.GitGenerator{
Files: append([]argoprojiov1alpha1.GitFileGeneratorItem{}, fileNamePath, fileServerPath),
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
},
}
clusterGeneratorParams := map[string]interface{}{

View File

@@ -4,14 +4,21 @@ import (
"fmt"
"testing"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
testutils "github.com/argoproj/argo-cd/v2/applicationset/utils/test"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
// type clientSet struct {
// RepoServerServiceClient apiclient.RepoServerServiceClient
// }
// func (c *clientSet) NewRepoServerClient() (io.Closer, apiclient.RepoServerServiceClient, error) {
// return io.NewCloser(func() error { return nil }), c.RepoServerServiceClient, nil
// }
func Test_generateParamsFromGitFile(t *testing.T) {
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
@@ -237,11 +244,11 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel()
argoCDServiceMock := mocks.Repos{}
argoCDServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
argoCDServiceMock.Mock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
var gitGenerator = NewGitGenerator(&argoCDServiceMock)
var gitGenerator = NewGitGenerator(argoCDServiceMock)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -267,7 +274,7 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
assert.Equal(t, testCaseCopy.expected, got)
}
argoCDServiceMock.AssertExpectations(t)
argoCDServiceMock.Mock.AssertExpectations(t)
})
}
}
@@ -532,11 +539,11 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel()
argoCDServiceMock := mocks.Repos{}
argoCDServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
argoCDServiceMock.Mock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
var gitGenerator = NewGitGenerator(&argoCDServiceMock)
var gitGenerator = NewGitGenerator(argoCDServiceMock)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -563,7 +570,7 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
assert.Equal(t, testCaseCopy.expected, got)
}
argoCDServiceMock.AssertExpectations(t)
argoCDServiceMock.Mock.AssertExpectations(t)
})
}
@@ -823,11 +830,11 @@ cluster:
t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel()
argoCDServiceMock := mocks.Repos{}
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
argoCDServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
argoCDServiceMock.Mock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
var gitGenerator = NewGitGenerator(&argoCDServiceMock)
var gitGenerator = NewGitGenerator(argoCDServiceMock)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -853,7 +860,7 @@ cluster:
assert.ElementsMatch(t, testCaseCopy.expected, got)
}
argoCDServiceMock.AssertExpectations(t)
argoCDServiceMock.Mock.AssertExpectations(t)
})
}
}
@@ -1172,11 +1179,11 @@ cluster:
t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel()
argoCDServiceMock := mocks.Repos{}
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
argoCDServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
argoCDServiceMock.Mock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
var gitGenerator = NewGitGenerator(&argoCDServiceMock)
var gitGenerator = NewGitGenerator(argoCDServiceMock)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -1203,7 +1210,7 @@ cluster:
assert.ElementsMatch(t, testCaseCopy.expected, got)
}
argoCDServiceMock.AssertExpectations(t)
argoCDServiceMock.Mock.AssertExpectations(t)
})
}
}

View File

@@ -5,7 +5,6 @@ import (
"testing"
"time"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -18,6 +17,7 @@ import (
"github.com/stretchr/testify/mock"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
testutils "github.com/argoproj/argo-cd/v2/applicationset/utils/test"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
@@ -30,7 +30,7 @@ func TestMatrixGenerate(t *testing.T) {
}
listGenerator := &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url"}`)}},
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url", "templated": "test-{{path.basenameNormalized}}"}`)}},
}
testCases := []struct {
@@ -50,8 +50,8 @@ func TestMatrixGenerate(t *testing.T) {
},
},
expected: []map[string]interface{}{
{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "cluster": "Cluster", "url": "Url"},
{"path": "app2", "path.basename": "app2", "path.basenameNormalized": "app2", "cluster": "Cluster", "url": "Url"},
{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "cluster": "Cluster", "url": "Url", "templated": "test-app1"},
{"path": "app2", "path.basename": "app2", "path.basenameNormalized": "app2", "cluster": "Cluster", "url": "Url", "templated": "test-app2"},
},
},
{
@@ -1054,8 +1054,8 @@ func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
},
}
repoServiceMock := &mocks.Repos{}
repoServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(map[string][]byte{
repoServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
repoServiceMock.Mock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(map[string][]byte{
"some/path.json": []byte("test: content"),
}, nil)
gitGenerator := NewGitGenerator(repoServiceMock)

View File

@@ -11,6 +11,7 @@ import (
"github.com/gosimple/slug"
"github.com/argoproj/argo-cd/v2/applicationset/services/pull_request"
pullrequest "github.com/argoproj/argo-cd/v2/applicationset/services/pull_request"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
@@ -65,7 +66,7 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
return nil, fmt.Errorf("failed to select pull request service provider: %v", err)
}
pulls, err := pullrequest.ListPullRequests(ctx, svc, appSetGenerator.PullRequest.Filters)
pulls, err := pull_request.ListPullRequests(ctx, svc, appSetGenerator.PullRequest.Filters)
if err != nil {
return nil, fmt.Errorf("error listing repos: %v", err)
}
@@ -83,25 +84,18 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
}
var shortSHALength int
var shortSHALength7 int
for _, pull := range pulls {
shortSHALength = 8
if len(pull.HeadSHA) < 8 {
shortSHALength = len(pull.HeadSHA)
}
shortSHALength7 = 7
if len(pull.HeadSHA) < 7 {
shortSHALength7 = len(pull.HeadSHA)
}
paramMap := map[string]interface{}{
"number": strconv.Itoa(pull.Number),
"branch": pull.Branch,
"branch_slug": slug.Make(pull.Branch),
"head_sha": pull.HeadSHA,
"head_short_sha": pull.HeadSHA[:shortSHALength],
"head_short_sha_7": pull.HeadSHA[:shortSHALength7],
"number": strconv.Itoa(pull.Number),
"branch": pull.Branch,
"branch_slug": slug.Make(pull.Branch),
"head_sha": pull.HeadSHA,
"head_short_sha": pull.HeadSHA[:shortSHALength],
}
// PR lables will only be supported for Go Template appsets, since fasttemplate will be deprecated.

View File

@@ -43,7 +43,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
"branch_slug": "branch1",
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb",
"head_short_sha_7": "089d92c",
},
},
expectedErr: nil,
@@ -69,7 +68,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
"branch_slug": "feat-areally-long-pull-request-name-to-test-argo",
"head_sha": "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
"head_short_sha": "9b34ff5b",
"head_short_sha_7": "9b34ff5",
},
},
expectedErr: nil,
@@ -95,7 +93,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
"branch_slug": "a-very-short-sha",
"head_sha": "abcd",
"head_short_sha": "abcd",
"head_short_sha_7": "abcd",
},
},
expectedErr: nil,
@@ -133,7 +130,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
"branch_slug": "branch1",
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb",
"head_short_sha_7": "089d92c",
"labels": []string{"preview"},
},
},
@@ -167,7 +163,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
"branch_slug": "branch1",
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb",
"head_short_sha_7": "089d92c",
},
},
expectedErr: nil,

View File

@@ -142,18 +142,12 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
}
params := make([]map[string]interface{}, 0, len(repos))
var shortSHALength int
var shortSHALength7 int
for _, repo := range repos {
shortSHALength = 8
if len(repo.SHA) < 8 {
shortSHALength = len(repo.SHA)
}
shortSHALength7 = 7
if len(repo.SHA) < 7 {
shortSHALength7 = len(repo.SHA)
}
params = append(params, map[string]interface{}{
"organization": repo.Organization,
"repository": repo.Repository,
@@ -161,7 +155,6 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
"branch": repo.Branch,
"sha": repo.SHA,
"short_sha": repo.SHA[:shortSHALength],
"short_sha_7": repo.SHA[:shortSHALength7],
"labels": strings.Join(repo.Labels, ","),
"branchNormalized": utils.SanitizeName(repo.Branch),
})

View File

@@ -112,8 +112,6 @@ func TestSCMProviderGenerateParams(t *testing.T) {
assert.Equal(t, "0bc57212c3cbbec69d20b34c507284bd300def5b", params[0]["sha"])
assert.Equal(t, "0bc57212", params[0]["short_sha"])
assert.Equal(t, "59d0", params[1]["short_sha"])
assert.Equal(t, "0bc5721", params[0]["short_sha_7"])
assert.Equal(t, "59d0", params[1]["short_sha_7"])
assert.Equal(t, "prod,staging", params[0]["labels"])
assert.Equal(t, "repo2", params[1]["repository"])
}

View File

@@ -1,81 +0,0 @@
// Code generated by mockery v2.25.1. DO NOT EDIT.
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
)
// Repos is an autogenerated mock type for the Repos type
type Repos struct {
mock.Mock
}
// GetDirectories provides a mock function with given fields: ctx, repoURL, revision
func (_m *Repos) GetDirectories(ctx context.Context, repoURL string, revision string) ([]string, error) {
ret := _m.Called(ctx, repoURL, revision)
var r0 []string
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]string, error)); ok {
return rf(ctx, repoURL, revision)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string) []string); ok {
r0 = rf(ctx, repoURL, revision)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
r1 = rf(ctx, repoURL, revision)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetFiles provides a mock function with given fields: ctx, repoURL, revision, pattern
func (_m *Repos) GetFiles(ctx context.Context, repoURL string, revision string, pattern string) (map[string][]byte, error) {
ret := _m.Called(ctx, repoURL, revision, pattern)
var r0 map[string][]byte
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (map[string][]byte, error)); ok {
return rf(ctx, repoURL, revision, pattern)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) map[string][]byte); ok {
r0 = rf(ctx, repoURL, revision, pattern)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string][]byte)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok {
r1 = rf(ctx, repoURL, revision, pattern)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
type mockConstructorTestingTNewRepos interface {
mock.TestingT
Cleanup(func())
}
// NewRepos creates a new instance of Repos. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewRepos(t mockConstructorTestingTNewRepos) *Repos {
mock := &Repos{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@@ -1,57 +0,0 @@
// Code generated by mockery v2.21.1. DO NOT EDIT.
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
v1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
// RepositoryDB is an autogenerated mock type for the RepositoryDB type
type RepositoryDB struct {
mock.Mock
}
// GetRepository provides a mock function with given fields: ctx, url
func (_m *RepositoryDB) GetRepository(ctx context.Context, url string) (*v1alpha1.Repository, error) {
ret := _m.Called(ctx, url)
var r0 *v1alpha1.Repository
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (*v1alpha1.Repository, error)); ok {
return rf(ctx, url)
}
if rf, ok := ret.Get(0).(func(context.Context, string) *v1alpha1.Repository); ok {
r0 = rf(ctx, url)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.Repository)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, url)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
type mockConstructorTestingTNewRepositoryDB interface {
mock.TestingT
Cleanup(func())
}
// NewRepositoryDB creates a new instance of RepositoryDB. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewRepositoryDB(t mockConstructorTestingTNewRepositoryDB) *RepositoryDB {
mock := &RepositoryDB{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@@ -3,26 +3,25 @@ package services
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
repoapiclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient"
"github.com/argoproj/argo-cd/v2/util/db"
"github.com/argoproj/argo-cd/v2/util/git"
"github.com/argoproj/argo-cd/v2/util/io"
)
// RepositoryDB Is a lean facade for ArgoDB,
// Using a lean interface makes it easier to test the functionality of the git generator
// Using a lean interface makes it more easy to test the functionality the git generator uses
type RepositoryDB interface {
GetRepository(ctx context.Context, url string) (*v1alpha1.Repository, error)
}
type argoCDService struct {
repositoriesDB RepositoryDB
storecreds git.CredsStore
submoduleEnabled bool
repoServerClientSet repoapiclient.Clientset
repositoriesDB RepositoryDB
storecreds git.CredsStore
submoduleEnabled bool
}
type Repos interface {
@@ -34,61 +33,121 @@ type Repos interface {
GetDirectories(ctx context.Context, repoURL string, revision string) ([]string, error)
}
func NewArgoCDService(db db.ArgoDB, submoduleEnabled bool, repoClientset repoapiclient.Clientset) (Repos, error) {
func NewArgoCDService(db db.ArgoDB, gitCredStore git.CredsStore, submoduleEnabled bool) Repos {
return &argoCDService{
repositoriesDB: db.(RepositoryDB),
submoduleEnabled: submoduleEnabled,
repoServerClientSet: repoClientset,
}, nil
repositoriesDB: db.(RepositoryDB),
storecreds: gitCredStore,
submoduleEnabled: submoduleEnabled,
}
}
func (a *argoCDService) GetFiles(ctx context.Context, repoURL string, revision string, pattern string) (map[string][]byte, error) {
repo, err := a.repositoriesDB.GetRepository(ctx, repoURL)
if err != nil {
return nil, fmt.Errorf("error in GetRepository: %w", err)
return nil, fmt.Errorf("Error in GetRepository: %w", err)
}
fileRequest := &apiclient.GitFilesRequest{
Repo: repo,
SubmoduleEnabled: a.submoduleEnabled,
Revision: revision,
Path: pattern,
}
closer, client, err := a.repoServerClientSet.NewRepoServerClient()
gitRepoClient, err := git.NewClient(repo.Repo, repo.GetGitCreds(a.storecreds), repo.IsInsecure(), repo.IsLFSEnabled(), repo.Proxy)
if err != nil {
return nil, err
}
defer io.Close(closer)
fileResponse, err := client.GetGitFiles(ctx, fileRequest)
err = checkoutRepo(gitRepoClient, revision, a.submoduleEnabled)
if err != nil {
return nil, err
}
return fileResponse.GetMap(), nil
paths, err := gitRepoClient.LsFiles(pattern)
if err != nil {
return nil, fmt.Errorf("Error during listing files of local repo: %w", err)
}
res := map[string][]byte{}
for _, filePath := range paths {
bytes, err := os.ReadFile(filepath.Join(gitRepoClient.Root(), filePath))
if err != nil {
return nil, err
}
res[filePath] = bytes
}
return res, nil
}
func (a *argoCDService) GetDirectories(ctx context.Context, repoURL string, revision string) ([]string, error) {
repo, err := a.repositoriesDB.GetRepository(ctx, repoURL)
if err != nil {
return nil, fmt.Errorf("error in GetRepository: %w", err)
return nil, fmt.Errorf("Error in GetRepository: %w", err)
}
dirRequest := &apiclient.GitDirectoriesRequest{
Repo: repo,
SubmoduleEnabled: a.submoduleEnabled,
Revision: revision,
}
closer, client, err := a.repoServerClientSet.NewRepoServerClient()
gitRepoClient, err := git.NewClient(repo.Repo, repo.GetGitCreds(a.storecreds), repo.IsInsecure(), repo.IsLFSEnabled(), repo.Proxy)
if err != nil {
return nil, fmt.Errorf("error creating a new git client: %w", err)
}
err = checkoutRepo(gitRepoClient, revision, a.submoduleEnabled)
if err != nil {
return nil, fmt.Errorf("error while checking out repo: %w", err)
}
filteredPaths := []string{}
repoRoot := gitRepoClient.Root()
if err := filepath.Walk(repoRoot, func(path string, info os.FileInfo, fnErr error) error {
if fnErr != nil {
return fmt.Errorf("error walking the file tree: %w", fnErr)
}
if !info.IsDir() { // Skip files: directories only
return nil
}
fname := info.Name()
if strings.HasPrefix(fname, ".") { // Skip all folders starts with "."
return filepath.SkipDir
}
relativePath, err := filepath.Rel(repoRoot, path)
if err != nil {
return fmt.Errorf("error constructing relative repo path: %w", err)
}
if relativePath == "." { // Exclude '.' from results
return nil
}
filteredPaths = append(filteredPaths, relativePath)
return nil
}); err != nil {
return nil, err
}
defer io.Close(closer)
dirResponse, err := client.GetGitDirectories(ctx, dirRequest)
if err != nil {
return nil, err
}
return dirResponse.GetPaths(), nil
return filteredPaths, nil
}
func checkoutRepo(gitRepoClient git.Client, revision string, submoduleEnabled bool) error {
err := gitRepoClient.Init()
if err != nil {
return fmt.Errorf("Error during initializing repo: %w", err)
}
err = gitRepoClient.Fetch(revision)
if err != nil {
return fmt.Errorf("Error during fetching repo: %w", err)
}
commitSHA, err := gitRepoClient.LsRemote(revision)
if err != nil {
return fmt.Errorf("Error during fetching commitSHA: %w", err)
}
err = gitRepoClient.Checkout(commitSHA, submoduleEnabled)
if err != nil {
return fmt.Errorf("Error during repo checkout: %w", err)
}
return nil
}

View File

@@ -3,189 +3,231 @@ package services
import (
"context"
"fmt"
"sort"
"testing"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
repo_mocks "github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
db_mocks "github.com/argoproj/argo-cd/v2/util/db/mocks"
"github.com/argoproj/argo-cd/v2/util/git"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
type ArgocdRepositoryMock struct {
mock *mock.Mock
}
func (a ArgocdRepositoryMock) GetRepository(ctx context.Context, url string) (*v1alpha1.Repository, error) {
args := a.mock.Called(ctx, url)
return args.Get(0).(*v1alpha1.Repository), args.Error(1)
}
func TestGetDirectories(t *testing.T) {
type fields struct {
repositoriesDBFuncs []func(*mocks.RepositoryDB)
storecreds git.CredsStore
submoduleEnabled bool
repoServerClientFuncs []func(*repo_mocks.RepoServerServiceClient)
}
type args struct {
ctx context.Context
repoURL string
revision string
}
tests := []struct {
name string
fields fields
args args
want []string
wantErr assert.ErrorAssertionFunc
// Hardcode a specific revision to changes to argocd-example-apps from regressing this test:
// Author: Alexander Matyushentsev <Alexander_Matyushentsev@intuit.com>
// Date: Sun Jan 31 09:54:53 2021 -0800
// chore: downgrade kustomize guestbook image tag (#73)
exampleRepoRevision := "08f72e2a309beab929d9fd14626071b1a61a47f9"
for _, c := range []struct {
name string
repoURL string
revision string
repoRes *v1alpha1.Repository
repoErr error
expected []string
expectedError error
}{
{name: "ErrorGettingRepos", fields: fields{
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("unable to get repos"))
},
{
name: "All child folders should be returned",
repoURL: "https://github.com/argoproj/argocd-example-apps/",
revision: exampleRepoRevision,
repoRes: &v1alpha1.Repository{
Repo: "https://github.com/argoproj/argocd-example-apps/",
},
}, args: args{}, want: nil, wantErr: assert.Error},
{name: "ErrorGettingDirs", fields: fields{
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(&v1alpha1.Repository{}, nil)
},
repoErr: nil,
expected: []string{"apps", "apps/templates", "blue-green", "blue-green/templates", "guestbook", "helm-dependency",
"helm-guestbook", "helm-guestbook/templates", "helm-hooks", "jsonnet-guestbook", "jsonnet-guestbook-tla",
"ksonnet-guestbook", "ksonnet-guestbook/components", "ksonnet-guestbook/environments", "ksonnet-guestbook/environments/default",
"ksonnet-guestbook/environments/dev", "ksonnet-guestbook/environments/prod", "kustomize-guestbook", "plugins", "plugins/kasane",
"plugins/kustomized-helm", "plugins/kustomized-helm/overlays", "pre-post-sync", "sock-shop", "sock-shop/base", "sync-waves"},
},
{
name: "If GetRepository returns an error, it should pass back to caller",
repoURL: "https://github.com/argoproj/argocd-example-apps/",
revision: exampleRepoRevision,
repoRes: &v1alpha1.Repository{
Repo: "https://github.com/argoproj/argocd-example-apps/",
},
repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){
func(client *repo_mocks.RepoServerServiceClient) {
client.On("GetGitDirectories", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("unable to get dirs"))
},
repoErr: fmt.Errorf("Simulated error from GetRepository"),
expected: nil,
expectedError: fmt.Errorf("Error in GetRepository: Simulated error from GetRepository"),
},
{
name: "Test against repository containing no directories",
// Here I picked an arbitrary repository in argoproj-labs, with a commit containing no folders.
repoURL: "https://github.com/argoproj-labs/argo-workflows-operator/",
revision: "5f50933a576833b73b7a172909d8545a108685f4",
repoRes: &v1alpha1.Repository{
Repo: "https://github.com/argoproj-labs/argo-workflows-operator/",
},
}, args: args{}, want: nil, wantErr: assert.Error},
{name: "HappyCase", fields: fields{
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(&v1alpha1.Repository{}, nil)
},
},
repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){
func(client *repo_mocks.RepoServerServiceClient) {
client.On("GetGitDirectories", mock.Anything, mock.Anything).Return(&apiclient.GitDirectoriesResponse{
Paths: []string{"foo", "foo/bar", "bar/foo"},
}, nil)
},
},
}, args: args{}, want: []string{"foo", "foo/bar", "bar/foo"}, wantErr: assert.NoError},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockDb := &mocks.RepositoryDB{}
mockRepoClient := &repo_mocks.RepoServerServiceClient{}
// decorate the mocks
for i := range tt.fields.repositoriesDBFuncs {
tt.fields.repositoriesDBFuncs[i](mockDb)
}
for i := range tt.fields.repoServerClientFuncs {
tt.fields.repoServerClientFuncs[i](mockRepoClient)
repoErr: nil,
expected: []string{},
},
} {
cc := c
t.Run(cc.name, func(t *testing.T) {
argocdRepositoryMock := ArgocdRepositoryMock{mock: &mock.Mock{}}
argocdRepositoryMock.mock.On("GetRepository", mock.Anything, cc.repoURL).Return(cc.repoRes, cc.repoErr)
argocd := argoCDService{
repositoriesDB: argocdRepositoryMock,
}
a := &argoCDService{
repositoriesDB: mockDb,
storecreds: tt.fields.storecreds,
submoduleEnabled: tt.fields.submoduleEnabled,
repoServerClientSet: &repo_mocks.Clientset{RepoServerServiceClient: mockRepoClient},
got, err := argocd.GetDirectories(context.TODO(), cc.repoURL, cc.revision)
if cc.expectedError != nil {
assert.EqualError(t, err, cc.expectedError.Error())
} else {
sort.Strings(got)
sort.Strings(cc.expected)
assert.Equal(t, got, cc.expected)
assert.NoError(t, err)
}
got, err := a.GetDirectories(tt.args.ctx, tt.args.repoURL, tt.args.revision)
if !tt.wantErr(t, err, fmt.Sprintf("GetDirectories(%v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision)) {
return
}
assert.Equalf(t, tt.want, got, "GetDirectories(%v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision)
})
}
}
func TestGetFiles(t *testing.T) {
type fields struct {
repositoriesDBFuncs []func(*mocks.RepositoryDB)
storecreds git.CredsStore
submoduleEnabled bool
repoServerClientFuncs []func(*repo_mocks.RepoServerServiceClient)
}
type args struct {
ctx context.Context
// Hardcode a specific commit, so that changes to argoproj/argocd-example-apps/ don't break our tests
// "chore: downgrade kustomize guestbook image tag (#73)"
commitID := "08f72e2a309beab929d9fd14626071b1a61a47f9"
tests := []struct {
name string
repoURL string
revision string
pattern string
}
tests := []struct {
name string
fields fields
args args
want map[string][]byte
wantErr assert.ErrorAssertionFunc
repoRes *v1alpha1.Repository
repoErr error
expectSubsetOfPaths []string
doesNotContainPaths []string
expectedError error
}{
{name: "ErrorGettingRepos", fields: fields{
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("unable to get repos"))
},
{
name: "pull a specific revision of example apps and verify the list is expected",
repoRes: &v1alpha1.Repository{
Insecure: true,
InsecureIgnoreHostKey: true,
Repo: "https://github.com/argoproj/argocd-example-apps/",
},
}, args: args{}, want: nil, wantErr: assert.Error},
{name: "ErrorGettingFiles", fields: fields{
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(&v1alpha1.Repository{}, nil)
},
repoURL: "https://github.com/argoproj/argocd-example-apps/",
revision: commitID,
pattern: "*",
expectSubsetOfPaths: []string{
"apps/Chart.yaml",
"apps/templates/helm-guestbook.yaml",
"apps/templates/helm-hooks.yaml",
"apps/templates/kustomize-guestbook.yaml",
"apps/templates/namespaces.yaml",
"apps/templates/sync-waves.yaml",
"apps/values.yaml",
"blue-green/.helmignore",
"blue-green/Chart.yaml",
"blue-green/README.md",
"blue-green/templates/NOTES.txt",
"blue-green/templates/rollout.yaml",
"blue-green/templates/services.yaml",
"blue-green/values.yaml",
"guestbook/guestbook-ui-deployment.yaml",
"guestbook/guestbook-ui-svc.yaml",
"kustomize-guestbook/guestbook-ui-deployment.yaml",
"kustomize-guestbook/guestbook-ui-svc.yaml",
"kustomize-guestbook/kustomization.yaml",
},
repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){
func(client *repo_mocks.RepoServerServiceClient) {
client.On("GetGitFiles", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("unable to get files"))
},
},
{
name: "pull an invalid revision, and confirm an error is returned",
repoRes: &v1alpha1.Repository{
Insecure: true,
InsecureIgnoreHostKey: true,
Repo: "https://github.com/argoproj/argocd-example-apps/",
},
}, args: args{}, want: nil, wantErr: assert.Error},
{name: "HappyCase", fields: fields{
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(&v1alpha1.Repository{}, nil)
},
repoURL: "https://github.com/argoproj/argocd-example-apps/",
revision: "this-tag-does-not-exist",
pattern: "*",
expectSubsetOfPaths: []string{},
expectedError: fmt.Errorf("Error during fetching repo: `git fetch origin this-tag-does-not-exist --tags --force --prune` failed exit status 128: fatal: couldn't find remote ref this-tag-does-not-exist"),
},
{
name: "pull a specific revision of example apps, and use a ** pattern",
repoRes: &v1alpha1.Repository{
Insecure: true,
InsecureIgnoreHostKey: true,
Repo: "https://github.com/argoproj/argocd-example-apps/",
},
repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){
func(client *repo_mocks.RepoServerServiceClient) {
client.On("GetGitFiles", mock.Anything, mock.Anything).Return(&apiclient.GitFilesResponse{
Map: map[string][]byte{
"foo.json": []byte("hello: world!"),
"bar.yaml": []byte("yay: appsets"),
},
}, nil)
},
repoURL: "https://github.com/argoproj/argocd-example-apps/",
revision: commitID,
pattern: "**/*.yaml",
expectSubsetOfPaths: []string{
"apps/Chart.yaml",
"apps/templates/helm-guestbook.yaml",
"apps/templates/helm-hooks.yaml",
"apps/templates/kustomize-guestbook.yaml",
"apps/templates/namespaces.yaml",
"apps/templates/sync-waves.yaml",
"apps/values.yaml",
"blue-green/templates/rollout.yaml",
"blue-green/templates/services.yaml",
"blue-green/values.yaml",
"guestbook/guestbook-ui-deployment.yaml",
"guestbook/guestbook-ui-svc.yaml",
"kustomize-guestbook/guestbook-ui-deployment.yaml",
"kustomize-guestbook/guestbook-ui-svc.yaml",
"kustomize-guestbook/kustomization.yaml",
},
}, args: args{}, want: map[string][]byte{
"foo.json": []byte("hello: world!"),
"bar.yaml": []byte("yay: appsets"),
}, wantErr: assert.NoError},
doesNotContainPaths: []string{
"blue-green/.helmignore",
"blue-green/README.md",
"blue-green/templates/NOTES.txt",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockDb := &mocks.RepositoryDB{}
mockRepoClient := &repo_mocks.RepoServerServiceClient{}
// decorate the mocks
for i := range tt.fields.repositoriesDBFuncs {
tt.fields.repositoriesDBFuncs[i](mockDb)
}
for i := range tt.fields.repoServerClientFuncs {
tt.fields.repoServerClientFuncs[i](mockRepoClient)
for _, cc := range tests {
// Get all the paths for a repository, and confirm that the expected subset of paths is found (or the expected error is returned)
t.Run(cc.name, func(t *testing.T) {
argocdRepositoryMock := ArgocdRepositoryMock{mock: &mock.Mock{}}
argocdRepositoryMock.mock.On("GetRepository", mock.Anything, cc.repoURL).Return(cc.repoRes, cc.repoErr)
argocd := argoCDService{
repositoriesDB: argocdRepositoryMock,
}
a := &argoCDService{
repositoriesDB: mockDb,
storecreds: tt.fields.storecreds,
submoduleEnabled: tt.fields.submoduleEnabled,
repoServerClientSet: &repo_mocks.Clientset{RepoServerServiceClient: mockRepoClient},
getPathsRes, err := argocd.GetFiles(context.Background(), cc.repoURL, cc.revision, cc.pattern)
if cc.expectedError == nil {
assert.NoError(t, err)
for _, path := range cc.expectSubsetOfPaths {
assert.Contains(t, getPathsRes, path, "Unable to locate path: %s", path)
}
for _, shouldNotContain := range cc.doesNotContainPaths {
assert.NotContains(t, getPathsRes, shouldNotContain, "GetPaths should not contain %s", shouldNotContain)
}
} else {
assert.EqualError(t, err, cc.expectedError.Error())
}
got, err := a.GetFiles(tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern)
if !tt.wantErr(t, err, fmt.Sprintf("GetFiles(%v, %v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern)) {
return
}
assert.Equalf(t, tt.want, got, "GetFiles(%v, %v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern)
})
}
}
func TestNewArgoCDService(t *testing.T) {
service, err := NewArgoCDService(&db_mocks.ArgoDB{}, false, &repo_mocks.Clientset{})
assert.NoError(t, err, err)
assert.NotNil(t, service)
}

View File

@@ -3,6 +3,7 @@ package scm_provider
import (
"context"
"fmt"
"io"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
bitbucketv1 "github.com/gfleury/go-bitbucket-v1"
@@ -183,8 +184,9 @@ func (b *BitbucketServerProvider) listBranches(repo *Repository) ([]bitbucketv1.
func (b *BitbucketServerProvider) getDefaultBranch(org string, repo string) (*bitbucketv1.Branch, error) {
response, err := b.client.DefaultApi.GetDefaultBranch(org, repo)
if response != nil && response.StatusCode == 404 {
// There's no default branch i.e. empty repo, not an error
// The API will return 404 if a default branch is set but doesn't exist. In case the repo is empty and default branch is unset,
// we will get an EOF and a nil response.
if (response != nil && response.StatusCode == 404) || (response == nil && err == io.EOF) {
return nil, nil
}
if err != nil {

View File

@@ -365,6 +365,28 @@ func TestGetBranchesMissingDefault(t *testing.T) {
assert.Empty(t, repos)
}
func TestGetBranchesEmptyRepo(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Empty(t, r.Header.Get("Authorization"))
switch r.RequestURI {
case "/rest/api/1.0/projects/PROJECT/repos/REPO/branches/default":
return
}
}))
defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false)
assert.NoError(t, err)
repos, err := provider.GetBranches(context.Background(), &Repository{
Organization: "PROJECT",
Repository: "REPO",
URL: "ssh://git@mycompany.bitbucket.org/PROJECT/REPO.git",
Labels: []string{},
RepositoryId: 1,
})
assert.Empty(t, repos)
assert.NoError(t, err)
}
func TestGetBranchesErrorDefaultBranch(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Empty(t, r.Header.Get("Authorization"))

View File

@@ -0,0 +1,261 @@
package utils
import (
"fmt"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/klog/v2"
"sort"
"strconv"
"strings"
)
var (
unaryOperators = []string{
string(selection.Exists), string(selection.DoesNotExist),
}
binaryOperators = []string{
string(selection.In), string(selection.NotIn),
string(selection.Equals), string(selection.DoubleEquals), string(selection.NotEquals),
string(selection.GreaterThan), string(selection.LessThan),
}
validRequirementOperators = append(binaryOperators, unaryOperators...)
)
// Selector represents a label selector.
type Selector interface {
// Matches returns true if this selector matches the given set of labels.
Matches(labels.Labels) bool
// Add adds requirements to the Selector
Add(r ...Requirement) Selector
}
type internalSelector []Requirement
// ByKey sorts requirements by key to obtain deterministic parser
type ByKey []Requirement
func (a ByKey) Len() int { return len(a) }
func (a ByKey) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByKey) Less(i, j int) bool { return a[i].key < a[j].key }
// Matches for a internalSelector returns true if all
// its Requirements match the input Labels. If any
// Requirement does not match, false is returned.
func (s internalSelector) Matches(l labels.Labels) bool {
for ix := range s {
if matches := s[ix].Matches(l); !matches {
return false
}
}
return true
}
// Add adds requirements to the selector. It copies the current selector returning a new one
func (s internalSelector) Add(reqs ...Requirement) Selector {
ret := make(internalSelector, 0, len(s)+len(reqs))
ret = append(ret, s...)
ret = append(ret, reqs...)
sort.Sort(ByKey(ret))
return ret
}
type nothingSelector struct{}
func (n nothingSelector) Matches(l labels.Labels) bool {
return false
}
func (n nothingSelector) Add(r ...Requirement) Selector {
return n
}
// Nothing returns a selector that matches no labels
func nothing() Selector {
return nothingSelector{}
}
// Everything returns a selector that matches all labels.
func everything() Selector {
return internalSelector{}
}
// LabelSelectorAsSelector converts the LabelSelector api type into a struct that implements
// labels.Selector
// Note: This function should be kept in sync with the selector methods in pkg/labels/selector.go
func LabelSelectorAsSelector(ps *v1.LabelSelector) (Selector, error) {
if ps == nil {
return nothing(), nil
}
if len(ps.MatchLabels)+len(ps.MatchExpressions) == 0 {
return everything(), nil
}
requirements := make([]Requirement, 0, len(ps.MatchLabels)+len(ps.MatchExpressions))
for k, v := range ps.MatchLabels {
r, err := newRequirement(k, selection.Equals, []string{v})
if err != nil {
return nil, err
}
requirements = append(requirements, *r)
}
for _, expr := range ps.MatchExpressions {
var op selection.Operator
switch expr.Operator {
case v1.LabelSelectorOpIn:
op = selection.In
case v1.LabelSelectorOpNotIn:
op = selection.NotIn
case v1.LabelSelectorOpExists:
op = selection.Exists
case v1.LabelSelectorOpDoesNotExist:
op = selection.DoesNotExist
default:
return nil, fmt.Errorf("%q is not a valid pod selector operator", expr.Operator)
}
r, err := newRequirement(expr.Key, op, append([]string(nil), expr.Values...))
if err != nil {
return nil, err
}
requirements = append(requirements, *r)
}
selector := newSelector()
selector = selector.Add(requirements...)
return selector, nil
}
// NewSelector returns a nil selector
func newSelector() Selector {
return internalSelector(nil)
}
func validateLabelKey(k string, path *field.Path) *field.Error {
if errs := validation.IsQualifiedName(k); len(errs) != 0 {
return field.Invalid(path, k, strings.Join(errs, "; "))
}
return nil
}
// NewRequirement is the constructor for a Requirement.
// If any of these rules is violated, an error is returned:
// (1) The operator can only be In, NotIn, Equals, DoubleEquals, Gt, Lt, NotEquals, Exists, or DoesNotExist.
// (2) If the operator is In or NotIn, the values set must be non-empty.
// (3) If the operator is Equals, DoubleEquals, or NotEquals, the values set must contain one value.
// (4) If the operator is Exists or DoesNotExist, the value set must be empty.
// (5) If the operator is Gt or Lt, the values set must contain only one value, which will be interpreted as an integer.
// (6) The key is invalid due to its length, or sequence
//
// of characters. See validateLabelKey for more details.
//
// The empty string is a valid value in the input values set.
// Returned error, if not nil, is guaranteed to be an aggregated field.ErrorList
func newRequirement(key string, op selection.Operator, vals []string, opts ...field.PathOption) (*Requirement, error) {
var allErrs field.ErrorList
path := field.ToPath(opts...)
if err := validateLabelKey(key, path.Child("key")); err != nil {
allErrs = append(allErrs, err)
}
valuePath := path.Child("values")
switch op {
case selection.In, selection.NotIn:
if len(vals) == 0 {
allErrs = append(allErrs, field.Invalid(valuePath, vals, "for 'in', 'notin' operators, values set can't be empty"))
}
case selection.Equals, selection.DoubleEquals, selection.NotEquals:
if len(vals) != 1 {
allErrs = append(allErrs, field.Invalid(valuePath, vals, "exact-match compatibility requires one single value"))
}
case selection.Exists, selection.DoesNotExist:
if len(vals) != 0 {
allErrs = append(allErrs, field.Invalid(valuePath, vals, "values set must be empty for exists and does not exist"))
}
case selection.GreaterThan, selection.LessThan:
if len(vals) != 1 {
allErrs = append(allErrs, field.Invalid(valuePath, vals, "for 'Gt', 'Lt' operators, exactly one value is required"))
}
for i := range vals {
if _, err := strconv.ParseInt(vals[i], 10, 64); err != nil {
allErrs = append(allErrs, field.Invalid(valuePath.Index(i), vals[i], "for 'Gt', 'Lt' operators, the value must be an integer"))
}
}
default:
allErrs = append(allErrs, field.NotSupported(path.Child("operator"), op, validRequirementOperators))
}
return &Requirement{key: key, operator: op, strValues: vals}, allErrs.ToAggregate()
}
// Requirement contains values, a key, and an operator that relates the key and values.
// The zero value of Requirement is invalid.
// Requirement implements both set based match and exact match
// Requirement should be initialized via NewRequirement constructor for creating a valid Requirement.
// +k8s:deepcopy-gen=true
type Requirement struct {
key string
operator selection.Operator
// In the majority of cases we have at most one value here.
// It is generally faster to operate on a single-element slice
// than on a single-element map, so we have a slice here.
strValues []string
}
func (r *Requirement) hasValue(value string) bool {
for i := range r.strValues {
if r.strValues[i] == value {
return true
}
}
return false
}
func (r *Requirement) Matches(ls labels.Labels) bool {
switch r.operator {
case selection.In, selection.Equals, selection.DoubleEquals:
if !ls.Has(r.key) {
return false
}
return r.hasValue(ls.Get(r.key))
case selection.NotIn, selection.NotEquals:
if !ls.Has(r.key) {
return true
}
return !r.hasValue(ls.Get(r.key))
case selection.Exists:
return ls.Has(r.key)
case selection.DoesNotExist:
return !ls.Has(r.key)
case selection.GreaterThan, selection.LessThan:
if !ls.Has(r.key) {
return false
}
lsValue, err := strconv.ParseInt(ls.Get(r.key), 10, 64)
if err != nil {
klog.V(10).Infof("ParseInt failed for value %+v in label %+v, %+v", ls.Get(r.key), ls, err)
return false
}
// There should be only one strValue in r.strValues, and can be converted to an integer.
if len(r.strValues) != 1 {
klog.V(10).Infof("Invalid values count %+v of requirement %#v, for 'Gt', 'Lt' operators, exactly one value is required", len(r.strValues), r)
return false
}
var rValue int64
for i := range r.strValues {
rValue, err = strconv.ParseInt(r.strValues[i], 10, 64)
if err != nil {
klog.V(10).Infof("ParseInt failed for value %+v in requirement %#v, for 'Gt', 'Lt' operators, the value must be an integer", r.strValues[i], r)
return false
}
}
return (r.operator == selection.GreaterThan && lsValue > rValue) || (r.operator == selection.LessThan && lsValue < rValue)
default:
return false
}
}

View File

@@ -0,0 +1,34 @@
package test
import (
"context"
"github.com/stretchr/testify/mock"
)
type ArgoCDServiceMock struct {
Mock *mock.Mock
}
func (a ArgoCDServiceMock) GetApps(ctx context.Context, repoURL string, revision string) ([]string, error) {
args := a.Mock.Called(ctx, repoURL, revision)
return args.Get(0).([]string), args.Error(1)
}
func (a ArgoCDServiceMock) GetFiles(ctx context.Context, repoURL string, revision string, pattern string) (map[string][]byte, error) {
args := a.Mock.Called(ctx, repoURL, revision, pattern)
return args.Get(0).(map[string][]byte), args.Error(1)
}
func (a ArgoCDServiceMock) GetFileContent(ctx context.Context, repoURL string, revision string, path string) ([]byte, error) {
args := a.Mock.Called(ctx, repoURL, revision, path)
return args.Get(0).([]byte), args.Error(1)
}
func (a ArgoCDServiceMock) GetDirectories(ctx context.Context, repoURL string, revision string) ([]string, error) {
args := a.Mock.Called(ctx, repoURL, revision)
return args.Get(0).([]string), args.Error(1)
}

View File

@@ -96,6 +96,25 @@ func (r *Render) deeplyReplace(copy, original reflect.Value, replaceMap map[stri
// specific case time
if currentType == "time.Time" {
copy.Field(i).Set(original.Field(i))
} else if currentType == "Raw.k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" {
var unmarshaled interface{}
originalBytes := original.Field(i).Bytes()
err := json.Unmarshal(originalBytes, &unmarshaled)
if err != nil {
return fmt.Errorf("failed to unmarshal JSON field: %w", err)
}
jsonOriginal := reflect.ValueOf(&unmarshaled)
jsonCopy := reflect.New(jsonOriginal.Type()).Elem()
err = r.deeplyReplace(jsonCopy, jsonOriginal, replaceMap, useGoTemplate)
if err != nil {
return fmt.Errorf("failed to deeply replace JSON field contents: %w", err)
}
jsonCopyInterface := jsonCopy.Interface().(*interface{})
data, err := json.Marshal(jsonCopyInterface)
if err != nil {
return fmt.Errorf("failed to marshal templated JSON field: %w", err)
}
copy.Field(i).Set(reflect.ValueOf(data))
} else if err := r.deeplyReplace(copy.Field(i), original.Field(i), replaceMap, useGoTemplate); err != nil {
return err
}
@@ -248,7 +267,10 @@ func (r *Render) Replace(tmpl string, replaceMap map[string]interface{}, useGoTe
return tmpl, nil
}
fstTmpl := fasttemplate.New(tmpl, "{{", "}}")
fstTmpl, err := fasttemplate.NewTemplate(tmpl, "{{", "}}")
if err != nil {
return "", fmt.Errorf("invalid template: %w", err)
}
replacedTmpl := fstTmpl.ExecuteFuncString(func(w io.Writer, tag string) (int, error) {
trimmedTag := strings.TrimSpace(tag)
replacement, ok := replaceMap[trimmedTag].(string)

View File

@@ -464,6 +464,14 @@ func TestRenderTemplateParamsGoTemplate(t *testing.T) {
}
}
func Test_Render_Replace_no_panic_on_missing_closing_brace(t *testing.T) {
r := &Render{}
assert.NotPanics(t, func() {
_, err := r.Replace("{{properly.closed}} {{improperly.closed}", nil, false)
assert.Error(t, err)
})
}
func TestRenderTemplateKeys(t *testing.T) {
t.Run("fasttemplate", func(t *testing.T) {
application := &argoappsv1.Application{

View File

@@ -1502,51 +1502,6 @@
}
}
},
"/api/v1/applications/{name}/revisions/{revision}/chartdetails": {
"get": {
"tags": [
"ApplicationService"
],
"summary": "Get the chart metadata (description, maintainers, home) for a specific revision of the application",
"operationId": "ApplicationService_RevisionChartDetails",
"parameters": [
{
"type": "string",
"description": "the application's name",
"name": "name",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the revision of the app",
"name": "revision",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the application's namespace.",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1alpha1ChartDetails"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/runtimeError"
}
}
}
}
},
"/api/v1/applications/{name}/revisions/{revision}/metadata": {
"get": {
"tags": [
@@ -6485,26 +6440,6 @@
}
}
},
"v1alpha1ChartDetails": {
"type": "object",
"title": "ChartDetails contains helm chart metadata for a specific version",
"properties": {
"description": {
"type": "string"
},
"home": {
"type": "string",
"title": "The URL of this projects home page, e.g. \"http://example.com\""
},
"maintainers": {
"type": "array",
"title": "List of maintainer details, name and email, e.g. [\"John Doe <john_doe@my-company.com>\"]",
"items": {
"type": "string"
}
}
}
},
"v1alpha1Cluster": {
"type": "object",
"title": "Cluster is the definition of a cluster resource",
@@ -8653,9 +8588,6 @@
"Compiler": {
"type": "string"
},
"ExtraBuildInfo": {
"type": "string"
},
"GitCommit": {
"type": "string"
},

View File

@@ -2,13 +2,10 @@ package command
import (
"fmt"
"math"
"net/http"
"os"
"time"
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
"github.com/argoproj/argo-cd/v2/util/tls"
"github.com/argoproj/pkg/stats"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
@@ -20,6 +17,7 @@ import (
"github.com/argoproj/argo-cd/v2/applicationset/webhook"
cmdutil "github.com/argoproj/argo-cd/v2/cmd/util"
"github.com/argoproj/argo-cd/v2/common"
"github.com/argoproj/argo-cd/v2/reposerver/askpass"
"github.com/argoproj/argo-cd/v2/util/env"
"github.com/argoproj/argo-cd/v2/util/github_app"
@@ -47,20 +45,17 @@ func getSubmoduleEnabled() bool {
func NewCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
metricsAddr string
probeBindAddr string
webhookAddr string
enableLeaderElection bool
namespace string
argocdRepoServer string
policy string
debugLog bool
dryRun bool
enableProgressiveSyncs bool
repoServerPlaintext bool
repoServerStrictTLS bool
repoServerTimeoutSeconds int
clientConfig clientcmd.ClientConfig
metricsAddr string
probeBindAddr string
webhookAddr string
enableLeaderElection bool
namespace string
argocdRepoServer string
policy string
debugLog bool
dryRun bool
enableProgressiveSyncs bool
)
scheme := runtime.NewScheme()
_ = clientgoscheme.AddToScheme(scheme)
@@ -85,7 +80,9 @@ func NewCommand() *cobra.Command {
cli.SetLogLevel(cmdutil.LogLevel)
restConfig, err := clientConfig.ClientConfig()
errors.CheckError(err)
if err != nil {
return err
}
restConfig.UserAgent = fmt.Sprintf("argocd-applicationset-controller/%s (%s)", vers.Version, vers.Platform)
@@ -113,40 +110,25 @@ func NewCommand() *cobra.Command {
os.Exit(1)
}
dynamicClient, err := dynamic.NewForConfig(mgr.GetConfig())
errors.CheckError(err)
if err != nil {
return err
}
k8sClient, err := kubernetes.NewForConfig(mgr.GetConfig())
errors.CheckError(err)
if err != nil {
return err
}
argoSettingsMgr := argosettings.NewSettingsManager(ctx, k8sClient, namespace)
appSetConfig := appclientset.NewForConfigOrDie(mgr.GetConfig())
argoCDDB := db.NewDB(namespace, argoSettingsMgr, k8sClient)
askPassServer := askpass.NewServer()
scmAuth := generators.SCMAuthProviders{
GitHubApps: github_app.NewAuthCredentials(argoCDDB.(db.RepoCredsDB)),
}
tlsConfig := apiclient.TLSConfiguration{
DisableTLS: repoServerPlaintext,
StrictValidation: repoServerPlaintext,
}
if !repoServerPlaintext && repoServerStrictTLS {
pool, err := tls.LoadX509CertPool(
fmt.Sprintf("%s/reposerver/tls/tls.crt", env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath)),
fmt.Sprintf("%s/reposerver/tls/ca.crt", env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath)),
)
errors.CheckError(err)
tlsConfig.Certificates = pool
}
repoClientset := apiclient.NewRepoServerClientset(argocdRepoServer, repoServerTimeoutSeconds, tlsConfig)
argoCDService, err := services.NewArgoCDService(argoCDDB, getSubmoduleEnabled(), repoClientset)
errors.CheckError(err)
terminalGenerators := map[string]generators.Generator{
"List": generators.NewListGenerator(),
"Clusters": generators.NewClusterGenerator(mgr.GetClient(), ctx, k8sClient, namespace),
"Git": generators.NewGitGenerator(argoCDService),
"Git": generators.NewGitGenerator(services.NewArgoCDService(argoCDDB, askPassServer, getSubmoduleEnabled())),
"SCMProvider": generators.NewSCMProviderGenerator(mgr.GetClient(), scmAuth),
"ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, dynamicClient, k8sClient, namespace),
"PullRequest": generators.NewPullRequestGenerator(mgr.GetClient(), scmAuth),
@@ -183,6 +165,7 @@ func NewCommand() *cobra.Command {
startWebhookServer(webhookHandler, webhookAddr)
}
go func() { errors.CheckError(askPassServer.Run(askpass.SocketPath)) }()
if err = (&controllers.ApplicationSetReconciler{
Generators: topLevelGenerators,
Client: mgr.GetClient(),
@@ -223,9 +206,6 @@ func NewCommand() *cobra.Command {
command.Flags().StringVar(&cmdutil.LogLevel, "loglevel", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_LOGLEVEL", "info"), "Set the logging level. One of: debug|info|warn|error")
command.Flags().BoolVar(&dryRun, "dry-run", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_DRY_RUN", false), "Enable dry run mode")
command.Flags().BoolVar(&enableProgressiveSyncs, "enable-progressive-syncs", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_PROGRESSIVE_SYNCS", false), "Enable use of the experimental progressive syncs feature.")
command.Flags().BoolVar(&repoServerPlaintext, "repo-server-plaintext", env.ParseBoolFromEnv("ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT", false), "Disable TLS on connections to repo server")
command.Flags().BoolVar(&repoServerStrictTLS, "repo-server-strict-tls", env.ParseBoolFromEnv("ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_STRICT_TLS", false), "Whether to use strict validation of the TLS cert presented by the repo server")
command.Flags().IntVar(&repoServerTimeoutSeconds, "repo-server-timeout-seconds", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_TIMEOUT_SECONDS", 60, 0, math.MaxInt64), "Repo server RPC call timeout seconds.")
return &command
}

View File

@@ -30,7 +30,7 @@ func NewCommand() *cobra.Command {
var command = cobra.Command{
Use: cliName,
Short: "Run ArgoCD ConfigManagementPlugin Server",
Long: "ArgoCD ConfigManagementPlugin Server is an internal service which runs as sidecar container in reposerver deployment. The following configuration options are available:",
Long: "ArgoCD ConfigManagementPlugin Server is an internal service which runs as sidecar container in reposerver deployment. It can be configured by following options.",
DisableAutoGenTag: true,
RunE: func(c *cobra.Command, args []string) error {
ctx := c.Context()

View File

@@ -55,6 +55,7 @@ func NewCommand() *cobra.Command {
argocdRepoServerStrictTLS bool
configMapName string
secretName string
applicationNamespaces []string
)
var command = cobra.Command{
Use: "controller",
@@ -138,7 +139,7 @@ func NewCommand() *cobra.Command {
log.Infof("serving metrics on port %d", metricsPort)
log.Infof("loading configuration %d", metricsPort)
ctrl := notificationscontroller.NewController(k8sClient, dynamicClient, argocdService, namespace, appLabelSelector, registry, secretName, configMapName)
ctrl := notificationscontroller.NewController(k8sClient, dynamicClient, argocdService, namespace, applicationNamespaces, appLabelSelector, registry, secretName, configMapName)
err = ctrl.Init(ctx)
if err != nil {
return err
@@ -161,5 +162,6 @@ func NewCommand() *cobra.Command {
command.Flags().BoolVar(&argocdRepoServerStrictTLS, "argocd-repo-server-strict-tls", false, "Perform strict validation of TLS certificates when connecting to repo server")
command.Flags().StringVar(&configMapName, "config-map-name", "argocd-notifications-cm", "Set notifications ConfigMap name")
command.Flags().StringVar(&secretName, "secret-name", "argocd-notifications-secret", "Set notifications Secret name")
command.Flags().StringSliceVar(&applicationNamespaces, "application-namespaces", env.StringsFromEnv("ARGOCD_APPLICATION_NAMESPACES", []string{}, ","), "List of additional namespaces that this controller should send notifications for")
return &command
}

View File

@@ -84,6 +84,8 @@ func NewCommand() *cobra.Command {
allowOutOfBoundsSymlinks bool
streamedManifestMaxTarSize string
streamedManifestMaxExtractedSize string
helmManifestMaxExtractedSize string
disableManifestMaxExtractedSize bool
)
var command = cobra.Command{
Use: cliName,
@@ -122,6 +124,9 @@ func NewCommand() *cobra.Command {
streamedManifestMaxExtractedSizeQuantity, err := resource.ParseQuantity(streamedManifestMaxExtractedSize)
errors.CheckError(err)
helmManifestMaxExtractedSizeQuantity, err := resource.ParseQuantity(helmManifestMaxExtractedSize)
errors.CheckError(err)
askPassServer := askpass.NewServer()
metricsServer := metrics.NewMetricsServer()
cacheutil.CollectMetrics(redisClient, metricsServer)
@@ -136,6 +141,7 @@ func NewCommand() *cobra.Command {
AllowOutOfBoundsSymlinks: allowOutOfBoundsSymlinks,
StreamedManifestMaxExtractedSize: streamedManifestMaxExtractedSizeQuantity.ToDec().Value(),
StreamedManifestMaxTarSize: streamedManifestMaxTarSizeQuantity.ToDec().Value(),
HelmManifestMaxExtractedSize: helmManifestMaxExtractedSizeQuantity.ToDec().Value(),
}, askPassServer)
errors.CheckError(err)
@@ -216,6 +222,8 @@ func NewCommand() *cobra.Command {
command.Flags().BoolVar(&allowOutOfBoundsSymlinks, "allow-oob-symlinks", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_ALLOW_OUT_OF_BOUNDS_SYMLINKS", false), "Allow out-of-bounds symlinks in repositories (not recommended)")
command.Flags().StringVar(&streamedManifestMaxTarSize, "streamed-manifest-max-tar-size", env.StringFromEnv("ARGOCD_REPO_SERVER_STREAMED_MANIFEST_MAX_TAR_SIZE", "100M"), "Maximum size of streamed manifest archives")
command.Flags().StringVar(&streamedManifestMaxExtractedSize, "streamed-manifest-max-extracted-size", env.StringFromEnv("ARGOCD_REPO_SERVER_STREAMED_MANIFEST_MAX_EXTRACTED_SIZE", "1G"), "Maximum size of streamed manifest archives when extracted")
command.Flags().StringVar(&helmManifestMaxExtractedSize, "helm-manifest-max-extracted-size", env.StringFromEnv("ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE", "1G"), "Maximum size of helm manifest archives when extracted")
command.Flags().BoolVar(&disableManifestMaxExtractedSize, "disable-helm-manifest-max-extracted-size", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE", false), "Disable maximum size of helm manifest archives when extracted")
tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(&command)
cacheSrc = reposervercache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {
redisClient = client

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"math"
"strings"
"time"
"github.com/argoproj/pkg/stats"
@@ -60,6 +61,7 @@ func NewCommand() *cobra.Command {
repoServerAddress string
dexServerAddress string
disableAuth bool
contentTypes string
enableGZip bool
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
cacheSrc func() (*servercache.Cache, error)
@@ -164,6 +166,11 @@ func NewCommand() *cobra.Command {
baseHRef = rootPath
}
var contentTypesList []string
if contentTypes != "" {
contentTypesList = strings.Split(contentTypes, ";")
}
argoCDOpts := server.ArgoCDServerOpts{
Insecure: insecure,
ListenPort: listenPort,
@@ -177,6 +184,7 @@ func NewCommand() *cobra.Command {
DexServerAddr: dexServerAddress,
DexTLSConfig: dexTlsConfig,
DisableAuth: disableAuth,
ContentTypes: contentTypesList,
EnableGZip: enableGZip,
TLSConfigCustomizer: tlsConfigCustomizer,
Cache: cache,
@@ -225,6 +233,7 @@ func NewCommand() *cobra.Command {
command.Flags().StringVar(&dexServerAddress, "dex-server", env.StringFromEnv("ARGOCD_SERVER_DEX_SERVER", common.DefaultDexServerAddr), "Dex server address")
command.Flags().BoolVar(&disableAuth, "disable-auth", env.ParseBoolFromEnv("ARGOCD_SERVER_DISABLE_AUTH", false), "Disable client authentication")
command.Flags().BoolVar(&enableGZip, "enable-gzip", env.ParseBoolFromEnv("ARGOCD_SERVER_ENABLE_GZIP", false), "Enable GZIP compression")
command.Flags().StringVar(&contentTypes, "api-content-types", env.StringFromEnv("ARGOCD_API_CONTENT_TYPES", "application/json"), "Semicolon separated list of allowed content types for non GET api requests. Any content type is allowed if empty.")
command.AddCommand(cli.NewVersionCmd(cliName))
command.Flags().IntVar(&listenPort, "port", common.DefaultPortAPIServer, "Listen on given port")
command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortArgoCDAPIServerMetrics, "Start metrics on given port")

View File

@@ -132,6 +132,7 @@ func NewImportCommand() *cobra.Command {
errors.CheckError(err)
config.QPS = 100
config.Burst = 50
errors.CheckError(err)
namespace, _, err := clientConfig.Namespace()
errors.CheckError(err)
acdClients := newArgoCDClientsets(config, namespace)

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"os"
"strings"
"github.com/ghodss/yaml"
log "github.com/sirupsen/logrus"
@@ -292,9 +293,11 @@ func getPolicyFromConfigMap(cm *corev1.ConfigMap) (string, string, string) {
if !ok {
userPolicy = ""
}
defaultRole, ok = cm.Data[rbac.ConfigMapPolicyDefaultKey]
if !ok {
defaultRole = ""
if defaultRole == "" {
defaultRole, ok = cm.Data[rbac.ConfigMapPolicyDefaultKey]
if !ok {
defaultRole = ""
}
}
return userPolicy, defaultRole, cm.Data[rbac.ConfigMapMatchModeKey]
@@ -371,6 +374,9 @@ func resolveRBACResourceName(name string) string {
// isValidRBACAction checks whether a given action is a valid RBAC action
func isValidRBACAction(action string) bool {
if strings.HasPrefix(action, rbacpolicy.ActionAction+"/") {
return true
}
_, ok := validRBACActions[action]
return ok
}

View File

@@ -27,6 +27,11 @@ func Test_isValidRBACAction(t *testing.T) {
})
}
func Test_isValidRBACAction_ActionAction(t *testing.T) {
ok := isValidRBACAction("action/apps/Deployment/restart")
assert.True(t, ok)
}
func Test_isValidRBACResource(t *testing.T) {
for k := range validRBACResources {
t.Run(k, func(t *testing.T) {

View File

@@ -1025,7 +1025,7 @@ func findandPrintDiff(ctx context.Context, app *argoappv1.Application, resources
items := make([]objKeyLiveTarget, 0)
if diffOptions.local != "" {
localObjs := groupObjsByKey(getLocalObjects(ctx, app, diffOptions.local, diffOptions.localRepoRoot, argoSettings.AppLabelKey, diffOptions.cluster.Info.ServerVersion, diffOptions.cluster.Info.APIVersions, argoSettings.KustomizeOptions, argoSettings.ConfigManagementPlugins, argoSettings.TrackingMethod), liveObjs, app.Spec.Destination.Namespace)
items = groupObjsForDiff(resources, localObjs, items, argoSettings, app.InstanceName(argoSettings.ControllerNamespace))
items = groupObjsForDiff(resources, localObjs, items, argoSettings, app.InstanceName(argoSettings.ControllerNamespace), app.Spec.Destination.Namespace)
} else if diffOptions.revision != "" {
var unstructureds []*unstructured.Unstructured
for _, mfst := range diffOptions.res.Manifests {
@@ -1034,7 +1034,7 @@ func findandPrintDiff(ctx context.Context, app *argoappv1.Application, resources
unstructureds = append(unstructureds, obj)
}
groupedObjs := groupObjsByKey(unstructureds, liveObjs, app.Spec.Destination.Namespace)
items = groupObjsForDiff(resources, groupedObjs, items, argoSettings, app.Name)
items = groupObjsForDiff(resources, groupedObjs, items, argoSettings, app.InstanceName(argoSettings.ControllerNamespace), app.Spec.Destination.Namespace)
} else if diffOptions.serversideRes != nil {
var unstructureds []*unstructured.Unstructured
for _, mfst := range diffOptions.serversideRes.Manifests {
@@ -1043,7 +1043,7 @@ func findandPrintDiff(ctx context.Context, app *argoappv1.Application, resources
unstructureds = append(unstructureds, obj)
}
groupedObjs := groupObjsByKey(unstructureds, liveObjs, app.Spec.Destination.Namespace)
items = groupObjsForDiff(resources, groupedObjs, items, argoSettings, app.Name)
items = groupObjsForDiff(resources, groupedObjs, items, argoSettings, app.InstanceName(argoSettings.ControllerNamespace), app.Spec.Destination.Namespace)
} else {
for i := range resources.Items {
res := resources.Items[i]
@@ -1103,7 +1103,7 @@ func findandPrintDiff(ctx context.Context, app *argoappv1.Application, resources
return foundDiffs
}
func groupObjsForDiff(resources *application.ManagedResourcesResponse, objs map[kube.ResourceKey]*unstructured.Unstructured, items []objKeyLiveTarget, argoSettings *settings.Settings, appName string) []objKeyLiveTarget {
func groupObjsForDiff(resources *application.ManagedResourcesResponse, objs map[kube.ResourceKey]*unstructured.Unstructured, items []objKeyLiveTarget, argoSettings *settings.Settings, appName, namespace string) []objKeyLiveTarget {
resourceTracking := argo.NewResourceTracking()
for _, res := range resources.Items {
var live = &unstructured.Unstructured{}
@@ -1118,7 +1118,7 @@ func groupObjsForDiff(resources *application.ManagedResourcesResponse, objs map[
}
if local, ok := objs[key]; ok || live != nil {
if local != nil && !kube.IsCRD(local) {
err = resourceTracking.SetAppInstance(local, argoSettings.AppLabelKey, appName, "", argoappv1.TrackingMethod(argoSettings.GetTrackingMethod()))
err = resourceTracking.SetAppInstance(local, argoSettings.AppLabelKey, appName, namespace, argoappv1.TrackingMethod(argoSettings.GetTrackingMethod()))
errors.CheckError(err)
}
@@ -1495,7 +1495,7 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
}
}
for _, appName := range appNames {
_, err := waitOnApplicationStatus(ctx, acdClient, appName, timeout, watch, selectedResources)
_, _, err := waitOnApplicationStatus(ctx, acdClient, appName, timeout, watch, selectedResources)
errors.CheckError(err)
}
},
@@ -1659,8 +1659,15 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
errors.CheckError(err)
if app.Spec.HasMultipleSources() {
log.Fatal("argocd cli does not work on multi-source app")
return
if revision != "" {
log.Fatal("argocd cli does not work on multi-source app with --revision flag")
return
}
if local != "" {
log.Fatal("argocd cli does not work on multi-source app with --local flag")
return
}
}
// filters out only those resources that needs to be synced
@@ -1772,15 +1779,15 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
errors.CheckError(err)
if !async {
app, err := waitOnApplicationStatus(ctx, acdClient, appQualifiedName, timeout, watchOpts{operation: true}, selectedResources)
app, opState, err := waitOnApplicationStatus(ctx, acdClient, appQualifiedName, timeout, watchOpts{operation: true}, selectedResources)
errors.CheckError(err)
if !dryRun {
if !app.Status.OperationState.Phase.Successful() {
log.Fatalf("Operation has completed with phase: %s", app.Status.OperationState.Phase)
if !opState.Phase.Successful() {
log.Fatalf("Operation has completed with phase: %s", opState.Phase)
} else if len(selectedResources) == 0 && app.Status.Sync.Status != argoappv1.SyncStatusCodeSynced {
// Only get resources to be pruned if sync was application-wide and final status is not synced
pruningRequired := app.Status.OperationState.SyncResult.Resources.PruningRequired()
pruningRequired := opState.SyncResult.Resources.PruningRequired()
if pruningRequired > 0 {
log.Fatalf("%d resources require pruning", pruningRequired)
}
@@ -1994,7 +2001,10 @@ func checkResourceStatus(watch watchOpts, healthStatus string, syncStatus string
const waitFormatString = "%s\t%5s\t%10s\t%10s\t%20s\t%8s\t%7s\t%10s\t%s\n"
func waitOnApplicationStatus(ctx context.Context, acdClient argocdclient.Client, appName string, timeout uint, watch watchOpts, selectedResources []*argoappv1.SyncOperationResource) (*argoappv1.Application, error) {
// waitOnApplicationStatus watches an application and blocks until either the desired watch conditions
// are fulfiled or we reach the timeout. Returns the app once desired conditions have been filled.
// Additionally return the operationState at time of fulfilment (which may be different than returned app).
func waitOnApplicationStatus(ctx context.Context, acdClient argocdclient.Client, appName string, timeout uint, watch watchOpts, selectedResources []*argoappv1.SyncOperationResource) (*argoappv1.Application, *argoappv1.OperationState, error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
@@ -2052,10 +2062,20 @@ func waitOnApplicationStatus(ctx context.Context, acdClient argocdclient.Client,
AppNamespace: &appNs,
})
errors.CheckError(err)
// printFinalStatus() will refresh and update the app object, potentially causing the app's
// status.operationState to be different than the version when we break out of the event loop.
// This means the app.status is unreliable for determining the final state of the operation.
// finalOperationState captures the operationState as it was seen when we met the conditions of
// the wait, so the caller can rely on it to determine the outcome of the operation.
// See: https://github.com/argoproj/argo-cd/issues/5592
finalOperationState := app.Status.OperationState
appEventCh := acdClient.WatchApplicationWithRetry(ctx, appName, app.ResourceVersion)
for appEvent := range appEventCh {
app = &appEvent.Application
finalOperationState = app.Status.OperationState
operationInProgress := false
// consider the operation is in progress
if app.Operation != nil {
@@ -2093,7 +2113,7 @@ func waitOnApplicationStatus(ctx context.Context, acdClient argocdclient.Client,
if selectedResourcesAreReady && (!operationInProgress || !watch.operation) {
app = printFinalStatus(app)
return app, nil
return app, finalOperationState, nil
}
newStates := groupResourceStates(app, selectedResources)
@@ -2103,7 +2123,7 @@ func waitOnApplicationStatus(ctx context.Context, acdClient argocdclient.Client,
if prevState, found := prevStates[stateKey]; found {
if watch.health && prevState.Health != string(health.HealthStatusUnknown) && prevState.Health != string(health.HealthStatusDegraded) && newState.Health == string(health.HealthStatusDegraded) {
_ = printFinalStatus(app)
return nil, fmt.Errorf("application '%s' health state has transitioned from %s to %s", appName, prevState.Health, newState.Health)
return nil, finalOperationState, fmt.Errorf("application '%s' health state has transitioned from %s to %s", appName, prevState.Health, newState.Health)
}
doPrint = prevState.Merge(newState)
} else {
@@ -2117,7 +2137,7 @@ func waitOnApplicationStatus(ctx context.Context, acdClient argocdclient.Client,
_ = w.Flush()
}
_ = printFinalStatus(app)
return nil, fmt.Errorf("timed out (%ds) waiting for app %q match desired state", timeout, appName)
return nil, finalOperationState, 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
@@ -2273,7 +2293,7 @@ func NewApplicationRollbackCommand(clientOpts *argocdclient.ClientOptions) *cobr
})
errors.CheckError(err)
_, err = waitOnApplicationStatus(ctx, acdClient, app.QualifiedName(), timeout, watchOpts{
_, _, err = waitOnApplicationStatus(ctx, acdClient, app.QualifiedName(), timeout, watchOpts{
operation: true,
}, nil)
errors.CheckError(err)

View File

@@ -1067,12 +1067,12 @@ func TestFilterAppResources(t *testing.T) {
selectedResources []*argoappv1.SyncOperationResource
expectedResult []*argoappv1.SyncOperationResource
}{
// --resource apps:ReplicaSet:replicaSet-name1 --resource *:Service:*
//--resource apps:ReplicaSet:replicaSet-name1 --resource *:Service:*
{testName: "Include ReplicaSet replicaSet-name1 resouce and all service resources",
selectedResources: []*argoappv1.SyncOperationResource{&includeAllServiceResources, &includeReplicaSet1Resource},
expectedResult: []*argoappv1.SyncOperationResource{&replicaSet1, &service1, &service2},
},
// --resource apps:ReplicaSet:replicaSet-name1 --resource !*:Service:*
//--resource apps:ReplicaSet:replicaSet-name1 --resource !*:Service:*
{testName: "Include ReplicaSet replicaSet-name1 resouce and exclude all service resources",
selectedResources: []*argoappv1.SyncOperationResource{&excludeAllServiceResources, &includeReplicaSet1Resource},
expectedResult: []*argoappv1.SyncOperationResource{&replicaSet1, &replicaSet2, &job, &deployment},

View File

@@ -8,7 +8,7 @@ import (
"golang.org/x/crypto/bcrypt"
)
// NewBcryptCmd represents the bcrypt command
// bcryptCmd represents the bcrypt command
func NewBcryptCmd() *cobra.Command {
var (
password string

View File

@@ -135,7 +135,7 @@ func NewCertAddTLSCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
return command
}
// NewCertAddSSHCommand returns a new instance of an `argocd cert add` command
// NewCertAddCommand returns a new instance of an `argocd cert add` command
func NewCertAddSSHCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
fromFile string

View File

@@ -7,6 +7,8 @@ import (
"github.com/stretchr/testify/assert"
)
//
func Test_userDisplayName_email(t *testing.T) {
claims := jwt.MapClaims{"iss": "qux", "sub": "foo", "email": "firstname.lastname@example.com", "groups": []string{"baz"}}
actualName := userDisplayName(claims)

View File

@@ -3,6 +3,7 @@ package commands
import (
"fmt"
"os"
"strconv"
"text/tabwriter"
log "github.com/sirupsen/logrus"
@@ -250,15 +251,12 @@ func printRepoTable(repos appsv1.Repositories) {
_, _ = fmt.Fprintf(w, "TYPE\tNAME\tREPO\tINSECURE\tOCI\tLFS\tCREDS\tSTATUS\tMESSAGE\tPROJECT\n")
for _, r := range repos {
var hasCreds string
if !r.HasCredentials() {
hasCreds = "false"
if r.InheritedCreds {
hasCreds = "inherited"
} else {
if r.InheritedCreds {
hasCreds = "inherited"
} else {
hasCreds = "true"
}
hasCreds = strconv.FormatBool(r.HasCredentials())
}
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%v\t%v\t%s\t%s\t%s\t%s\n", r.Type, r.Name, r.Repo, r.IsInsecure(), r.EnableOCI, r.EnableLFS, hasCreds, r.ConnectionState.Status, r.ConnectionState.Message, r.Project)
}
_ = w.Flush()

View File

@@ -116,9 +116,6 @@ func printClientVersion(version *common.Version, short bool) string {
output += fmt.Sprintf(" GoVersion: %s\n", version.GoVersion)
output += fmt.Sprintf(" Compiler: %s\n", version.Compiler)
output += fmt.Sprintf(" Platform: %s\n", version.Platform)
if version.ExtraBuildInfo != "" {
output += fmt.Sprintf(" ExtraBuildInfo: %s\n", version.ExtraBuildInfo)
}
return output
}
@@ -150,9 +147,6 @@ func printServerVersion(version *version.VersionMessage, short bool) string {
if version.Platform != "" {
output += fmt.Sprintf(" Platform: %s\n", version.Platform)
}
if version.ExtraBuildInfo != "" {
output += fmt.Sprintf(" ExtraBuildInfo: %s\n", version.ExtraBuildInfo)
}
if version.KustomizeVersion != "" {
output += fmt.Sprintf(" Kustomize Version: %s\n", version.KustomizeVersion)
}

View File

@@ -685,7 +685,7 @@ func setAnnotations(app *argoappv1.Application, annotations []string) {
}
}
// LiveObjects deserializes the list of live states into unstructured objects
// liveObjects deserializes the list of live states into unstructured objects
func LiveObjects(resources []*argoappv1.ResourceDiff) ([]*unstructured.Unstructured, error) {
objs := make([]*unstructured.Unstructured, len(resources))
for i, resState := range resources {

View File

@@ -94,7 +94,7 @@ func (opts *ProjectOpts) GetDestinations() []v1alpha1.ApplicationDestination {
return destinations
}
// GetSignatureKeys TODO: Get configured keys and emit warning when a key is specified that is not configured
// TODO: Get configured keys and emit warning when a key is specified that is not configured
func (opts *ProjectOpts) GetSignatureKeys() []v1alpha1.SignatureKey {
signatureKeys := make([]v1alpha1.SignatureKey, 0)
for _, keyStr := range opts.SignatureKeys {

View File

@@ -24,6 +24,7 @@ import (
"github.com/argoproj/argo-cd/v2/util/io/files"
"github.com/argoproj/gitops-engine/pkg/utils/kube"
"github.com/cyphar/filepath-securejoin"
"github.com/mattn/go-zglob"
log "github.com/sirupsen/logrus"
)
@@ -96,6 +97,14 @@ func runCommand(ctx context.Context, command Command, path string, env []string)
<-ctx.Done()
// Kill by group ID to make sure child processes are killed. The - tells `kill` that it's a group ID.
// Since we didn't set Pgid in SysProcAttr, the group ID is the same as the process ID. https://pkg.go.dev/syscall#SysProcAttr
// Sending a TERM signal first to allow any potential cleanup if needed, and then sending a KILL signal
_ = sysCallTerm(-cmd.Process.Pid)
// modify cleanup timeout to allow process to cleanup
cleanupTimeout := 5 * time.Second
time.Sleep(cleanupTimeout)
_ = sysCallKill(-cmd.Process.Pid)
}()
@@ -182,7 +191,7 @@ func getTempDirMustCleanup(baseDir string) (workDir string, cleanup func(), err
if err := os.RemoveAll(workDir); err != nil {
log.WithFields(map[string]interface{}{
common.SecurityField: common.SecurityHigh,
common.SecurityCWEField: 459,
common.SecurityCWEField: common.SecurityCWEIncompleteCleanup,
}).Errorf("Failed to clean up temp directory: %s", err)
}
}
@@ -302,7 +311,7 @@ func (s *Service) matchRepositoryGeneric(stream MatchRepositoryStream) error {
return fmt.Errorf("match repository error receiving stream: %w", err)
}
isSupported, isDiscoveryEnabled, err := s.matchRepository(bufferedCtx, workDir, metadata.GetEnv())
isSupported, isDiscoveryEnabled, err := s.matchRepository(bufferedCtx, workDir, metadata.GetEnv(), metadata.GetAppRelPath())
if err != nil {
return fmt.Errorf("match repository error: %w", err)
}
@@ -315,12 +324,20 @@ func (s *Service) matchRepositoryGeneric(stream MatchRepositoryStream) error {
return nil
}
func (s *Service) matchRepository(ctx context.Context, workdir string, envEntries []*apiclient.EnvEntry) (isSupported bool, isDiscoveryEnabled bool, err error) {
func (s *Service) matchRepository(ctx context.Context, workdir string, envEntries []*apiclient.EnvEntry, appRelPath string) (isSupported bool, isDiscoveryEnabled bool, err error) {
config := s.initConstants.PluginConfig
appPath, err := securejoin.SecureJoin(workdir, appRelPath)
if err != nil {
log.WithFields(map[string]interface{}{
common.SecurityField: common.SecurityHigh,
common.SecurityCWEField: common.SecurityCWEIncompleteCleanup,
}).Errorf("error joining workdir %q and appRelPath %q: %v", workdir, appRelPath, err)
}
if config.Spec.Discover.FileName != "" {
log.Debugf("config.Spec.Discover.FileName is provided")
pattern := filepath.Join(workdir, config.Spec.Discover.FileName)
pattern := filepath.Join(appPath, config.Spec.Discover.FileName)
matches, err := filepath.Glob(pattern)
if err != nil {
e := fmt.Errorf("error finding filename match for pattern %q: %w", pattern, err)
@@ -332,7 +349,7 @@ func (s *Service) matchRepository(ctx context.Context, workdir string, envEntrie
if config.Spec.Discover.Find.Glob != "" {
log.Debugf("config.Spec.Discover.Find.Glob is provided")
pattern := filepath.Join(workdir, config.Spec.Discover.Find.Glob)
pattern := filepath.Join(appPath, config.Spec.Discover.Find.Glob)
// filepath.Glob doesn't have '**' support hence selecting third-party lib
// https://github.com/golang/go/issues/11862
matches, err := zglob.Glob(pattern)
@@ -348,7 +365,7 @@ func (s *Service) matchRepository(ctx context.Context, workdir string, envEntrie
if len(config.Spec.Discover.Find.Command.Command) > 0 {
log.Debugf("Going to try runCommand.")
env := append(os.Environ(), environ(envEntries)...)
find, err := runCommand(ctx, config.Spec.Discover.Find.Command, workdir, env)
find, err := runCommand(ctx, config.Spec.Discover.Find.Command, appPath, env)
if err != nil {
return false, true, fmt.Errorf("error running find command: %w", err)
}

View File

@@ -100,7 +100,7 @@ func TestMatchRepository(t *testing.T) {
f := setup(t, withDiscover(d))
// when
match, discovery, err := f.service.matchRepository(context.Background(), f.path, f.env)
match, discovery, err := f.service.matchRepository(context.Background(), f.path, f.env, ".")
// then
assert.NoError(t, err)
@@ -115,7 +115,7 @@ func TestMatchRepository(t *testing.T) {
f := setup(t, withDiscover(d))
// when
match, discovery, err := f.service.matchRepository(context.Background(), f.path, f.env)
match, discovery, err := f.service.matchRepository(context.Background(), f.path, f.env, ".")
// then
assert.NoError(t, err)
@@ -130,7 +130,7 @@ func TestMatchRepository(t *testing.T) {
f := setup(t, withDiscover(d))
// when
_, _, err := f.service.matchRepository(context.Background(), f.path, f.env)
_, _, err := f.service.matchRepository(context.Background(), f.path, f.env, ".")
// then
assert.ErrorContains(t, err, "syntax error")
@@ -145,7 +145,7 @@ func TestMatchRepository(t *testing.T) {
f := setup(t, withDiscover(d))
// when
match, discovery, err := f.service.matchRepository(context.Background(), f.path, f.env)
match, discovery, err := f.service.matchRepository(context.Background(), f.path, f.env, ".")
// then
assert.NoError(t, err)
@@ -162,7 +162,7 @@ func TestMatchRepository(t *testing.T) {
f := setup(t, withDiscover(d))
// when
match, discovery, err := f.service.matchRepository(context.Background(), f.path, f.env)
match, discovery, err := f.service.matchRepository(context.Background(), f.path, f.env, ".")
// then
assert.NoError(t, err)
@@ -179,7 +179,7 @@ func TestMatchRepository(t *testing.T) {
f := setup(t, withDiscover(d))
// when
_, _, err := f.service.matchRepository(context.Background(), f.path, f.env)
_, _, err := f.service.matchRepository(context.Background(), f.path, f.env, ".")
// then
assert.ErrorContains(t, err, "error finding glob match for pattern")
@@ -196,7 +196,7 @@ func TestMatchRepository(t *testing.T) {
f := setup(t, withDiscover(d))
// when
match, discovery, err := f.service.matchRepository(context.Background(), f.path, f.env)
match, discovery, err := f.service.matchRepository(context.Background(), f.path, f.env, ".")
// then
assert.NoError(t, err)
@@ -215,7 +215,7 @@ func TestMatchRepository(t *testing.T) {
f := setup(t, withDiscover(d))
// when
match, discovery, err := f.service.matchRepository(context.Background(), f.path, f.env)
match, discovery, err := f.service.matchRepository(context.Background(), f.path, f.env, ".")
// then
assert.NoError(t, err)
assert.False(t, match)
@@ -233,7 +233,7 @@ func TestMatchRepository(t *testing.T) {
f := setup(t, withDiscover(d))
// when
match, discovery, err := f.service.matchRepository(context.Background(), f.path, f.env)
match, discovery, err := f.service.matchRepository(context.Background(), f.path, f.env, ".")
// then
assert.NoError(t, err)
@@ -253,7 +253,7 @@ func TestMatchRepository(t *testing.T) {
f := setup(t, withDiscover(d))
// when
match, discovery, err := f.service.matchRepository(context.Background(), f.path, f.env)
match, discovery, err := f.service.matchRepository(context.Background(), f.path, f.env, ".")
// then
assert.NoError(t, err)
@@ -272,7 +272,7 @@ func TestMatchRepository(t *testing.T) {
f := setup(t, withDiscover(d))
// when
match, discovery, err := f.service.matchRepository(context.Background(), f.path, f.env)
match, discovery, err := f.service.matchRepository(context.Background(), f.path, f.env, ".")
// then
assert.Error(t, err)
@@ -285,7 +285,7 @@ func TestMatchRepository(t *testing.T) {
f := setup(t, withDiscover(d))
// when
match, discovery, err := f.service.matchRepository(context.Background(), f.path, f.env)
match, discovery, err := f.service.matchRepository(context.Background(), f.path, f.env, ".")
// then
assert.NoError(t, err)
@@ -369,6 +369,28 @@ func TestRunCommandEmptyCommand(t *testing.T) {
assert.ErrorContains(t, err, "Command is empty")
}
// TestRunCommandContextTimeoutWithGracefulTermination makes sure that the process is given enough time to cleanup before sending SIGKILL.
func TestRunCommandContextTimeoutWithCleanup(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 900*time.Millisecond)
defer cancel()
// Use a subshell so there's a child command.
// This command sleeps for 4 seconds which is currently less than the 5 second delay between SIGTERM and SIGKILL signal and then exits successfully.
command := Command{
Command: []string{"sh", "-c"},
Args: []string{`(trap 'echo "cleanup completed"; exit' TERM; sleep 4)`},
}
before := time.Now()
output, err := runCommand(ctx, command, "", []string{})
after := time.Now()
assert.Error(t, err) // The command should time out, causing an error.
assert.Less(t, after.Sub(before), 1*time.Second)
// The command should still have completed the cleanup after termination.
assert.Contains(t, output, "cleanup completed")
}
func Test_getParametersAnnouncement_empty_command(t *testing.T) {
staticYAML := `
- name: static-a

View File

@@ -14,3 +14,7 @@ func newSysProcAttr(setpgid bool) *syscall.SysProcAttr {
func sysCallKill(pid int) error {
return syscall.Kill(pid, syscall.SIGKILL)
}
func sysCallTerm(pid int) error {
return syscall.Kill(pid, syscall.SIGTERM)
}

View File

@@ -14,3 +14,7 @@ func newSysProcAttr(setpgid bool) *syscall.SysProcAttr {
func sysCallKill(pid int) error {
return nil
}
func sysCallTerm(pid int) error {
return nil
}

View File

@@ -29,9 +29,9 @@ const (
ArgoCDNotificationsConfigMapName = "argocd-notifications-cm"
ArgoCDNotificationsSecretName = "argocd-notifications-secret"
ArgoCDRBACConfigMapName = "argocd-rbac-cm"
// ArgoCDKnownHostsConfigMapName contains SSH known hosts data for connecting repositories. Will get mounted as volume to pods
// Contains SSH known hosts data for connecting repositories. Will get mounted as volume to pods
ArgoCDKnownHostsConfigMapName = "argocd-ssh-known-hosts-cm"
// ArgoCDTLSCertsConfigMapName contains TLS certificate data for connecting repositories. Will get mounted as volume to pods
// Contains TLS certificate data for connecting repositories. Will get mounted as volume to pods
ArgoCDTLSCertsConfigMapName = "argocd-tls-certs-cm"
ArgoCDGPGKeysConfigMapName = "argocd-gpg-keys-cm"
)
@@ -51,28 +51,28 @@ const (
DefaultPortRepoServerMetrics = 8084
)
// DefaultAddressAPIServer for ArgoCD components
// Default listener address for ArgoCD components
const (
DefaultAddressAPIServer = "localhost"
)
// Default paths on the pod's file system
const (
// DefaultPathTLSConfig is the default path where TLS certificates for repositories are located
// The default path where TLS certificates for repositories are located
DefaultPathTLSConfig = "/app/config/tls"
// DefaultPathSSHConfig is the default path where SSH known hosts are stored
// The default path where SSH known hosts are stored
DefaultPathSSHConfig = "/app/config/ssh"
// DefaultSSHKnownHostsName is the Default name for the SSH known hosts file
// Default name for the SSH known hosts file
DefaultSSHKnownHostsName = "ssh_known_hosts"
// DefaultGnuPgHomePath is the Default path to GnuPG home directory
// Default path to GnuPG home directory
DefaultGnuPgHomePath = "/app/config/gpg/keys"
// DefaultAppConfigPath is the Default path to repo server TLS endpoint config
// Default path to repo server TLS endpoint config
DefaultAppConfigPath = "/app/config"
// DefaultPluginSockFilePath is the Default path to cmp server plugin socket file
// Default path to cmp server plugin socket file
DefaultPluginSockFilePath = "/home/argocd/cmp-server/plugins"
// DefaultPluginConfigFilePath is the Default path to cmp server plugin configuration file
// Default path to cmp server plugin configuration file
DefaultPluginConfigFilePath = "/home/argocd/cmp-server/config"
// PluginConfigFileName is the Plugin Config File is a ConfigManagementPlugin manifest located inside the plugin container
// Plugin Config File is a ConfigManagementPlugin manifest located inside the plugin container
PluginConfigFileName = "plugin.yaml"
)
@@ -139,7 +139,7 @@ const (
// LabelValueSecretTypeRepoCreds indicates a secret type of repository credentials
LabelValueSecretTypeRepoCreds = "repo-creds"
// AnnotationKeyAppInstance is the Argo CD application name is used as the instance name
// The Argo CD application name is used as the instance name
AnnotationKeyAppInstance = "argocd.argoproj.io/tracking-id"
// AnnotationCompareOptions is a comma-separated list of options for comparison
@@ -171,19 +171,19 @@ const (
EnvVarSSODebug = "ARGOCD_SSO_DEBUG"
// EnvVarRBACDebug is an environment variable to enable additional RBAC debugging in the API server
EnvVarRBACDebug = "ARGOCD_RBAC_DEBUG"
// EnvVarSSHDataPath overrides the location where SSH known hosts for repo access data is stored
// Overrides the location where SSH known hosts for repo access data is stored
EnvVarSSHDataPath = "ARGOCD_SSH_DATA_PATH"
// EnvVarTLSDataPath overrides the location where TLS certificate for repo access data is stored
// Overrides the location where TLS certificate for repo access data is stored
EnvVarTLSDataPath = "ARGOCD_TLS_DATA_PATH"
// EnvGitAttemptsCount specifies number of git remote operations attempts count
// Specifies number of git remote operations attempts count
EnvGitAttemptsCount = "ARGOCD_GIT_ATTEMPTS_COUNT"
// EnvGitRetryMaxDuration specifices max duration of git remote operation retry
// Specifices max duration of git remote operation retry
EnvGitRetryMaxDuration = "ARGOCD_GIT_RETRY_MAX_DURATION"
// EnvGitRetryDuration specifies duration of git remote operation retry
// Specifies duration of git remote operation retry
EnvGitRetryDuration = "ARGOCD_GIT_RETRY_DURATION"
// EnvGitRetryFactor specifies fator of git remote operation retry
// Specifies fator of git remote operation retry
EnvGitRetryFactor = "ARGOCD_GIT_RETRY_FACTOR"
// EnvGitSubmoduleEnabled overrides git submodule support, true by default
// Overrides git submodule support, true by default
EnvGitSubmoduleEnabled = "ARGOCD_GIT_MODULES_ENABLED"
// EnvGnuPGHome is the path to ArgoCD's GnuPG keyring for signature verification
EnvGnuPGHome = "ARGOCD_GNUPGHOME"
@@ -205,7 +205,7 @@ const (
EnvGithubAppCredsExpirationDuration = "ARGOCD_GITHUB_APP_CREDS_EXPIRATION_DURATION"
// EnvHelmIndexCacheDuration controls how the helm repository index file is cached for (default: 0)
EnvHelmIndexCacheDuration = "ARGOCD_HELM_INDEX_CACHE_DURATION"
// EnvAppConfigPath allows to override the configuration path for repo server
// EnvRepoServerConfigPath allows to override the configuration path for repo server
EnvAppConfigPath = "ARGOCD_APP_CONF_PATH"
// EnvLogFormat log format that is defined by `--logformat` option
EnvLogFormat = "ARGOCD_LOG_FORMAT"
@@ -296,29 +296,32 @@ func GetCMPWorkDir() string {
}
const (
// AnnotationApplicationSetRefresh is an annotation that is added when an ApplicationSet is requested to be refreshed by a webhook. The ApplicationSet controller will remove this annotation at the end of reconciliation.
// AnnotationApplicationRefresh is an annotation that is added when an ApplicationSet is requested to be refreshed by a webhook. The ApplicationSet controller will remove this annotation at the end of reconciliation.
AnnotationApplicationSetRefresh = "argocd.argoproj.io/application-set-refresh"
)
// gRPC settings
const (
GRPCKeepAliveEnforcementMinimum = 10 * time.Second
// GRPCKeepAliveTime is 2x enforcement minimum to ensure network jitter does not introduce ENHANCE_YOUR_CALM errors
// Keep alive is 2x enforcement minimum to ensure network jitter does not introduce ENHANCE_YOUR_CALM errors
GRPCKeepAliveTime = 2 * GRPCKeepAliveEnforcementMinimum
)
// Security severity logging
const (
SecurityField = "security"
SecurityCWEField = "CWE"
SecurityEmergency = 5 // Indicates unmistakably malicious events that should NEVER occur accidentally and indicates an active attack (i.e. brute forcing, DoS)
SecurityCritical = 4 // Indicates any malicious or exploitable event that had a side effect (i.e. secrets being left behind on the filesystem)
SecurityHigh = 3 // Indicates likely malicious events but one that had no side effects or was blocked (i.e. out of bounds symlinks in repos)
SecurityMedium = 2 // Could indicate malicious events, but has a high likelihood of being user/system error (i.e. access denied)
SecurityLow = 1 // Unexceptional entries (i.e. successful access logs)
SecurityField = "security"
// SecurityCWEField is the logs field for the CWE associated with a log line. CWE stands for Common Weakness Enumeration. See https://cwe.mitre.org/
SecurityCWEField = "CWE"
SecurityCWEIncompleteCleanup = 459
SecurityCWEMissingReleaseOfFileDescriptor = 775
SecurityEmergency = 5 // Indicates unmistakably malicious events that should NEVER occur accidentally and indicates an active attack (i.e. brute forcing, DoS)
SecurityCritical = 4 // Indicates any malicious or exploitable event that had a side effect (i.e. secrets being left behind on the filesystem)
SecurityHigh = 3 // Indicates likely malicious events but one that had no side effects or was blocked (i.e. out of bounds symlinks in repos)
SecurityMedium = 2 // Could indicate malicious events, but has a high likelihood of being user/system error (i.e. access denied)
SecurityLow = 1 // Unexceptional entries (i.e. successful access logs)
)
// TokenVerificationError is a generic error message for a failure to verify a JWT
// Common error messages
const TokenVerificationError = "failed to verify the token"
var TokenVerificationErr = errors.New(TokenVerificationError)

View File

@@ -16,7 +16,6 @@ var (
gitTag = "" // output from `git describe --exact-match --tags HEAD` (if clean tree state)
gitTreeState = "" // determined from `git status --porcelain`. either 'clean' or 'dirty'
kubectlVersion = "" // determined from go.mod file
extraBuildInfo = "" // extra build information for vendors to populate during build
)
// Version contains Argo version information
@@ -30,7 +29,6 @@ type Version struct {
Compiler string
Platform string
KubectlVersion string
ExtraBuildInfo string
}
func (v Version) String() string {
@@ -68,7 +66,6 @@ func GetVersion() Version {
versionStr += "+unknown"
}
}
return Version{
Version: versionStr,
BuildDate: buildDate,
@@ -79,6 +76,5 @@ func GetVersion() Version {
Compiler: runtime.Compiler,
Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
KubectlVersion: kubectlVersion,
ExtraBuildInfo: extraBuildInfo,
}
}

View File

@@ -1240,40 +1240,44 @@ func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Appli
}
func (ctrl *ApplicationController) setOperationState(app *appv1.Application, state *appv1.OperationState) {
kube.RetryUntilSucceed(context.Background(), updateOperationStateTimeout, "Update application operation state", logutils.NewLogrusLogger(logutils.NewWithCurrentConfig()), func() error {
if state.Phase == "" {
// expose any bugs where we neglect to set phase
panic("no phase was set")
}
if state.Phase.Completed() {
now := metav1.Now()
state.FinishedAt = &now
}
patch := map[string]interface{}{
"status": map[string]interface{}{
"operationState": state,
},
}
if state.Phase.Completed() {
// If operation is completed, clear the operation field to indicate no operation is
// in progress.
patch["operation"] = nil
}
if reflect.DeepEqual(app.Status.OperationState, state) {
log.Infof("No operation updates necessary to '%s'. Skipping patch", app.QualifiedName())
return nil
}
patchJSON, err := json.Marshal(patch)
if err != nil {
return fmt.Errorf("error marshaling json: %w", err)
}
if app.Status.OperationState != nil && app.Status.OperationState.FinishedAt != nil && state.FinishedAt == nil {
patchJSON, err = jsonpatch.MergeMergePatches(patchJSON, []byte(`{"status": {"operationState": {"finishedAt": null}}}`))
if err != nil {
return fmt.Errorf("error merging operation state patch: %w", err)
}
}
logCtx := log.WithFields(log.Fields{"application": app.Name, "appNamespace": app.Namespace, "project": app.Spec.Project})
if state.Phase == "" {
// expose any bugs where we neglect to set phase
panic("no phase was set")
}
if state.Phase.Completed() {
now := metav1.Now()
state.FinishedAt = &now
}
patch := map[string]interface{}{
"status": map[string]interface{}{
"operationState": state,
},
}
if state.Phase.Completed() {
// If operation is completed, clear the operation field to indicate no operation is
// in progress.
patch["operation"] = nil
}
if reflect.DeepEqual(app.Status.OperationState, state) {
logCtx.Infof("No operation updates necessary to '%s'. Skipping patch", app.QualifiedName())
return
}
patchJSON, err := json.Marshal(patch)
if err != nil {
logCtx.Errorf("error marshaling json: %v", err)
return
}
if app.Status.OperationState != nil && app.Status.OperationState.FinishedAt != nil && state.FinishedAt == nil {
patchJSON, err = jsonpatch.MergeMergePatches(patchJSON, []byte(`{"status": {"operationState": {"finishedAt": null}}}`))
if err != nil {
logCtx.Errorf("error merging operation state patch: %v", err)
return
}
}
kube.RetryUntilSucceed(context.Background(), updateOperationStateTimeout, "Update application operation state", logutils.NewLogrusLogger(logutils.NewWithCurrentConfig()), func() error {
appClient := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace)
_, err = appClient.Patch(context.Background(), app.Name, types.MergePatchType, patchJSON, metav1.PatchOptions{})
if err != nil {
@@ -1281,32 +1285,36 @@ func (ctrl *ApplicationController) setOperationState(app *appv1.Application, sta
if apierr.IsNotFound(err) {
return nil
}
// kube.RetryUntilSucceed logs failed attempts at "debug" level, but we want to know if this fails. Log a
// warning.
logCtx.Warnf("error patching application with operation state: %v", err)
return fmt.Errorf("error patching application with operation state: %w", err)
}
log.Infof("updated '%s' operation (phase: %s)", app.QualifiedName(), state.Phase)
if state.Phase.Completed() {
eventInfo := argo.EventInfo{Reason: argo.EventReasonOperationCompleted}
var messages []string
if state.Operation.Sync != nil && len(state.Operation.Sync.Resources) > 0 {
messages = []string{"Partial sync operation"}
} else {
messages = []string{"Sync operation"}
}
if state.SyncResult != nil {
messages = append(messages, "to", state.SyncResult.Revision)
}
if state.Phase.Successful() {
eventInfo.Type = v1.EventTypeNormal
messages = append(messages, "succeeded")
} else {
eventInfo.Type = v1.EventTypeWarning
messages = append(messages, "failed:", state.Message)
}
ctrl.auditLogger.LogAppEvent(app, eventInfo, strings.Join(messages, " "), "")
ctrl.metricsServer.IncSync(app, state)
}
return nil
})
logCtx.Infof("updated '%s' operation (phase: %s)", app.QualifiedName(), state.Phase)
if state.Phase.Completed() {
eventInfo := argo.EventInfo{Reason: argo.EventReasonOperationCompleted}
var messages []string
if state.Operation.Sync != nil && len(state.Operation.Sync.Resources) > 0 {
messages = []string{"Partial sync operation"}
} else {
messages = []string{"Sync operation"}
}
if state.SyncResult != nil {
messages = append(messages, "to", state.SyncResult.Revision)
}
if state.Phase.Successful() {
eventInfo.Type = v1.EventTypeNormal
messages = append(messages, "succeeded")
} else {
eventInfo.Type = v1.EventTypeWarning
messages = append(messages, "failed:", state.Message)
}
ctrl.auditLogger.LogAppEvent(app, eventInfo, strings.Join(messages, " "), "")
ctrl.metricsServer.IncSync(app, state)
}
}
func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext bool) {

View File

@@ -3,9 +3,11 @@ package controller
import (
"context"
"encoding/json"
"errors"
"testing"
"time"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/api/resource"
clustercache "github.com/argoproj/gitops-engine/pkg/cache"
@@ -50,6 +52,7 @@ type namespacedResource struct {
type fakeData struct {
apps []runtime.Object
manifestResponse *apiclient.ManifestResponse
manifestResponses []*apiclient.ManifestResponse
managedLiveObjs map[kube.ResourceKey]*unstructured.Unstructured
namespacedResources map[kube.ResourceKey]namespacedResource
configMapData map[string]string
@@ -65,7 +68,15 @@ func newFakeController(data *fakeData) *ApplicationController {
// Mock out call to GenerateManifest
mockRepoClient := mockrepoclient.RepoServerServiceClient{}
mockRepoClient.On("GenerateManifest", mock.Anything, mock.Anything).Return(data.manifestResponse, nil)
if len(data.manifestResponses) > 0 {
for _, response := range data.manifestResponses {
mockRepoClient.On("GenerateManifest", mock.Anything, mock.Anything).Return(response, nil).Once()
}
} else {
mockRepoClient.On("GenerateManifest", mock.Anything, mock.Anything).Return(data.manifestResponse, nil)
}
mockRepoClientset := mockrepoclient.Clientset{RepoServerServiceClient: &mockRepoClient}
secret := corev1.Secret{
@@ -223,9 +234,14 @@ spec:
project: default
sources:
- path: some/path
helm:
valueFiles:
- $values_test/values.yaml
repoURL: https://github.com/argoproj/argocd-example-apps.git
- path: some/other/path
repoURL: https://github.com/argoproj/argocd-example-apps-fake.git
- ref: values_test
repoURL: https://github.com/argoproj/argocd-example-apps-fake-ref.git
syncPolicy:
automated: {}
status:
@@ -237,6 +253,7 @@ status:
revisions:
- HEAD
- HEAD
- HEAD
phase: Succeeded
startedAt: 2018-09-21T23:50:25Z
syncResult:
@@ -251,11 +268,14 @@ status:
revisions:
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
- cccccccccccccccccccccccccccccccccccccccc
sources:
- path: some/path
repoURL: https://github.com/argoproj/argocd-example-apps.git
- path: some/other/path
repoURL: https://github.com/argoproj/argocd-example-apps-fake.git
- path: some/other/path
repoURL: https://github.com/argoproj/argocd-example-apps-fake-ref.git
`
var fakeAppWithDestName = `
@@ -909,6 +929,41 @@ func TestSetOperationStateOnDeletedApp(t *testing.T) {
assert.True(t, patched)
}
type logHook struct {
entries []logrus.Entry
}
func (h *logHook) Levels() []logrus.Level {
return []logrus.Level{logrus.WarnLevel}
}
func (h *logHook) Fire(entry *logrus.Entry) error {
h.entries = append(h.entries, *entry)
return nil
}
func TestSetOperationStateLogRetries(t *testing.T) {
hook := logHook{}
logrus.AddHook(&hook)
t.Cleanup(func() {
logrus.StandardLogger().ReplaceHooks(logrus.LevelHooks{})
})
ctrl := newFakeController(&fakeData{apps: []runtime.Object{}})
fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset)
fakeAppCs.ReactionChain = nil
patched := false
fakeAppCs.AddReactor("patch", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
if !patched {
patched = true
return true, nil, errors.New("fake error")
}
return true, nil, nil
})
ctrl.setOperationState(newFakeApp(), &v1alpha1.OperationState{Phase: synccommon.OperationSucceeded})
assert.True(t, patched)
assert.Contains(t, hook.entries[0].Message, "fake error")
}
func TestNeedRefreshAppStatus(t *testing.T) {
testCases := []struct {
name string

View File

@@ -45,7 +45,7 @@ const (
// EnvClusterCacheWatchResyncDuration is the env variable that holds cluster cache watch re-sync duration
EnvClusterCacheWatchResyncDuration = "ARGOCD_CLUSTER_CACHE_WATCH_RESYNC_DURATION"
// EnvClusterSyncRetryTimeoutDuration is the env variable that holds cluster retry duration when sync error happens
// EnvClusterRetryTimeoutDuration is the env variable that holds cluster retry duration when sync error happens
EnvClusterSyncRetryTimeoutDuration = "ARGOCD_CLUSTER_SYNC_RETRY_TIMEOUT_DURATION"
// EnvClusterCacheListPageSize is the env variable to control size of the list page size when making K8s queries
@@ -56,7 +56,7 @@ const (
// k8s list queries results across all clusters to avoid memory spikes during cache initialization.
EnvClusterCacheListSemaphore = "ARGOCD_CLUSTER_CACHE_LIST_SEMAPHORE"
// EnvClusterCacheAttemptLimit is the env variable to control the retry limit for listing resources during cluster cache sync
// EnvClusterCacheRetryLimit is the env variable to control the retry limit for listing resources during cluster cache sync
EnvClusterCacheAttemptLimit = "ARGOCD_CLUSTER_CACHE_ATTEMPT_LIMIT"
// EnvClusterCacheRetryUseBackoff is the env variable to control whether to use a backoff strategy with the retry during cluster cache sync
@@ -488,10 +488,11 @@ func (c *liveStateCache) getSyncedCluster(server string) (clustercache.ClusterCa
func (c *liveStateCache) invalidate(cacheSettings cacheSettings) {
log.Info("invalidating live state cache")
c.lock.Lock()
defer c.lock.Unlock()
c.cacheSettings = cacheSettings
for _, clust := range c.clusters {
clusters := c.clusters
c.lock.Unlock()
for _, clust := range clusters {
clust.Invalidate(clustercache.SetSettings(cacheSettings.clusterSettings))
}
log.Info("live state cache invalidated")
@@ -701,12 +702,14 @@ func (c *liveStateCache) handleModEvent(oldCluster *appv1.Cluster, newCluster *a
}
func (c *liveStateCache) handleDeleteEvent(clusterServer string) {
c.lock.Lock()
defer c.lock.Unlock()
c.lock.RLock()
cluster, ok := c.clusters[clusterServer]
c.lock.RUnlock()
if ok {
cluster.Invalidate()
c.lock.Lock()
delete(c.clusters, clusterServer)
c.lock.Unlock()
}
}

View File

@@ -1,13 +1,16 @@
package cache
import (
"context"
"errors"
"net"
"net/url"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -15,8 +18,10 @@ import (
"github.com/argoproj/gitops-engine/pkg/cache"
"github.com/argoproj/gitops-engine/pkg/cache/mocks"
"github.com/stretchr/testify/mock"
"k8s.io/client-go/kubernetes/fake"
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
argosettings "github.com/argoproj/argo-cd/v2/util/settings"
)
type netError string
@@ -107,6 +112,98 @@ func TestHandleAddEvent_ClusterExcluded(t *testing.T) {
assert.Len(t, clustersCache.clusters, 0)
}
func TestHandleDeleteEvent_CacheDeadlock(t *testing.T) {
testCluster := &appv1.Cluster{
Server: "https://mycluster",
Config: appv1.ClusterConfig{Username: "bar"},
}
fakeClient := fake.NewSimpleClientset()
settingsMgr := argosettings.NewSettingsManager(context.TODO(), fakeClient, "argocd")
externalLockRef := sync.RWMutex{}
gitopsEngineClusterCache := &mocks.ClusterCache{}
clustersCache := liveStateCache{
clusters: map[string]cache.ClusterCache{
testCluster.Server: gitopsEngineClusterCache,
},
clusterFilter: func(cluster *appv1.Cluster) bool {
return true
},
settingsMgr: settingsMgr,
// Set the lock here so we can reference it later
// nolint We need to overwrite here to have access to the lock
lock: externalLockRef,
}
channel := make(chan string)
// Mocked lock held by the gitops-engine cluster cache
mockMutex := sync.RWMutex{}
// Locks to force trigger condition during test
// Condition order:
// EnsuredSynced -> Locks gitops-engine
// handleDeleteEvent -> Locks liveStateCache
// EnsureSynced via sync, newResource, populateResourceInfoHandler -> attempts to Lock liveStateCache
// handleDeleteEvent via cluster.Invalidate -> attempts to Lock gitops-engine
handleDeleteWasCalled := sync.Mutex{}
engineHoldsLock := sync.Mutex{}
handleDeleteWasCalled.Lock()
engineHoldsLock.Lock()
gitopsEngineClusterCache.On("EnsureSynced").Run(func(args mock.Arguments) {
// Held by EnsureSync calling into sync and watchEvents
mockMutex.Lock()
defer mockMutex.Unlock()
// Continue Execution of timer func
engineHoldsLock.Unlock()
// Wait for handleDeleteEvent to be called triggering the lock
// on the liveStateCache
handleDeleteWasCalled.Lock()
t.Logf("handleDelete was called, EnsureSynced continuing...")
handleDeleteWasCalled.Unlock()
// Try and obtain the lock on the liveStateCache
alreadyFailed := !externalLockRef.TryLock()
if alreadyFailed {
channel <- "DEADLOCKED -- EnsureSynced could not obtain lock on liveStateCache"
return
}
externalLockRef.Lock()
t.Logf("EnsureSynce was able to lock liveStateCache")
externalLockRef.Unlock()
}).Return(nil).Once()
gitopsEngineClusterCache.On("Invalidate").Run(func(args mock.Arguments) {
// If deadlock is fixed should be able to acquire lock here
alreadyFailed := !mockMutex.TryLock()
if alreadyFailed {
channel <- "DEADLOCKED -- Invalidate could not obtain lock on gitops-engine"
return
}
mockMutex.Lock()
t.Logf("Invalidate was able to lock gitops-engine cache")
mockMutex.Unlock()
}).Return()
go func() {
// Start the gitops-engine lock holds
go func() {
err := gitopsEngineClusterCache.EnsureSynced()
if err != nil {
assert.Fail(t, err.Error())
}
}()
// Wait for EnsureSynced to grab the lock for gitops-engine
engineHoldsLock.Lock()
t.Log("EnsureSynced has obtained lock on gitops-engine")
engineHoldsLock.Unlock()
// Run in background
go clustersCache.handleDeleteEvent(testCluster.Server)
// Allow execution to continue on clusters cache call to trigger lock
handleDeleteWasCalled.Unlock()
channel <- "PASSED"
}()
select {
case str := <-channel:
assert.Equal(t, "PASSED", str, str)
case <-time.After(5 * time.Second):
assert.Fail(t, "Ended up in deadlock")
}
}
func TestIsRetryableError(t *testing.T) {
var (
tlsHandshakeTimeoutErr net.Error = netError("net/http: TLS handshake timeout")

View File

@@ -107,7 +107,7 @@ type appStateManager struct {
persistResourceHealth bool
}
func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, sources []v1alpha1.ApplicationSource, appLabelKey string, revisions []string, noCache, noRevisionCache, verifySignature bool, proj *v1alpha1.AppProject) ([]*unstructured.Unstructured, map[*v1alpha1.ApplicationSource]*apiclient.ManifestResponse, error) {
func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, sources []v1alpha1.ApplicationSource, appLabelKey string, revisions []string, noCache, noRevisionCache, verifySignature bool, proj *v1alpha1.AppProject) ([]*unstructured.Unstructured, []*apiclient.ManifestResponse, error) {
ts := stats.NewTimingStats()
helmRepos, err := m.db.ListHelmRepositories(context.Background())
@@ -164,7 +164,7 @@ func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, sources []v1alp
}
defer io.Close(conn)
manifestInfoMap := make(map[*v1alpha1.ApplicationSource]*apiclient.ManifestResponse)
manifestInfos := make([]*apiclient.ManifestResponse, 0)
targetObjs := make([]*unstructured.Unstructured, 0)
// Store the map of all sources having ref field into a map for applications with sources field
@@ -215,20 +215,14 @@ func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, sources []v1alp
return nil, nil, err
}
// GenerateManifest can return empty ManifestResponse without error if app has multiple sources
// and if any of the source does not have path and chart field not specified.
// In that scenario, we continue to the next source
if app.Spec.HasMultipleSources() && len(manifestInfo.Manifests) == 0 {
continue
}
targetObj, err := unmarshalManifests(manifestInfo.Manifests)
if err != nil {
return nil, nil, err
}
targetObjs = append(targetObjs, targetObj...)
manifestInfoMap[&source] = manifestInfo
manifestInfos = append(manifestInfos, manifestInfo)
}
ts.AddCheckpoint("unmarshal_ms")
@@ -238,7 +232,7 @@ func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, sources []v1alp
}
logCtx = logCtx.WithField("time_ms", time.Since(ts.StartTime).Milliseconds())
logCtx.Info("getRepoObjs stats")
return targetObjs, manifestInfoMap, nil
return targetObjs, manifestInfos, nil
}
func unmarshalManifests(manifests []string) ([]*unstructured.Unstructured, error) {
@@ -399,7 +393,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
var targetObjs []*unstructured.Unstructured
now := metav1.Now()
var manifestInfoMap map[*v1alpha1.ApplicationSource]*apiclient.ManifestResponse
var manifestInfos []*apiclient.ManifestResponse
if len(localManifests) == 0 {
// If the length of revisions is not same as the length of sources,
@@ -411,7 +405,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
}
}
targetObjs, manifestInfoMap, err = m.getRepoObjs(app, sources, appLabelKey, revisions, noCache, noRevisionCache, verifySignature, project)
targetObjs, manifestInfos, err = m.getRepoObjs(app, sources, appLabelKey, revisions, noCache, noRevisionCache, verifySignature, project)
if err != nil {
targetObjs = make([]*unstructured.Unstructured, 0)
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now})
@@ -434,9 +428,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
}
}
// empty out manifestInfoMap
for as := range manifestInfoMap {
delete(manifestInfoMap, as)
}
manifestInfos = make([]*apiclient.ManifestResponse, 0)
}
ts.AddCheckpoint("git_ms")
@@ -516,12 +508,12 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
}
manifestRevisions := make([]string, 0)
for _, manifestInfo := range manifestInfoMap {
for _, manifestInfo := range manifestInfos {
manifestRevisions = append(manifestRevisions, manifestInfo.Revision)
}
// restore comparison using cached diff result if previous comparison was performed for the same revision
revisionChanged := len(manifestInfoMap) != len(sources) || !reflect.DeepEqual(app.Status.Sync.Revisions, manifestRevisions)
revisionChanged := len(manifestInfos) != len(sources) || !reflect.DeepEqual(app.Status.Sync.Revisions, manifestRevisions)
specChanged := !reflect.DeepEqual(app.Status.Sync.ComparedTo, appv1.ComparedTo{Source: app.Spec.GetSource(), Destination: app.Spec.Destination, Sources: sources})
_, refreshRequested := app.IsRefreshRequested()
@@ -688,7 +680,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
// Git has already performed the signature verification via its GPG interface, and the result is available
// in the manifest info received from the repository server. We now need to form our opinion about the result
// and stop processing if we do not agree about the outcome.
for _, manifestInfo := range manifestInfoMap {
for _, manifestInfo := range manifestInfos {
if gpg.IsGPGEnabled() && verifySignature && manifestInfo != nil {
conditions = append(conditions, verifyGnuPGSignature(manifestInfo.Revision, project, manifestInfo)...)
}
@@ -705,11 +697,11 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
}
if hasMultipleSources {
for _, manifestInfo := range manifestInfoMap {
for _, manifestInfo := range manifestInfos {
compRes.appSourceTypes = append(compRes.appSourceTypes, appv1.ApplicationSourceType(manifestInfo.SourceType))
}
} else {
for _, manifestInfo := range manifestInfoMap {
for _, manifestInfo := range manifestInfos {
compRes.appSourceType = v1alpha1.ApplicationSourceType(manifestInfo.SourceType)
break
}

View File

@@ -233,6 +233,74 @@ func TestCompareAppStateExtraHook(t *testing.T) {
assert.Equal(t, 0, len(app.Status.Conditions))
}
// TestAppRevisions tests that revisions are properly propagated for a single source app
func TestAppRevisionsSingleSource(t *testing.T) {
obj1 := NewPod()
obj1.SetNamespace(test.FakeDestNamespace)
data := fakeData{
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{toJSON(t, obj1)},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
}
ctrl := newFakeController(&data)
app := newFakeApp()
revisions := make([]string, 0)
revisions = append(revisions, "")
compRes := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, app.Spec.GetSources(), false, false, nil, app.Spec.HasMultipleSources())
assert.NotNil(t, compRes)
assert.NotNil(t, compRes.syncStatus)
assert.NotEmpty(t, compRes.syncStatus.Revision)
assert.Len(t, compRes.syncStatus.Revisions, 0)
}
// TestAppRevisions tests that revisions are properly propagated for a multi source app
func TestAppRevisionsMultiSource(t *testing.T) {
obj1 := NewPod()
obj1.SetNamespace(test.FakeDestNamespace)
data := fakeData{
manifestResponses: []*apiclient.ManifestResponse{
{
Manifests: []string{toJSON(t, obj1)},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
{
Manifests: []string{toJSON(t, obj1)},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "def456",
},
{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "ghi789",
},
},
managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
}
ctrl := newFakeController(&data)
app := newFakeMultiSourceApp()
revisions := make([]string, 0)
revisions = append(revisions, "")
compRes := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, app.Spec.GetSources(), false, false, nil, app.Spec.HasMultipleSources())
assert.NotNil(t, compRes)
assert.NotNil(t, compRes.syncStatus)
assert.Empty(t, compRes.syncStatus.Revision)
assert.Len(t, compRes.syncStatus.Revisions, 3)
assert.Equal(t, "abc123", compRes.syncStatus.Revisions[0])
assert.Equal(t, "def456", compRes.syncStatus.Revisions[1])
assert.Equal(t, "ghi789", compRes.syncStatus.Revisions[2])
}
func toJSON(t *testing.T, obj *unstructured.Unstructured) string {
data, err := json.Marshal(obj)
assert.NoError(t, err)

View File

@@ -2,7 +2,6 @@ package controller
import (
"context"
"encoding/json"
"fmt"
"os"
"strconv"
@@ -10,6 +9,7 @@ import (
"time"
cdcommon "github.com/argoproj/argo-cd/v2/common"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"github.com/argoproj/gitops-engine/pkg/sync"
"github.com/argoproj/gitops-engine/pkg/sync/common"
@@ -20,6 +20,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/managedfields"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/kubectl/pkg/util/openapi"
"github.com/argoproj/argo-cd/v2/controller/metrics"
@@ -366,11 +367,10 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
}
}
// normalizeTargetResources will apply the diff normalization in all live and target resources.
// Then it calculates the merge patch between the normalized live and the current live resources.
// Finally it applies the merge patch in the normalized target resources. This is done to ensure
// that target resources have the same ignored diff fields values from live ones to avoid them to
// be applied in the cluster. Returns the list of normalized target resources.
// normalizeTargetResources modifies target resources to ensure ignored fields are not touched during synchronization:
// - applies normalization to the target resources based on the live resources
// - copies ignored fields from the matching live resources: apply normalizer to the live resource,
// calculates the patch performed by normalizer and applies the patch to the target resource
func normalizeTargetResources(cr *comparisonResult) ([]*unstructured.Unstructured, error) {
// normalize live and target resources
normalized, err := diff.Normalize(cr.reconciliationResult.Live, cr.reconciliationResult.Target, cr.diffConfig)
@@ -389,94 +389,35 @@ func normalizeTargetResources(cr *comparisonResult) ([]*unstructured.Unstructure
patchedTargets = append(patchedTargets, originalTarget)
continue
}
// calculate targetPatch between normalized and target resource
targetPatch, err := getMergePatch(normalizedTarget, originalTarget)
var lookupPatchMeta *strategicpatch.PatchMetaFromStruct
versionedObject, err := scheme.Scheme.New(normalizedTarget.GroupVersionKind())
if err == nil {
meta, err := strategicpatch.NewPatchMetaFromStruct(versionedObject)
if err != nil {
return nil, err
}
lookupPatchMeta = &meta
}
livePatch, err := getMergePatch(normalized.Lives[idx], live, lookupPatchMeta)
if err != nil {
return nil, err
}
// check if there is a patch to apply. An empty patch is identified by a '{}' string.
if len(targetPatch) > 2 {
livePatch, err := getMergePatch(normalized.Lives[idx], live)
if err != nil {
return nil, err
}
// generate a minimal patch that uses the fields from targetPatch (template)
// with livePatch values
patch, err := compilePatch(targetPatch, livePatch)
if err != nil {
return nil, err
}
normalizedTarget, err = applyMergePatch(normalizedTarget, patch)
if err != nil {
return nil, err
}
} else {
// if there is no patch just use the original target
normalizedTarget = originalTarget
normalizedTarget, err = applyMergePatch(normalizedTarget, livePatch, versionedObject)
if err != nil {
return nil, err
}
patchedTargets = append(patchedTargets, normalizedTarget)
}
return patchedTargets, nil
}
// compilePatch will generate a patch using the fields from templatePatch with
// the values from valuePatch.
func compilePatch(templatePatch, valuePatch []byte) ([]byte, error) {
templateMap := make(map[string]interface{})
err := json.Unmarshal(templatePatch, &templateMap)
if err != nil {
return nil, err
}
valueMap := make(map[string]interface{})
err = json.Unmarshal(valuePatch, &valueMap)
if err != nil {
return nil, err
}
resultMap := intersectMap(templateMap, valueMap)
return json.Marshal(resultMap)
}
// intersectMap will return map with the fields intersection from the 2 provided
// maps populated with the valueMap values.
func intersectMap(templateMap, valueMap map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{})
for k, v := range templateMap {
if innerTMap, ok := v.(map[string]interface{}); ok {
if innerVMap, ok := valueMap[k].(map[string]interface{}); ok {
result[k] = intersectMap(innerTMap, innerVMap)
}
} else if innerTSlice, ok := v.([]interface{}); ok {
if innerVSlice, ok := valueMap[k].([]interface{}); ok {
items := []interface{}{}
for idx, innerTSliceValue := range innerTSlice {
if idx < len(innerVSlice) {
if tSliceValueMap, ok := innerTSliceValue.(map[string]interface{}); ok {
if vSliceValueMap, ok := innerVSlice[idx].(map[string]interface{}); ok {
item := intersectMap(tSliceValueMap, vSliceValueMap)
items = append(items, item)
}
} else {
items = append(items, innerVSlice[idx])
}
}
}
if len(items) > 0 {
result[k] = items
}
}
} else {
if _, ok := valueMap[k]; ok {
result[k] = valueMap[k]
}
}
}
return result
}
// getMergePatch calculates and returns the patch between the original and the
// modified unstructures.
func getMergePatch(original, modified *unstructured.Unstructured) ([]byte, error) {
func getMergePatch(original, modified *unstructured.Unstructured, lookupPatchMeta *strategicpatch.PatchMetaFromStruct) ([]byte, error) {
originalJSON, err := original.MarshalJSON()
if err != nil {
return nil, err
@@ -485,20 +426,30 @@ func getMergePatch(original, modified *unstructured.Unstructured) ([]byte, error
if err != nil {
return nil, err
}
if lookupPatchMeta != nil {
return strategicpatch.CreateThreeWayMergePatch(modifiedJSON, modifiedJSON, originalJSON, lookupPatchMeta, true)
}
return jsonpatch.CreateMergePatch(originalJSON, modifiedJSON)
}
// applyMergePatch will apply the given patch in the obj and return the patched
// unstructure.
func applyMergePatch(obj *unstructured.Unstructured, patch []byte) (*unstructured.Unstructured, error) {
func applyMergePatch(obj *unstructured.Unstructured, patch []byte, versionedObject interface{}) (*unstructured.Unstructured, error) {
originalJSON, err := obj.MarshalJSON()
if err != nil {
return nil, err
}
patchedJSON, err := jsonpatch.MergePatch(originalJSON, patch)
var patchedJSON []byte
if versionedObject == nil {
patchedJSON, err = jsonpatch.MergePatch(originalJSON, patch)
} else {
patchedJSON, err = strategicpatch.StrategicMergePatch(originalJSON, patch, versionedObject)
}
if err != nil {
return nil, err
}
patchedObj := &unstructured.Unstructured{}
_, _, err = unstructured.UnstructuredJSONScheme.Decode(patchedJSON, nil, patchedObj)
if err != nil {

View File

@@ -348,3 +348,207 @@ func TestNormalizeTargetResources(t *testing.T) {
assert.Equal(t, 2, len(containers))
})
}
func TestNormalizeTargetResourcesWithList(t *testing.T) {
type fixture struct {
comparisonResult *comparisonResult
}
setupHttpProxy := func(t *testing.T, ignores []v1alpha1.ResourceIgnoreDifferences) *fixture {
t.Helper()
dc, err := diff.NewDiffConfigBuilder().
WithDiffSettings(ignores, nil, true).
WithNoCache().
Build()
require.NoError(t, err)
live := test.YamlToUnstructured(testdata.LiveHTTPProxy)
target := test.YamlToUnstructured(testdata.TargetHTTPProxy)
return &fixture{
&comparisonResult{
reconciliationResult: sync.ReconciliationResult{
Live: []*unstructured.Unstructured{live},
Target: []*unstructured.Unstructured{target},
},
diffConfig: dc,
},
}
}
t.Run("will properly ignore nested fields within arrays", func(t *testing.T) {
// given
ignores := []v1alpha1.ResourceIgnoreDifferences{
{
Group: "projectcontour.io",
Kind: "HTTPProxy",
JQPathExpressions: []string{".spec.routes[]"},
//JSONPointers: []string{"/spec/routes"},
},
}
f := setupHttpProxy(t, ignores)
target := test.YamlToUnstructured(testdata.TargetHTTPProxy)
f.comparisonResult.reconciliationResult.Target = []*unstructured.Unstructured{target}
// when
patchedTargets, err := normalizeTargetResources(f.comparisonResult)
// then
require.NoError(t, err)
require.Equal(t, 1, len(f.comparisonResult.reconciliationResult.Live))
require.Equal(t, 1, len(f.comparisonResult.reconciliationResult.Target))
require.Equal(t, 1, len(patchedTargets))
// live should have 1 entry
require.Len(t, dig(f.comparisonResult.reconciliationResult.Live[0].Object, []interface{}{"spec", "routes", 0, "rateLimitPolicy", "global", "descriptors"}), 1)
// assert some arbitrary field to show `entries[0]` is not an empty object
require.Equal(t, "sample-header", dig(f.comparisonResult.reconciliationResult.Live[0].Object, []interface{}{"spec", "routes", 0, "rateLimitPolicy", "global", "descriptors", 0, "entries", 0, "requestHeader", "headerName"}))
// target has 2 entries
require.Len(t, dig(f.comparisonResult.reconciliationResult.Target[0].Object, []interface{}{"spec", "routes", 0, "rateLimitPolicy", "global", "descriptors", 0, "entries"}), 2)
// assert some arbitrary field to show `entries[0]` is not an empty object
require.Equal(t, "sample-header", dig(f.comparisonResult.reconciliationResult.Target[0].Object, []interface{}{"spec", "routes", 0, "rateLimitPolicy", "global", "descriptors", 0, "entries", 0, "requestHeaderValueMatch", "headers", 0, "name"}))
// It should be *1* entries in the array
require.Len(t, dig(patchedTargets[0].Object, []interface{}{"spec", "routes", 0, "rateLimitPolicy", "global", "descriptors"}), 1)
// and it should NOT equal an empty object
require.Len(t, dig(patchedTargets[0].Object, []interface{}{"spec", "routes", 0, "rateLimitPolicy", "global", "descriptors", 0, "entries", 0}), 1)
})
t.Run("will correctly set array entries if new entries have been added", func(t *testing.T) {
// given
ignores := []v1alpha1.ResourceIgnoreDifferences{
{
Group: "apps",
Kind: "Deployment",
JQPathExpressions: []string{".spec.template.spec.containers[].env[] | select(.name == \"SOME_ENV_VAR\")"},
},
}
f := setupHttpProxy(t, ignores)
live := test.YamlToUnstructured(testdata.LiveDeploymentEnvVarsYaml)
target := test.YamlToUnstructured(testdata.TargetDeploymentEnvVarsYaml)
f.comparisonResult.reconciliationResult.Live = []*unstructured.Unstructured{live}
f.comparisonResult.reconciliationResult.Target = []*unstructured.Unstructured{target}
// when
targets, err := normalizeTargetResources(f.comparisonResult)
// then
require.NoError(t, err)
require.Equal(t, 1, len(targets))
containers, ok, err := unstructured.NestedSlice(targets[0].Object, "spec", "template", "spec", "containers")
require.NoError(t, err)
require.True(t, ok)
assert.Equal(t, 1, len(containers))
ports := containers[0].(map[string]interface{})["ports"].([]interface{})
assert.Equal(t, 1, len(ports))
env := containers[0].(map[string]interface{})["env"].([]interface{})
assert.Equal(t, 3, len(env))
first := env[0]
second := env[1]
third := env[2]
// Currently the defined order at this time is the insertion order of the target manifest.
assert.Equal(t, "SOME_ENV_VAR", first.(map[string]interface{})["name"])
assert.Equal(t, "some_value", first.(map[string]interface{})["value"])
assert.Equal(t, "SOME_OTHER_ENV_VAR", second.(map[string]interface{})["name"])
assert.Equal(t, "some_other_value", second.(map[string]interface{})["value"])
assert.Equal(t, "YET_ANOTHER_ENV_VAR", third.(map[string]interface{})["name"])
assert.Equal(t, "yet_another_value", third.(map[string]interface{})["value"])
})
t.Run("ignore-deployment-image-replicas-changes-additive", func(t *testing.T) {
// given
ignores := []v1alpha1.ResourceIgnoreDifferences{
{
Group: "apps",
Kind: "Deployment",
JSONPointers: []string{"/spec/replicas"},
}, {
Group: "apps",
Kind: "Deployment",
JQPathExpressions: []string{".spec.template.spec.containers[].image"},
},
}
f := setupHttpProxy(t, ignores)
live := test.YamlToUnstructured(testdata.MinimalImageReplicaDeploymentYaml)
target := test.YamlToUnstructured(testdata.AdditionalImageReplicaDeploymentYaml)
f.comparisonResult.reconciliationResult.Live = []*unstructured.Unstructured{live}
f.comparisonResult.reconciliationResult.Target = []*unstructured.Unstructured{target}
// when
targets, err := normalizeTargetResources(f.comparisonResult)
// then
require.NoError(t, err)
require.Equal(t, 1, len(targets))
metadata, ok, err := unstructured.NestedMap(targets[0].Object, "metadata")
require.NoError(t, err)
require.True(t, ok)
labels, ok := metadata["labels"].(map[string]interface{})
require.True(t, ok)
assert.Equal(t, 2, len(labels))
assert.Equal(t, "web", labels["appProcess"])
spec, ok, err := unstructured.NestedMap(targets[0].Object, "spec")
require.NoError(t, err)
require.True(t, ok)
assert.Equal(t, int64(1), spec["replicas"])
template, ok := spec["template"].(map[string]interface{})
require.True(t, ok)
tMetadata, ok := template["metadata"].(map[string]interface{})
require.True(t, ok)
tLabels, ok := tMetadata["labels"].(map[string]interface{})
require.True(t, ok)
assert.Equal(t, 2, len(tLabels))
assert.Equal(t, "web", tLabels["appProcess"])
tSpec, ok := template["spec"].(map[string]interface{})
require.True(t, ok)
containers, ok, err := unstructured.NestedSlice(tSpec, "containers")
require.NoError(t, err)
require.True(t, ok)
assert.Equal(t, 1, len(containers))
first := containers[0].(map[string]interface{})
assert.Equal(t, "alpine:3", first["image"])
resources, ok := first["resources"].(map[string]interface{})
require.True(t, ok)
requests, ok := resources["requests"].(map[string]interface{})
require.True(t, ok)
assert.Equal(t, "400m", requests["cpu"])
env, ok, err := unstructured.NestedSlice(first, "env")
require.NoError(t, err)
require.True(t, ok)
assert.Equal(t, 1, len(env))
env0 := env[0].(map[string]interface{})
assert.Equal(t, "EV", env0["name"])
assert.Equal(t, "here", env0["value"])
})
}
func dig(obj interface{}, path []interface{}) interface{} {
i := obj
for _, segment := range path {
switch segment.(type) {
case int:
i = i.([]interface{})[segment.(int)]
case string:
i = i.(map[string]interface{})[segment.(string)]
default:
panic("invalid path for object")
}
}
return i
}

View File

@@ -0,0 +1,28 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: client
appProcess: web
name: client
spec:
replicas: 2
selector:
matchLabels:
app: client
strategy: {}
template:
metadata:
labels:
app: client
appProcess: web
spec:
containers:
- image: alpine:2
name: alpine
resources:
requests:
cpu: 400m
env:
- name: EV
value: here

View File

@@ -11,4 +11,22 @@ var (
//go:embed target-deployment-new-entries.yaml
TargetDeploymentNewEntries string
//go:embed live-httpproxy.yaml
LiveHTTPProxy string
//go:embed target-httpproxy.yaml
TargetHTTPProxy string
//go:embed live-deployment-env-vars.yaml
LiveDeploymentEnvVarsYaml string
//go:embed target-deployment-env-vars.yaml
TargetDeploymentEnvVarsYaml string
//go:embed minimal-image-replicas-deployment.yaml
MinimalImageReplicaDeploymentYaml string
//go:embed additional-image-replicas-deployment.yaml
AdditionalImageReplicaDeploymentYaml string
)

View File

@@ -0,0 +1,177 @@
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
argocd.argoproj.io/tracking-id: 'guestbook:apps/Deployment:default/kustomize-guestbook-ui'
deployment.kubernetes.io/revision: '9'
iksm-version: '2.0'
kubectl.kubernetes.io/last-applied-configuration: >
{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"argocd.argoproj.io/tracking-id":"guestbook:apps/Deployment:default/kustomize-guestbook-ui","iksm-version":"2.0"},"name":"kustomize-guestbook-ui","namespace":"default"},"spec":{"replicas":4,"revisionHistoryLimit":3,"selector":{"matchLabels":{"app":"guestbook-ui"}},"template":{"metadata":{"labels":{"app":"guestbook-ui"}},"spec":{"containers":[{"env":[{"name":"SOME_ENV_VAR","value":"some_value"}],"image":"gcr.io/heptio-images/ks-guestbook-demo:0.1","name":"guestbook-ui","ports":[{"containerPort":80}],"resources":{"requests":{"cpu":"50m","memory":"100Mi"}}}]}}}}
creationTimestamp: '2022-01-05T15:45:21Z'
generation: 119
managedFields:
- apiVersion: apps/v1
fieldsType: FieldsV1
fieldsV1:
'f:metadata':
'f:annotations':
'f:iksm-version': {}
manager: janitor
operation: Apply
time: '2022-01-06T18:21:04Z'
- apiVersion: apps/v1
fieldsType: FieldsV1
fieldsV1:
'f:metadata':
'f:annotations':
.: {}
'f:argocd.argoproj.io/tracking-id': {}
'f:kubectl.kubernetes.io/last-applied-configuration': {}
'f:spec':
'f:progressDeadlineSeconds': {}
'f:replicas': {}
'f:revisionHistoryLimit': {}
'f:selector': {}
'f:strategy':
'f:rollingUpdate':
.: {}
'f:maxSurge': {}
'f:maxUnavailable': {}
'f:type': {}
'f:template':
'f:metadata':
'f:labels':
.: {}
'f:app': {}
'f:spec':
'f:containers':
'k:{"name":"guestbook-ui"}':
.: {}
'f:env':
.: {}
'k:{"name":"SOME_ENV_VAR"}':
.: {}
'f:name': {}
'f:value': {}
'f:image': {}
'f:imagePullPolicy': {}
'f:name': {}
'f:ports':
.: {}
'k:{"containerPort":80,"protocol":"TCP"}':
.: {}
'f:containerPort': {}
'f:protocol': {}
'f:resources':
.: {}
'f:requests':
.: {}
'f:cpu': {}
'f:memory': {}
'f:terminationMessagePath': {}
'f:terminationMessagePolicy': {}
'f:dnsPolicy': {}
'f:restartPolicy': {}
'f:schedulerName': {}
'f:securityContext': {}
'f:terminationGracePeriodSeconds': {}
manager: argocd
operation: Update
time: '2022-01-06T15:04:15Z'
- apiVersion: apps/v1
fieldsType: FieldsV1
fieldsV1:
'f:metadata':
'f:annotations':
'f:deployment.kubernetes.io/revision': {}
'f:status':
'f:availableReplicas': {}
'f:conditions':
.: {}
'k:{"type":"Available"}':
.: {}
'f:lastTransitionTime': {}
'f:lastUpdateTime': {}
'f:message': {}
'f:reason': {}
'f:status': {}
'f:type': {}
'k:{"type":"Progressing"}':
.: {}
'f:lastTransitionTime': {}
'f:lastUpdateTime': {}
'f:message': {}
'f:reason': {}
'f:status': {}
'f:type': {}
'f:observedGeneration': {}
'f:readyReplicas': {}
'f:replicas': {}
'f:updatedReplicas': {}
manager: kube-controller-manager
operation: Update
time: '2022-01-06T18:15:14Z'
name: kustomize-guestbook-ui
namespace: default
resourceVersion: '8289211'
uid: ef253575-ce44-4c5e-84ad-16e81d0df6eb
spec:
progressDeadlineSeconds: 600
replicas: 4
revisionHistoryLimit: 3
selector:
matchLabels:
app: guestbook-ui
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: guestbook-ui
spec:
containers:
- env:
- name: SOME_ENV_VAR
value: some_value
image: 'gcr.io/heptio-images/ks-guestbook-demo:0.1'
imagePullPolicy: IfNotPresent
name: guestbook-ui
ports:
- containerPort: 80
protocol: TCP
resources:
requests:
cpu: 50m
memory: 100Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
status:
availableReplicas: 4
conditions:
- lastTransitionTime: '2022-01-05T22:20:37Z'
lastUpdateTime: '2022-01-05T22:43:47Z'
message: >-
ReplicaSet "kustomize-guestbook-ui-6549d54677" has successfully
progressed.
reason: NewReplicaSetAvailable
status: 'True'
type: Progressing
- lastTransitionTime: '2022-01-06T18:15:14Z'
lastUpdateTime: '2022-01-06T18:15:14Z'
message: Deployment has minimum availability.
reason: MinimumReplicasAvailable
status: 'True'
type: Available
observedGeneration: 119
readyReplicas: 4
replicas: 4
updatedReplicas: 4

14
controller/testdata/live-httpproxy.yaml vendored Normal file
View File

@@ -0,0 +1,14 @@
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: my-http-proxy
namespace: default
spec:
routes:
- rateLimitPolicy:
global:
descriptors:
- entries:
- requestHeader:
descriptorKey: sample-key
headerName: sample-header

View File

@@ -0,0 +1,21 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: client
name: client
spec:
replicas: 1
selector:
matchLabels:
app: client
strategy: {}
template:
metadata:
labels:
app: client
spec:
containers:
- image: alpine:3
name: alpine
resources: {}

View File

@@ -0,0 +1,35 @@
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
argocd.argoproj.io/tracking-id: 'guestbook:apps/Deployment:default/kustomize-guestbook-ui'
iksm-version: '1.0'
name: kustomize-guestbook-ui
namespace: default
spec:
replicas: 1
revisionHistoryLimit: 3
selector:
matchLabels:
app: guestbook-ui
template:
metadata:
labels:
app: guestbook-ui
spec:
containers:
- env:
- name: SOME_OTHER_ENV_VAR
value: some_other_value
- name: YET_ANOTHER_ENV_VAR
value: yet_another_value
- name: SOME_ENV_VAR
value: different_value!
image: 'gcr.io/heptio-images/ks-guestbook-demo:0.1'
name: guestbook-ui
ports:
- containerPort: 80
resources:
requests:
cpu: 50m
memory: 100Mi

View File

@@ -0,0 +1,23 @@
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: my-http-proxy
namespace: default
spec:
routes:
- rateLimitPolicy:
global:
descriptors:
- entries:
- requestHeaderValueMatch:
headers:
- contains: sample-key
name: sample-header
value: third
- requestHeader:
descriptorKey: sample-key
headerName: sample-header
- entries:
- requestHeader:
descriptorKey: sample-key
headerName: sample-header

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

View File

@@ -1,118 +0,0 @@
# Component Architecture
Argo CD is designed with a component based architecture. The goal is
to separate the responsibility in different deployable units in order
to have the following benefits:
- **Modularity**: Provides great level of flexibility. Components
interact with each other via an interface. This means that as long
as the interface contract is respected, a given component can be
replaced without requiring the rest of the system to adapt. It is
also possible to run the system without certain components if a
specific group of functionality isn't desired.
- **Single responsibility**: Helps to determine where the different
types of functionality should be implemented which drives for
better system cohesiveness.
- **Reusability**: Clearly defined interfaces helps in functionality
discoverability which benefits reusability of services.
The default Argo CD installation is composed by different components
and different Kubernetes controllers. The controllers aren't
categorized as components as they have proprietary interfaces (CRDs)
and therefore, miss the modular nature. There are more resources
created while installing Argo CD (ConfigMaps, Services, etc), but for
simplicity we are covering just the ones directly related with the
componentized architecture.
## Dependencies
The diagram below has represented all dependencies between the
different components used by the default Argo CD installation:
![Components Diagram](../../assets/argocd-components.png)
There are 4 logical layers represented in the diagram:
- **UI**: This is the presentation layer. Users interact with Argo CD
mainly by components from this layer.
- **Application**: The capabilities required to support the components
from the UI layer.
- **Core**: The main Argo CD gitops functionality is implemented by
components and Kubernetes controllers from the Core layer.
- **Infra**: Represent the tools that Argo CD depends on as part of
its infrastructure.
The logical layers also help making the diagram easier to follow as
dependencies are represented in a top-down relationship. This means
that components from the top layers will be allowed to depend on any
component from any of the bottom layers. However components from the
bottom layers will never depend on any ones from upper layers.
## Responsibility
Below you can refer to a brief description of Argo CD components and
its main responsibilities.
### Webapp
Argo CD ships with a powerful web interface that allows managing
applications deployed in a given Kubernetes cluster.
### CLI
Argo CD provides a CLI that can be used by users to interact with Argo
CD API. The CLI can also be used for automation and scripting.
### API Server
Defines the proprietary API exposed by Argo CD that powers the Webapp
and the CLI functionalities.
### Application Controller
The Application Controller is responsible for reconciling the
Application resource in Kubernetes syncronizing the desired
application state (provided in Git) with the live state (in
Kubernetes). The Application Controller is also responsible for
reconciling the Project resource.
### ApplicationSet Controller
The ApplicationSet Controller is responsible for reconciling the
ApplicationSet resource.
### Repo Server
Repo Server plays an important role in Argo CD architecture as it is
responsible for interacting with the Git repository to generate the
desired state for all Kubernetes resources that belongs to a given
application.
### Redis
Redis is used by Argo CD to provide a cache layer reducing requests
sent to the Kube API as well as to the Git provider. It also supports
a few UI operations.
### Kube API
Argo CD controllers will connect to the Kubernetes API in order to run
the reconciliation loop.
### Git
As a gitops tool Argo CD requires that the desired state of the
Kubernetes resources to be provided in a Git repository.
We use "git" here to stand in for an actual git repo, a Helm repo,
or an OCI artifact repo. Argo CD supports all those options.
### Dex
Argo CD relies on Dex to provide authentication with external OIDC
providers. However other tools can be used instead of Dex. Check the
[user management
documentation](../../operator-manual/user-management/index.md) for
more details.

View File

@@ -9,9 +9,11 @@ These are the upcoming releases dates:
| Release | Release Planning Meeting | Release Candidate 1 | General Availability | Release Champion | Checklist |
|---------|--------------------------|-----------------------|----------------------|-------------------------------------------------------|---------------------------------------------------------------|
| v2.6 | Monday, Dec. 12, 2022 | Monday, Dec. 19, 2022 | Monday, Feb. 6, 2023 | [William Tam](https://github.com/wtam2018) | [checklist](https://github.com/argoproj/argo-cd/issues/11563) |
| v2.7 | Monday, Mar. 6, 2023 | Monday, Mar. 20, 2023 | Monday, May. 1, 2023 | [Pavel Kostohrys](https://github.com/pasha-codefresh) |
| v2.8 | Monday, Jun. 5, 2023 | Monday, Jun. 19, 2023 | Monday, Aug. 7, 2023 | [Keith Chong](https://github.keithchong)
| v2.9 | Monday, Sep. 4, 2023 | Monday, Sep. 18, 2023 | Monday, Nov. 6, 2023 |
| v2.7 | Monday, Mar. 6, 2023 | Monday, Mar. 20, 2023 | Monday, May. 1, 2023 | [Pavel Kostohrys](https://github.com/pasha-codefresh) | [checklist](https://github.com/argoproj/argo-cd/issues/12762) |
| v2.8 | Monday, Jun. 20, 2023 | Monday, Jun. 26, 2023 | Monday, Aug. 7, 2023 | [Keith Chong](https://github.com/keithchong) | [checklist](https://github.com/argoproj/argo-cd/issues/13742) |
| v2.9 | Monday, Sep. 4, 2023 | Monday, Sep. 18, 2023 | Monday, Nov. 6, 2023 | [Leonardo Almeida](https://github.com/leoluz) | [checklist](https://github.com/argoproj/argo-cd/issues/14078) |
| v2.10 | Monday, Dec. 4, 2023 | Monday, Dec. 18, 2023 | Monday, Feb. 5, 2024 |
Actual release dates might differ from the plan by a few days.

View File

@@ -9,12 +9,7 @@ To test:
```bash
make serve-docs
```
Check for broken external links:
```bash
make lint-docs
```
Once running, you can view your locally built documentation at [http://0.0.0.0:8000/](http://0.0.0.0:8000/).
## Deploying

View File

@@ -53,7 +53,7 @@ The following read will help you to submit a PR that meets the standards of our
Please use a meaningful and concise title for your PR. This will help us to pick PRs for review quickly, and the PR title will also end up in the Changelog.
We use [PR title checker](https://github.com/marketplace/actions/pr-title-checker) to categorize your PR into one of the following categories:
We use the [Semantic PR title checker](https://github.com/zeke/semantic-pull-requests) to categorize your PR into one of the following categories:
* `fix` - Your PR contains one or more code bug fixes
* `feat` - Your PR contains a new feature
@@ -157,9 +157,9 @@ Make sure you fulfill the pre-requisites above and run some preliminary tests. N
* Run `docker version`
* Run `go version`
### Build the required Docker image
### Build (or pull) the required Docker image
Build the required Docker image by running `make test-tools-image`. This image offers the environment of the virtualized toolchain.
Build the required Docker image by running `make test-tools-image` or pull the latest version by issuing `docker pull argoproj/argocd-test-tools`.
The `Dockerfile` used to build these images can be found at `test/container/Dockerfile`.

View File

@@ -71,6 +71,8 @@ We supply a `ClusterRole` and `ClusterRoleBinding` suitable for this purpose in
kubectl apply -f examples/k8s-rbac/argocd-server-applications/
```
`argocd-notifications-controller-rbac-clusterrole.yaml` and `argocd-notifications-controller-rbac-clusterrolebinding.yaml` are used to support notifications controller to notify apps in all namespaces.
!!! note
At some later point in time, we may make this cluster role part of the default installation manifests.

View File

@@ -6,7 +6,10 @@ metadata:
namespace: argocd
# Add this finalizer ONLY if you want these to cascade delete.
finalizers:
# The default behaviour is foreground cascading deletion
- resources-finalizer.argocd.argoproj.io
# Alternatively, you can use background cascading deletion
# - resources-finalizer.argocd.argoproj.io/background
# Add labels to your application object.
labels:
name: guestbook
@@ -169,6 +172,7 @@ spec:
- CreateNamespace=true # Namespace Auto-Creation ensures that namespace specified as the application destination exists in the destination cluster.
- PrunePropagationPolicy=foreground # Supported policies are background, foreground and orphan.
- PruneLast=true # Allow the ability for resource pruning to happen as a final, implicit wave of a sync operation
- RespectIgnoreDifferences=true # When syncing changes, respect fields ignored by the ignoreDifferences configuration
managedNamespaceMetadata: # Sets the metadata for the application namespace. Only valid if CreateNamespace=true (see above), otherwise it's a no-op.
labels: # The labels to set on the application namespace
any: label
@@ -187,18 +191,24 @@ spec:
maxDuration: 3m # the maximum amount of time allowed for the backoff strategy
# Will ignore differences between live and desired states during the diff. Note that these configurations are not
# used during the sync process.
# used during the sync process unless the `RespectIgnoreDifferences=true` sync option is enabled.
ignoreDifferences:
# for the specified json pointers
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas
- kind: ConfigMap
jqPathExpressions:
- '.data["config.yaml"].auth'
# for the specified managedFields managers
- group: "*"
kind: "*"
managedFieldsManagers:
- kube-controller-manager
# Name and namespace are optional. If specified, they must match exactly, these are not glob patterns.
name: my-deployment
namespace: my-namespace
# RevisionHistoryLimit limits the number of items kept in the application's revision history, which is used for
# informational purposes as well as for rollbacks to previous versions. This should only be changed in exceptional

View File

@@ -153,7 +153,7 @@ Or, a shorter way (using [path.Match](https://golang.org/pkg/path/#Match) syntax
```yaml
- path: /d/*
- path: /d/[f|g]
- path: /d/[fg]
exclude: true
```

View File

@@ -274,7 +274,6 @@ spec:
* `branch_slug`: The branch name will be cleaned to be conform to the DNS label standard as defined in [RFC 1123](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names), and truncated to 50 characters to give room to append/suffix-ing it with 13 more characters.
* `head_sha`: This is the SHA of the head of the pull request.
* `head_short_sha`: This is the short SHA of the head of the pull request (8 characters long or the length of the head SHA if it's shorter).
* `head_short_sha_7`: This is the short SHA of the head of the pull request (7 characters long or the length of the head SHA if it's shorter).
* `labels`: The array of pull request labels. (Supported only for Go Template ApplicationSet manifests.)
## Webhook Configuration

View File

@@ -319,6 +319,5 @@ spec:
* `branch`: The default branch of the repository.
* `sha`: The Git commit SHA for the branch.
* `short_sha`: The abbreviated Git commit SHA for the branch (8 chars or the length of the `sha` if it's shorter).
* `short_sha_7`: The abbreviated Git commit SHA for the branch (7 chars or the length of the `sha` if it's shorter).
* `labels`: A comma-separated list of repository labels.
* `branchNormalized`: The value of `branch` normalized to contain only lowercase alphanumeric characters, '-' or '.'.

View File

@@ -6,7 +6,7 @@ Generators are primarily based on the data source that they use to generate the
As of this writing there are eight generators:
- [List generator](Generators-List.md): The List generator allows you to target Argo CD Applications to clusters based on a fixed list of cluster name/URL values.
- [List generator](Generators-List.md): The List generator allows you to target Argo CD Applications to clusters based on a fixed list of any chosen key/value element pairs.
- [Cluster generator](Generators-Cluster.md): The Cluster generator allows you to target Argo CD Applications to clusters, based on the list of clusters defined within (and managed by) Argo CD (which includes automatically responding to cluster addition/removal events from Argo CD).
- [Git generator](Generators-Git.md): The Git generator allows you to create Applications based on files within a Git repository, or based on the directory structure of a Git repository.
- [Matrix generator](Generators-Matrix.md): The Matrix generator may be used to combine the generated parameters of two separate generators.

View File

@@ -87,6 +87,10 @@ By activating Go Templating, `{{ .path }}` becomes an object. Therefore, some ch
generators' templating:
- `{{ path }}` becomes `{{ .path.path }}`
- `{{ path.basename }}` becomes `{{ .path.basename }}`
- `{{ path.basenameNormalized }}` becomes `{{ .path.basenameNormalized }}`
- `{{ path.filename }}` becomes `{{ .path.filename }}`
- `{{ path.filenameNormalized }}` becomes `{{ .path.filenameNormalized }}`
- `{{ path[n] }}` becomes `{{ index .path.segments n }}`
Here is an example:

View File

@@ -15,7 +15,7 @@ As an experimental feature, progressive syncs must be explicitly enabled, in one
1. Pass `--enable-progressive-syncs` to the ApplicationSet controller args.
1. Set `ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_PROGRESSIVE_SYNCS=true` in the ApplicationSet controller environment variables.
1. Set `applicationsetcontroller.enable.progressive.syncs: true` in the Argo CD ConfigMap.
1. Set `applicationsetcontroller.enable.progressive.syncs: true` in the Argo CD `argocd-cmd-params-cm` ConfigMap.
## Strategies

View File

@@ -69,6 +69,9 @@ data:
server.rootpath: ""
# Directory path that contains additional static assets
server.staticassets: "/shared/app"
# Semicolon-separated list of content types allowed on non-GET requests. Set an empty string to allow all. Be aware
# that allowing content types besides application/json may make your API more vulnerable to CSRF attacks.
server.api.content.types: "application/json"
# Set the logging format. One of: text|json (default "text")
server.log.format: "text"

View File

@@ -7,12 +7,18 @@ metadata:
name: argocd-ssh-known-hosts-cm
data:
ssh_known_hosts: |
bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAubiN81eDcafrgMeLzaFPsw2kNvEcqTKl/VqLat/MaB33pZy0y3rJZtnqwR2qOOvbwKZYKiEO1O6VqNEBxKvJJelCq0dTXWT5pbO2gDXC6h6QDXCaHo6pOHGPUy+YBaGQRGuSusMEASYiWunYN0vCAI8QaXnWMXNMdFP3jHAJH0eDsoiGnLPBlBp4TNm6rYI74nMzgz3B9IikW4WVK+dc8KZJZWYjAuORU3jc1c/NPskD2ASinf8v3xnfXeukU0sJ5N6m5E8VLjObPEO+mN2t/FZTMZLiFqPWc/ALSqnMnnhwrNi2rbfg/rd/IpL8Le3pSBne8+seeFVBoGqzHM9yXw==
# This file was automatically generated by hack/update-ssh-known-hosts.sh. DO NOT EDIT
[ssh.github.com]:443 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
[ssh.github.com]:443 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
[ssh.github.com]:443 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
bitbucket.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPIQmuzMBuKdWeF4+a2sjSSpBK0iqitSQ+5BM9KhpexuGt20JpTVM7u5BDZngncgrqDMbWdxMWWOGtZ9UgbqgZE=
bitbucket.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIazEu89wgQZ4bqs3d63QSMzYVa0MuJ2e2gKTKqu+UUO
bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDQeJzhupRu0u0cdegZIa8e86EG2qOCsIsD1Xw0xSeiPDlCr7kq97NLmMbpKTX6Esc30NuoqEEHCuc7yWtwp8dI76EEEB1VqY9QJq6vk+aySyboD5QF61I/1WeTwu+deCbgKMGbUijeXhtfbxSxm6JwGrXrhBdofTsbKRUsrN1WoNgUa8uqN1Vx6WAJw1JHPhglEGGHea6QICwJOAr/6mrui/oB7pkaWKHj3z7d1IC4KWLtY47elvjbaTlkN04Kc/5LFEirorGYVbt15kAUlqGM65pk6ZBxtaO3+30LVlORZkxOh+LKL/BvbZ/iRNhItLqNyieoQj/uh/7Iv4uyH/cV/0b4WDSd3DptigWq84lJubb9t/DnZlrJazxyDCulTmKdOR7vs9gMTo+uoIrPSb8ScTtvw65+odKAlBj59dhnVp9zd7QUojOpXlL62Aw56U4oO+FALuevvMjiWeavKhJqlR7i5n9srYcrNV7ttmDw7kf/97P5zauIhxcjX+xHv4M=
github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
gitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=
gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf
gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9
ssh.dev.azure.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H
vs-ssh.visualstudio.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H
github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl

View File

@@ -54,7 +54,7 @@ spec:
command: [sh]
args: [-c, 'echo "Initializing..."']
# The generate command runs in the Application source directory each time manifests are generated. Standard output
# must be ONLY valid YAML manifests. A non-zero exit code will fail manifest generation.
# must be ONLY valid Kubernetes Objects in either YAML or JSON. A non-zero exit code will fail manifest generation.
# Error output will be sent to the UI, so avoid printing sensitive information (such as secrets).
generate:
command: [sh, -c]
@@ -67,8 +67,8 @@ spec:
# Only one of fileName, find.glob, or find.command should be specified. If multiple are specified then only the
# first (in that order) is evaluated.
discover:
# fileName is a glob pattern (https://pkg.go.dev/path/filepath#Glob) that is applied to the repository's root
# directory (not the Application source directory). If there is a match, this plugin may be used for the repository.
# fileName is a glob pattern (https://pkg.go.dev/path/filepath#Glob) that is applied to the Application's source
# directory. If there is a match, this plugin may be used for the Application.
fileName: "./subdir/s*.yaml"
find:
# This does the same thing as fileName, but it supports double-start (nested directory) glob patterns.
@@ -118,7 +118,7 @@ spec:
# static parameter announcements list.
command: [echo, '[{"name": "example-param", "string": "default-string-value"}]']
# If set to then the plugin receives repository files with original file mode. Dangerous since the repository
# If set to `true` then the plugin receives repository files with original file mode. Dangerous since the repository
# might have executable files. Set to true only if you trust the CMP plugin authors.
preserveFileMode: false
```
@@ -127,7 +127,7 @@ spec:
While the ConfigManagementPlugin _looks like_ a Kubernetes object, it is not actually a custom resource.
It only follows kubernetes-style spec conventions.
The `generate` command must print a valid YAML stream to stdout. Both `init` and `generate` commands are executed inside the application source directory.
The `generate` command must print a valid Kubernetes YAML or JSON object stream to stdout. Both `init` and `generate` commands are executed inside the application source directory.
The `discover.fileName` is used as [glob](https://pkg.go.dev/path/filepath#Glob) pattern to determine whether an
application repository is supported by the plugin or not.
@@ -424,7 +424,7 @@ data:
init: # Optional command to initialize application source directory
command: ["sample command"]
args: ["sample args"]
generate: # Command to generate manifests YAML
generate: # Command to generate Kubernetes Objects in either YAML or JSON
command: ["sample command"]
args: ["sample args"]
lockRepo: true # Defaults to false. See below.
@@ -441,7 +441,7 @@ spec:
init: # Optional command to initialize application source directory
command: ["sample command"]
args: ["sample args"]
generate: # Command to generate manifests YAML
generate: # Command to generate Kubernetes Objects in either YAML or JSON
command: ["sample command"]
args: ["sample args"]
```

View File

@@ -1,99 +0,0 @@
# Argo CD Core
## Introduction
Argo CD Core is a different installation that runs Argo CD in headless
mode. With this installation, you will have a fully functional GitOps
engine capable of getting the desired state from Git repositories and
applying it in Kubernetes.
The following groups of features won't be available in this
installation:
- Argo CD RBAC model
- Argo CD API
- OIDC based authentication
The following features will be partially available (see the
[usage](#using) section below for more details):
- Argo CD Web UI
- Argo CD CLI
- Multi-tenancy (strictly GitOps based on git push permissions)
A few use-cases that justify running Argo CD Core are:
- As a cluster admin, I want to rely on Kubernetes RBAC only.
- As a devops engineer, I don't want to learn a new API or depend on
another CLI to automate my deployments. I want instead rely in
Kubernetes API only.
- As a cluster admin, I don't want to provide Argo CD UI or Argo CD
CLI to developers.
## Architecture
Because Argo CD is designed with a component based architecture in
mind, it is possible to have a more minimalist installation. In this
case fewer components are installed and yet the main GitOps
functionality remains operational.
In the diagram below, the Core box, shows the components that will be
installed while opting for Argo CD Core:
![Argo CD Core](../assets/argocd-core-components.png)
Note that even if the Argo CD controller can run without Redis, it
isn't recommended. The Argo CD controller uses Redis as an important
caching mechanism reducing the load on Kube API and in Git. For this
reason, Redis is also included in this installation method.
## Installing
Argo CD Core can be installed by applying a single manifest file that
contains all the required resources.
Example:
```
export ARGOCD_VERSION=<desired argo cd release version (e.g. v2.7.0)>
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/$ARGOCD_VERSION/manifests/core-install.yaml
```
## Using
Once Argo CD Core is installed, users will be able to interact with it
by relying on GitOps. The available Kubernetes resources will be the
`Application` and the `ApplicationSet` CRDs. By using those resources,
users will be able to deploy and manage applications in Kubernetes.
It is still possible to use Argo CD CLI even when running Argo CD
Core. In this case, the CLI will spawn a local API server process that
will be used to handle the CLI command. Once the command is concluded,
the local API Server process will also be terminated. This happens
transparently for the user with no additional command required. Note
that Argo CD Core will rely only on Kubernetes RBAC and the user (or
the process) invoking the CLI needs to have access to the Argo CD
namespace with the proper permission in the `Application` and
`ApplicationSet` resources for executing a given command.
To use Argo CD CLI in core mode, it is required to pass the `--core`
flag with the `login` subcommand.
Example:
```bash
kubectl config set-context --current --namespace=argocd # change current kube context to argocd namespace
argocd login --core
```
Similarly, users can also run the Web UI locally if they prefer to
interact with Argo CD using this method. The Web UI can be started
locally by running the following command:
```
argocd admin dashboard -n argocd
```
Argo CD Web UI will be available at `http://localhost:8080`

View File

@@ -98,7 +98,7 @@ The AppProject CRD is the Kubernetes resource object representing a logical grou
It is defined by the following key pieces of information:
* `sourceRepos` reference to the repositories that applications within the project can pull manifests from.
* `destinations` reference to clusters and namespaces that applications within the project can deploy into (don't use the `name` field, only the `server` field is matched).
* `destinations` reference to clusters and namespaces that applications within the project can deploy into.
* `roles` list of entities with definitions of their access to resources within the project.
!!!warning "Projects which can deploy to the Argo CD namespace grant admin access"
@@ -416,30 +416,51 @@ data:
### SSH known host public keys
If you are connecting repositories via SSH, Argo CD will need to know the SSH known hosts public key of the repository servers. You can manage the SSH known hosts data in the ConfigMap named `argocd-ssh-known-hosts-cm`. This ConfigMap contains a single key/value pair, with `ssh_known_hosts` as the key and the actual public keys of the SSH servers as data. As opposed to TLS configuration, the public key(s) of each single repository server Argo CD will connect via SSH must be configured, otherwise the connections to the repository will fail. There is no fallback. The data can be copied from any existing `ssh_known_hosts` file, or from the output of the `ssh-keyscan` utility. The basic format is `<servername> <keydata>`, one entry per line.
If you are configuring repositories to use SSH, Argo CD will need to know their SSH public keys. In order for Argo CD to connect via SSH the public key(s) for each repository server must be pre-configured in Argo CD (unlike TLS configuration), otherwise the connections to the repository will fail.
An example ConfigMap object:
You can manage the SSH known hosts data in the `argocd-ssh-known-hosts-cm` ConfigMap. This ConfigMap contains a single entry, `ssh_known_hosts`, with the public keys of the SSH servers as its value. The value can be filled in from any existing `ssh_known_hosts` file, or from the output of the `ssh-keyscan` utility (which is part of OpenSSH's client package). The basic format is `<server_name> <keytype> <base64-encoded_key>`, one entry per line.
Here is an example of running `ssh-keyscan`:
```bash
$ for host in bitbucket.org github.com gitlab.com ssh.dev.azure.com vs-ssh.visualstudio.com ; do ssh-keyscan $host 2> /dev/null ; done
bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDQeJzhupRu0u0cdegZIa8e86EG2qOCsIsD1Xw0xSeiPDlCr7kq97NLmMbpKTX6Esc30NuoqEEHCuc7yWtwp8dI76EEEB1VqY9QJq6vk+aySyboD5QF61I/1WeTwu+deCbgKMGbUijeXhtfbxSxm6JwGrXrhBdofTsbKRUsrN1WoNgUa8uqN1Vx6WAJw1JHPhglEGGHea6QICwJOAr/6mrui/oB7pkaWKHj3z7d1IC4KWLtY47elvjbaTlkN04Kc/5LFEirorGYVbt15kAUlqGM65pk6ZBxtaO3+30LVlORZkxOh+LKL/BvbZ/iRNhItLqNyieoQj/uh/7Iv4uyH/cV/0b4WDSd3DptigWq84lJubb9t/DnZlrJazxyDCulTmKdOR7vs9gMTo+uoIrPSb8ScTtvw65+odKAlBj59dhnVp9zd7QUojOpXlL62Aw56U4oO+FALuevvMjiWeavKhJqlR7i5n9srYcrNV7ttmDw7kf/97P5zauIhxcjX+xHv4M=
github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
gitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=
gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf
gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9
ssh.dev.azure.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H
vs-ssh.visualstudio.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H
```
Here is an example `ConfigMap` object using the output from `ssh-keyscan` above:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-ssh-known-hosts-cm
namespace: argocd
labels:
app.kubernetes.io/name: argocd-cm
app.kubernetes.io/name: argocd-ssh-known-hosts-cm
app.kubernetes.io/part-of: argocd
name: argocd-ssh-known-hosts-cm
data:
ssh_known_hosts: |
bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAubiN81eDcafrgMeLzaFPsw2kNvEcqTKl/VqLat/MaB33pZy0y3rJZtnqwR2qOOvbwKZYKiEO1O6VqNEBxKvJJelCq0dTXWT5pbO2gDXC6h6QDXCaHo6pOHGPUy+YBaGQRGuSusMEASYiWunYN0vCAI8QaXnWMXNMdFP3jHAJH0eDsoiGnLPBlBp4TNm6rYI74nMzgz3B9IikW4WVK+dc8KZJZWYjAuORU3jc1c/NPskD2ASinf8v3xnfXeukU0sJ5N6m5E8VLjObPEO+mN2t/FZTMZLiFqPWc/ALSqnMnnhwrNi2rbfg/rd/IpL8Le3pSBne8+seeFVBoGqzHM9yXw==
# This file was automatically generated by hack/update-ssh-known-hosts.sh. DO NOT EDIT
[ssh.github.com]:443 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
[ssh.github.com]:443 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
[ssh.github.com]:443 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
bitbucket.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPIQmuzMBuKdWeF4+a2sjSSpBK0iqitSQ+5BM9KhpexuGt20JpTVM7u5BDZngncgrqDMbWdxMWWOGtZ9UgbqgZE=
bitbucket.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIazEu89wgQZ4bqs3d63QSMzYVa0MuJ2e2gKTKqu+UUO
bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDQeJzhupRu0u0cdegZIa8e86EG2qOCsIsD1Xw0xSeiPDlCr7kq97NLmMbpKTX6Esc30NuoqEEHCuc7yWtwp8dI76EEEB1VqY9QJq6vk+aySyboD5QF61I/1WeTwu+deCbgKMGbUijeXhtfbxSxm6JwGrXrhBdofTsbKRUsrN1WoNgUa8uqN1Vx6WAJw1JHPhglEGGHea6QICwJOAr/6mrui/oB7pkaWKHj3z7d1IC4KWLtY47elvjbaTlkN04Kc/5LFEirorGYVbt15kAUlqGM65pk6ZBxtaO3+30LVlORZkxOh+LKL/BvbZ/iRNhItLqNyieoQj/uh/7Iv4uyH/cV/0b4WDSd3DptigWq84lJubb9t/DnZlrJazxyDCulTmKdOR7vs9gMTo+uoIrPSb8ScTtvw65+odKAlBj59dhnVp9zd7QUojOpXlL62Aw56U4oO+FALuevvMjiWeavKhJqlR7i5n9srYcrNV7ttmDw7kf/97P5zauIhxcjX+xHv4M=
github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
gitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=
gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf
gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9
ssh.dev.azure.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H
vs-ssh.visualstudio.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H
github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
```
!!! note
@@ -581,6 +602,132 @@ stringData:
}
```
EKS cluster secret example using argocd-k8s-auth and [IRSA](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html):
```yaml
apiVersion: v1
kind: Secret
metadata:
name: mycluster-secret
labels:
argocd.argoproj.io/secret-type: cluster
type: Opaque
stringData:
name: "mycluster.com"
server: "https://mycluster.com"
config: |
{
"awsAuthConfig": {
"clusterName": "my-eks-cluster-name",
"roleARN": "arn:aws:iam::<AWS_ACCOUNT_ID>:role/<IAM_ROLE_NAME>"
},
"tlsClientConfig": {
"insecure": false,
"caData": "<base64 encoded certificate>"
}
}
```
Note that you should have IRSA enabled on your EKS cluster, create an appropriate IAM role which allows it to assume
other IAM roles (whichever `roleARN`s that Argo CD needs to assume) and have an assume role policy which allows
the argocd-application-controller and argocd-server pods to assume said role via OIDC.
Example trust relationship config for `<arn:aws:iam::<AWS_ACCOUNT_ID>:role/<ARGO_CD_MANAGEMENT_IAM_ROLE_NAME>`, which
is required for Argo CD to perform actions via IAM. Ensure that the cluster has an [IAM OIDC provider configured](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html)
for it.
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<AWS_ACCOUNT_ID>:oidc-provider/oidc.eks.<AWS_REGION>.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.<AWS_REGION>.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": ["system:serviceaccount:argocd:argocd-application-controller", "system:serviceaccount:argocd:argocd-server"],
"oidc.eks.<AWS_REGION>.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud": "sts.amazonaws.com"
}
}
}
]
}
```
The Argo CD management role also needs to be allowed to assume other roles, in this case we want it to assume
`arn:aws:iam::<AWS_ACCOUNT_ID>:role/<IAM_ROLE_NAME>` so that it can manage the cluster mapped to that role. This can be
extended to allow assumption of multiple roles, either as an explicit array of role ARNs or by using `*` where appropriate.
```json
{
"Version" : "2012-10-17",
"Statement" : {
"Effect" : "Allow",
"Action" : "sts:AssumeRole",
"Principal" : {
"AWS" : "<arn:aws:iam::<AWS_ACCOUNT_ID>:role/<IAM_ROLE_NAME>"
}
}
}
```
Example service account configs for `argocd-application-controller` and `argocd-server`. Note that once the annotations
have been set on the service accounts, both the application controller and server pods need to be restarted.
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
eks.amazonaws.com/role-arn: "<arn:aws:iam::<AWS_ACCOUNT_ID>:role/<ARGO_CD_MANAGEMENT_IAM_ROLE_NAME>"
name: argocd-application-controller
---
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
eks.amazonaws.com/role-arn: "<arn:aws:iam::<AWS_ACCOUNT_ID>:role/<ARGO_CD_MANAGEMENT_IAM_ROLE_NAME>"
name: argocd-server
```
In turn, the `roleARN` of each managed cluster needs to be added to each respective cluster's `aws-auth` config map (see
[Enabling IAM principal access to your cluster](https://docs.aws.amazon.com/eks/latest/userguide/add-user-role.html)), as
well as having an assume role policy which allows it to be assumed by the Argo CD pod role.
Example assume role policy for a cluster which is managed by Argo CD:
```json
{
"Version" : "2012-10-17",
"Statement" : {
"Effect" : "Allow",
"Action" : "sts:AssumeRole",
"Principal" : {
"AWS" : "<arn:aws:iam::<AWS_ACCOUNT_ID>:role/<ARGO_CD_MANAGEMENT_IAM_ROLE_NAME>"
}
}
}
```
Example kube-system/aws-auth configmap for your cluster managed by Argo CD:
```yaml
apiVersion: v1
data:
# Other groups and accounts omitted for brevity. Ensure that no other rolearns and/or groups are inadvertently removed,
# or you risk borking access to your cluster.
#
# The group name is a RoleBinding which you use to map to a [Cluster]Role. See https://kubernetes.io/docs/reference/access-authn-authz/rbac/#role-binding-examples
mapRoles: |
- "groups":
- "<GROUP-NAME-IN-K8S-RBAC>"
"rolearn": "<arn:aws:iam::<AWS_ACCOUNT_ID>:role/<IAM_ROLE_NAME>"
"username": "<some-username>"
```
GKE cluster secret example using argocd-k8s-auth and [Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity):
```yaml

View File

@@ -11,38 +11,42 @@ or individual resources (pods, services, etc.).
## Configuring Deep Links
The configuration for Deep Links is present in `argocd-cm` as `<location>.links` fields where
`<location>` determines where it will be displayed. The possible values for `<location>` are :
- `project` : all links under this field will show up in the project tab in the Argo CD UI
- `application` : all links under this field will show up in the application summary tab
- `resource` : all links under this field will show up in the resource (deployments, pods, services, etc.) summary tab
`<location>` determines where it will be displayed. The possible values for `<location>` are:
Each link in the list has five subfields :
1. `title` : title/tag that will be displayed in the UI corresponding to that link
2. `url` : the actual URL where the deep link will redirect to, this field can be templated to use data from the
corresponding application, project or resource objects (depending on where it is located). This uses [text/template](pkg.go.dev/text/template) pkg for templating
3. `description` (optional) : a description for what the deep link is about
4. `icon.class` (optional) : a font-awesome icon class to be used when displaying the links in dropdown menus
5. `if` (optional) : a conditional statement that results in either `true` or `false`, it also has access to the same
- `project`: all links under this field will show up in the project tab in the Argo CD UI
- `application`: all links under this field will show up in the application summary tab
- `resource`: all links under this field will show up in the resource (deployments, pods, services, etc.) summary tab
Each link in the list has five subfields:
1. `title`: title/tag that will be displayed in the UI corresponding to that link
2. `url`: the actual URL where the deep link will redirect to, this field can be templated to use data from the
corresponding application, project or resource objects (depending on where it is located). This uses [text/template](https://pkg.go.dev/text/template) pkg for templating
3. `description` (optional): a description for what the deep link is about
4. `icon.class` (optional): a font-awesome icon class to be used when displaying the links in dropdown menus
5. `if` (optional): a conditional statement that results in either `true` or `false`, it also has access to the same
data as the `url` field. If the condition resolves to `true` the deep link will be displayed - else it will be hidden. If
the field is omitted, by default the deep links will be displayed. This uses [antonmedv/expr](https://github.com/antonmedv/expr/tree/master/docs) for evaluating conditions
!!!note
For resources of kind Secret the data fields are redacted but other fields are accessible for templating the deep links.
For resources of kind Secret the data fields are redacted but other fields are accessible for templating the deep links.
!!!warning
Make sure to validate the url templates and inputs to prevent data leaks or possible generation of any malicious links.
Make sure to validate the url templates and inputs to prevent data leaks or possible generation of any malicious links.
As mentioned earlier the links and conditions can be templated to use data from the resource, each category of links can access different types of data linked to that resource.
Overall we have these 4 resources available for templating in the system :
- `application` : this key is used to access the application resource data.
- `resource` : this key is used to access values for the actual k8s resource.
- `cluster` : this key is used to access the related destination cluster data like name, server, namespaces etc.
- `project` : this key is used to access the project resource data.
Overall we have these 4 resources available for templating in the system:
The above resources are accessible in particular link categories, here's a list of resources available in each category :
- `resource.links` : `resource`, `application`, `cluster` and `project`
- `application.links` : `application` and `cluster`
- `project.links` : `project`
- `application`: this key is used to access the application resource data.
- `resource`: this key is used to access values for the actual k8s resource.
- `cluster`: this key is used to access the related destination cluster data like name, server, namespaces etc.
- `project`: this key is used to access the project resource data.
The above resources are accessible in particular link categories, here's a list of resources available in each category:
- `resource.links`: `resource`, `application`, `cluster` and `project`
- `application.links`: `application` and `cluster`
- `project.links`: `project`
An example `argocd-cm.yaml` file with deep links and their variations :
@@ -56,7 +60,7 @@ An example `argocd-cm.yaml` file with deep links and their variations :
# sample application level links
application.links: |
# pkg.go.dev/text/template is used for evaluating url templates
- url: https://mycompany.splunk.com?search={{.application.spec.destination.namespace}}&env={{.project.metadata.label.env}}
- url: https://mycompany.splunk.com?search={{.application.spec.destination.namespace}}&env={{.project.metadata.labels.env}}
title: Splunk
# conditionally show link e.g. for specific project
# github.com/antonmedv/expr is used for evaluation of conditions
@@ -65,10 +69,10 @@ An example `argocd-cm.yaml` file with deep links and their variations :
if: application.spec.project == "default"
- url: https://{{.application.metadata.annotations.splunkhost}}?search={{.application.spec.destination.namespace}}
title: Splunk
if: application.metadata.annotations.splunkhost
if: application.metadata.annotations.splunkhost != ""
# sample resource level links
resource.links: |
- url: https://mycompany.splunk.com?search={{.resource.metadata.name}}&env={{.project.metadata.label.env}}
- url: https://mycompany.splunk.com?search={{.resource.metadata.name}}&env={{.project.metadata.labels.env}}
title: Splunk
if: resource.kind == "Pod" || resource.kind == "Deployment"
```

View File

@@ -15,13 +15,13 @@ export VERSION=v1.0.1
Export to a backup:
```bash
docker run -v ~/.kube:/home/argocd/.kube --rm argoproj/argocd:$VERSION argocd admin export > backup.yaml
docker run -v ~/.kube:/home/argocd/.kube --rm quay.io/argoproj/argocd:$VERSION argocd admin export > backup.yaml
```
Import from a backup:
```bash
docker run -i -v ~/.kube:/home/argocd/.kube --rm argoproj/argocd:$VERSION argocd admin import - < backup.yaml
docker run -i -v ~/.kube:/home/argocd/.kube --rm quay.io/argoproj/argocd:$VERSION argocd admin import - < backup.yaml
```
!!! note

View File

@@ -538,15 +538,15 @@ spec:
- secretName: secret-yourdomain-com
rules:
- host: argocd.yourdomain.com
http:
paths:
- pathType: ImplementationSpecific
path: "/*" # "*" is needed. Without this, the UI Javascript and CSS will not load properly
backend:
service:
name: argocd-server
port:
number: 80
http:
paths:
- pathType: ImplementationSpecific
path: "/*" # "*" is needed. Without this, the UI Javascript and CSS will not load properly
backend:
service:
name: argocd-server
port:
number: 80
```
If you use the version `1.21.3-gke.1600` or later, you should use the following Ingress resource:
@@ -563,15 +563,15 @@ spec:
- secretName: secret-yourdomain-com
rules:
- host: argocd.yourdomain.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: argocd-server
port:
number: 80
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: argocd-server
port:
number: 80
```
As you may know already, it can take some minutes to deploy the load balancer and become ready to accept connections. Once it's ready, get the public IP address for your Load Balancer, go to your DNS server (Google or third party) and point your domain or subdomain (i.e. argocd.yourdomain.com) to that IP address.

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