Compare commits

..

150 Commits

Author SHA1 Message Date
Michael Crenshaw
6f7af53bea chore(deps): bump slsa-github-generator to 1.9.0 (#16200)
* chore(deps): bump slsa-github-generator to 1.9.0

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

* missed a spot

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

---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-11-01 10:46:55 -04:00
gcp-cherry-pick-bot[bot]
684c033e72 docs(cmp): fix CMP param getter example (#16077) (#16190) (#16198)
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:17:39 -04:00
github-actions[bot]
113b53859d Bump version to 2.8.6 (#16181)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-10-31 10:15:09 -04:00
gcp-cherry-pick-bot[bot]
bd6e79da98 Set cert resolver in notifications-controller (#15394) (#16171)
Signed-off-by: Siddhesh Ghadi <sghadi1203@gmail.com>
Co-authored-by: Siddhesh Ghadi <61187612+svghadi@users.noreply.github.com>
2023-10-30 21:17:12 -04:00
gcp-cherry-pick-bot[bot]
fd63155b0e fix: argocd notification controller app cluster permission issue (#16057) (#16160)
* 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:40 -04:00
github-actions[bot]
85025e1dcb Bump version to 2.8.5 (#16113)
Signed-off-by: GitHub <noreply@github.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-10-27 18:41:17 -04:00
Michael Crenshaw
b3ba6e1844 chore: fix build error (#16130)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-10-27 10:44:04 -04:00
Michael Crenshaw
870965d081 fix(notifications): Allow notifications controller to notify on all namespaces (cherry-pick 2.8) (#15855)
* 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:27 -04:00
Tom Sands
7415f47b2b feat(cmp): Print stderr output from command even on success (#15921) (#15973) (#16124)
* feat(cmp): Print stderr output from command even on success



* docs(cmp): Document logging from cmp sidecard for development purposes



---------

Signed-off-by: Mathias Petermann <mathias.petermann@gmail.com>
Signed-off-by: Thomas Sands <thomas.sands@ly.st>
Co-authored-by: Mathias Petermann <mathias.petermann@gmail.com>
2023-10-26 11:32:29 -04:00
gcp-cherry-pick-bot[bot]
a9c87d2ad7 fix: helm set parameter to allow passing list parameters (#15978) (#15994)
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:35 -04:00
gcp-cherry-pick-bot[bot]
0a4043099b fix(appset): performProgressiveSyncs only when the applicationset is using it (#15299) (#15991)
Signed-off-by: Eric Blackburn <eblackburn@indeed.com>
Co-authored-by: ericblackburn <eblackburn@indeed.com>
2023-10-16 16:44:42 -04:00
jannfis
ebab8ec625 chore(deps): Bump gRPC to 1.58.3 and x/net to v0.17.0 [release-2.8] (#15917)
* 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:58 -04:00
gcp-cherry-pick-bot[bot]
c90d7baf31 fix(ui): pod count tooltip (#15928) (#15960)
Signed-off-by: Smriti Prakash <smriti_prakash@intuit.com>
Co-authored-by: smriti0710 <smriti3prakash7@gmail.com>
Co-authored-by: Smriti Prakash <smriti_prakash@intuit.com>
2023-10-13 13:08:02 -04:00
Michael Crenshaw
d23e132bdf chore(deps): bump library/golang from 1.20.6 to 1.20.10 (#15906)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-10-11 16:15:47 -04:00
gcp-cherry-pick-bot[bot]
5698864d60 fix(ui): responsive topbar, filter button in application details page (#11188) (#15789) (#15797)
Signed-off-by: Julien Fuix <julienfuix@epitech.eu>
Co-authored-by: Julien <57724344+JulienFuix@users.noreply.github.com>
Co-authored-by: Julien Fuix <julienfuix@epitech.eu>
2023-10-03 11:59:27 -04:00
gcp-cherry-pick-bot[bot]
0c2f16b811 fix(plugin): remove git environment variables unavailable to plugin execution (#14998) (#15104) (#15794)
* remove git creds environment variables unavailable to plugin execution



* remove integration test asserting askpass is forwarded



---------

Signed-off-by: jmcshane <james.mcshane@superorbital.io>
Co-authored-by: James McShane <jmcshan1@gmail.com>
2023-10-03 11:49:01 -04:00
gcp-cherry-pick-bot[bot]
4e936006f7 fix(action): populate all fields of Job from CronJob (#15259) (#15727) (#15729)
Signed-off-by: sergey.ladutko <sergey.ladutko@vizor-games.com>
Co-authored-by: SergeyLadutko <40435115+SergeyLadutko@users.noreply.github.com>
Co-authored-by: sergey.ladutko <sergey.ladutko@vizor-games.com>
2023-09-29 11:44:54 -04:00
gcp-cherry-pick-bot[bot]
365a1fc3e3 fix: sidebar style (#15652) (#15681)
Signed-off-by: ymktmk <ymktmk.tt@gmail.com>
Co-authored-by: ymktmk <73768462+ymktmk@users.noreply.github.com>
Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
2023-09-28 09:35:43 -04:00
gcp-cherry-pick-bot[bot]
4a459f2f30 docs: 'action' RBAC example for Kind without group (#15589) (#15597)
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-09-20 11:14:09 -04:00
gcp-cherry-pick-bot[bot]
3bee2940f0 fix(#12862): Update FlinkDeployment health check to support Flink v1.x (#15065) (#15558)
Signed-off-by: Dylan Slavin <dylan@sla.vin>
Co-authored-by: Dylan Slavin <dylan@sla.vin>
2023-09-19 09:05:44 -04:00
gcp-cherry-pick-bot[bot]
dffdb6bd03 fix(repo-server): avoid fetching commit sha for multisource applications (#15037) (#15067) (#15555)
* 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 07:20:02 -04:00
gcp-cherry-pick-bot[bot]
330bcd2673 docs: Fix docs for destinations in AppProjects (#15153) (#15182) (#15538)
Signed-off-by: Andreas Lindhé <andreas@lindhe.io>
Co-authored-by: Andreas Lindhé <lindhe@users.noreply.github.com>
2023-09-18 09:13:49 -04:00
gcp-cherry-pick-bot[bot]
908c51bc5c fix: Applicationset upsert for any namespaces (#15520) (#15525)
* fix: Applicationset upsert for any namespaces

PR implements a small change to the argocd command adding the AppsetNamespace for the existing Appset check.



* Retrigger CI pipeline



* Retrigger CI pipeline



---------

Signed-off-by: Harm Matthias Harms <matthias.harms@quis.de>
Co-authored-by: Harm Matthias Harms <harmmatthias.harms@gmail.com>
2023-09-15 12:01:36 -04:00
gcp-cherry-pick-bot[bot]
c24062573f fix: make WatchResourceTree use namespaced cache key (#15258) (#15522)
* 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:01:00 -04:00
github-actions[bot]
c279299281 Bump version to 2.8.4 (#15493)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: jannfis <jannfis@users.noreply.github.com>
2023-09-13 15:11:00 -04:00
gcp-cherry-pick-bot[bot]
24f8d82954 chore(deps): bump github.com/cyphar/filepath-securejoin (#15401) (#15492)
Bumps [github.com/cyphar/filepath-securejoin](https://github.com/cyphar/filepath-securejoin) from 0.2.3 to 0.2.4.
- [Release notes](https://github.com/cyphar/filepath-securejoin/releases)
- [Commits](https://github.com/cyphar/filepath-securejoin/compare/v0.2.3...v0.2.4)

---
updated-dependencies:
- dependency-name: github.com/cyphar/filepath-securejoin
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-13 13:59:11 -04:00
gcp-cherry-pick-bot[bot]
1ab570ad96 fix(controller): make managed namespaces more 'prune-proof' (#13999) (#15488)
* fix: make managed namespaces more 'prune-proof'

In the cases where someone would want to set resource tracking on a
managed namespace, or if someone would want to migrate from having a
namespace in Git to using `managedNamespaceMetadata`, we need to take
steps to ensure that the namespace does not get pruned.

In order to do that, we add the live namespace to the list of
`targetObjs` (so that it's considered a part of the source even though
it's not).

Finally, we need to also ensure that the managed namespace is not
considered `OutOfSync` (due to the same reason as above).

This is a subset of #11350, the main difference being that this commit
does not set resource tracking on its own, but can be opted into if
people choose to do so.



* refactor: extract managed namespace check



---------

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
2023-09-13 13:43:29 -04:00
gcp-cherry-pick-bot[bot]
8612a99fe8 fix: Gitlab scm_provider - don't create transport from scratch (#15424) (#15425) (#15471) 2023-09-13 13:35:07 -04:00
jannfis
33fa128bad fix: Allow retrieving badges in other namespaces (#15468) (#15482)
Signed-off-by: jannfis <jann@mistrust.net>
2023-09-13 13:01:05 -04:00
gcp-cherry-pick-bot[bot]
4e79aab03d fix: extends CR to allow cronjob/workflow triggers (#15300) (#15487)
Signed-off-by: Marcelo Moreira de Mello <tchello.mello@gmail.com>
Co-authored-by: Marcelo Mello <tchello.mello@gmail.com>
Co-authored-by: Nicholas Morey <nicholas@morey.tech>
2023-09-13 12:15:23 -04:00
gcp-cherry-pick-bot[bot]
7b89ae4424 fix: Stop appending :443 to the server address when using grpc-web (#15435) (#15470)
Signed-off-by: David Marby <david@dmarby.se>
Co-authored-by: David Marby <david@dmarby.se>
Co-authored-by: jannfis <jann@mistrust.net>
2023-09-12 19:13:05 -04:00
gcp-cherry-pick-bot[bot]
2577d51796 fix(appsets): gotemplate can cause panic from nil dereference (#15377) (#15378) (#15434)
* fix(appsets): gotemplate can cause panic from nil deference



* fix(appsets): gotemplate can cause panic from nil dereference



---------

Signed-off-by: rumstead <37445536+rumstead@users.noreply.github.com>
Co-authored-by: rumstead <37445536+rumstead@users.noreply.github.com>
2023-09-08 18:58:22 -04:00
gcp-cherry-pick-bot[bot]
2f8533efeb fix: handle annotations for resources with ':' in the name (#15101) (#15380) (#15414)
* 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:56:18 -04:00
gcp-cherry-pick-bot[bot]
c698426a5c fix(appset): Revert applicationset-name labels (#15324) (#15422)
* fix(15282) Revert applicationset-name labels



* fix(15282) fix unit test



---------

Signed-off-by: gmuselli <geoffrey.muselli@gmail.com>
Co-authored-by: Geoffrey MUSELLI <geoffrey.muselli@gmail.com>
2023-09-08 10:00:12 -04:00
github-actions[bot]
77556d9e64 Bump version to 2.8.3 (#15404)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: pasha-codefresh <pasha-codefresh@users.noreply.github.com>
2023-09-07 17:59:35 +03:00
Alexander Matyushentsev
1391ba7214 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
44e52c4ae7 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
gcp-cherry-pick-bot[bot]
4b55084a8f docs: Update ApplicationSet docs (#15269) (#15270)
Signed-off-by: David Muckle <dvdmuckle@dvdmuckle.xyz>
Co-authored-by: David Muckle <dvdmuckle@dvdmuckle.xyz>
2023-08-28 20:36:18 -04:00
gcp-cherry-pick-bot[bot]
c689ea4957 docs: improve doc on labels parameter on scmProvider generator (#15255) (#15265)
Signed-off-by: Jedrzej Kotkowski <jedrzejk143@gmail.com>
Co-authored-by: Jędrzej Kotkowski <96917147+jjsiv@users.noreply.github.com>
2023-08-28 17:48:58 -04:00
gcp-cherry-pick-bot[bot]
ea9827791f fix(ui): Helm chart empty maintainers blow up Argo UI (#15225) (#15243)
Signed-off-by: Carlos Castro carlos.castro@jumo.world

Signed-off-by: Carlos Castro carlos.castro@jumo.world

Signed-off-by: Carlos Castro carlos.castro@jumo.world
Signed-off-by: Carlos Castro <carlos.castro@jumo.world>
Co-authored-by: Carlos Castro <carlos.castro@jumo.world>
2023-08-25 09:59:33 -04:00
github-actions[bot]
dbdfc71270 Bump version to 2.8.2 (#15232)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-08-24 16:03:15 -04:00
gcp-cherry-pick-bot[bot]
31473491f5 fix(ui): Update default and max count for maxCookieNumber (#14979) (#15230)
* Update default and max count for maxCookieNumber

Signed-off-by: zvlb <vl.zemtsov@gmail.com>
Co-authored-by: Vladimir <31961982+zvlb@users.noreply.github.com>
2023-08-24 15:16:01 -04:00
gcp-cherry-pick-bot[bot]
016c2f25cd docs: document sourceNamespaces field (#15195) (#15212)
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:55 -04:00
gcp-cherry-pick-bot[bot]
31e6e28ca4 chore: add example jq path expression (#15130) (#15209)
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:29:53 -04:00
gcp-cherry-pick-bot[bot]
f07088281b fix: requeue ApplicationSet if there are validation errors (#14429) (#15206)
Signed-off-by: Chetan Banavikalmutt <chetanrns1997@gmail.com>
Co-authored-by: Chetan Banavikalmutt <chetanrns1997@gmail.com>
2023-08-24 11:29:05 -04:00
gcp-cherry-pick-bot[bot]
bf77b09b7b fix(appset): bitbucket server scm provider EOF on empty repo (#14411) (#15203)
* 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:21 -04:00
gcp-cherry-pick-bot[bot]
e982e0b80e fix(health): spec.executor.instances is Optional, Support a flexible number of executors (#11877) (#15200)
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:27:53 -04:00
gcp-cherry-pick-bot[bot]
3a468c6862 fix(ui): switch podgroup notification to tooltip message (#14821) (#15224)
* improve pod grouping ux



fix: update log view on container select



* fix(ui): improve pod grouping ux



* fix(ui):update the pod grouping messages to tooltip



* fix(ui):update the pod grouping messages to tooltip



* fix: GroupNodes notification



* fix: GroupNodes notification



---------

Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>
Signed-off-by: AS <11219262+ashutosh16@users.noreply.github.com>
Co-authored-by: AS <11219262+ashutosh16@users.noreply.github.com>
2023-08-24 11:14:42 -04:00
gcp-cherry-pick-bot[bot]
383f2a288b fix: stop creating new otel interceptor to avoid memory leak (#15174) (#15178)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Co-authored-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2023-08-22 20:09:28 -07:00
gcp-cherry-pick-bot[bot]
e006908fe9 fix(appset): Fix helm valuesObject with ApplicationSet (#14912) (#14920) (#15175)
Signed-off-by: Geoffrey Muselli <geoffrey.muselli@gmail.com>
Co-authored-by: Geoffrey MUSELLI <geoffrey.muselli@gmail.com>
2023-08-22 16:15:57 -07:00
gcp-cherry-pick-bot[bot]
2bc94af7bd fix(ui): code lint (#15150) (#15160)
Signed-off-by: ebuildy <ebuildy@gmail.com>
Co-authored-by: Thomas Decaux <ebuildy@gmail.com>
2023-08-22 14:20:40 -04:00
gcp-cherry-pick-bot[bot]
b9a32bb86e fix: windows build (#15154) (#15156)
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:47 -04:00
github-actions[bot]
356e33ac29 Bump version to 2.8.1 (#15139)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-08-21 16:29:01 -04:00
pasha-codefresh
9ffef110da 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]
af721bbb63 docs(progressive syncs): specify which ConfigMap to use (#15119) (#15133)
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:36 -04:00
gcp-cherry-pick-bot[bot]
91d249ec84 docs: ✏️ fix typo on configmap name for private certs (#9596) (#15131)
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:06 -04:00
gcp-cherry-pick-bot[bot]
004ca26b9e docs: fix typo (#15083) (#15090)
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 12:01:35 -04:00
gcp-cherry-pick-bot[bot]
2a17ca57ec docs: add docs for various annotations and labels (#14020) (#15112)
* 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:37 -04:00
gcp-cherry-pick-bot[bot]
89495d72df docs: kubectl to synchronize argocd apps (#14881) (#15085)
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:20:13 -04:00
gcp-cherry-pick-bot[bot]
885bb57ae8 docs: fix link for argocd-repo-creds.yaml sample (#15091) (#15097)
Signed-off-by: SHIMADA Kento <shimada.kento8974@gmail.com>
Co-authored-by: SHIMADA Kento <shimada.kento8974@gmail.com>
2023-08-17 14:30:53 -04:00
gcp-cherry-pick-bot[bot]
d6d7b1452d docs: document permitOnlyProjectScopedClusters field (#15076) (#15082)
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:30 -04:00
gcp-cherry-pick-bot[bot]
490c78b75f fix: bump ubuntu base image (#15020) (#15021) (#15023)
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:16 -04:00
Jason Meridth
092e55e279 chore(deps): upgrade nhooyr.io/websocket dependency (#15000) (#15008)
Upgrade from 1.8.6 to 1.8.7 due to high security issue

Was solved in dependency with https://github.com/nhooyr/websocket/pull/291

Signed-off-by: jmeridth <jmeridth@gmail.com>
2023-08-11 10:24:26 -04:00
gcp-cherry-pick-bot[bot]
fe526dd33c fix(actions): check if CronWorkflow has labels in create-workflow action (#14962) (#14974) (#14982)
Signed-off-by: Mickaël Canévet <mickael.canevet@gmail.com>
Co-authored-by: Mickaël Canévet <mickael.canevet@gmail.com>
2023-08-09 10:03:53 -04:00
gcp-cherry-pick-bot[bot]
cade0e970d fix(cmp): send sigterm to cmp commands before sigkill to allow for potential cleanup (#9180) (#14955) (#14958)
* 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:37:55 -04:00
gcp-cherry-pick-bot[bot]
e58eaaf36f fix(ui): COPY JSON for ArgoCD version should include trailing newline (#5117) (#14917) (#14938)
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:30 -04:00
gcp-cherry-pick-bot[bot]
0c4e249922 docs: Update helm.md - add missing syntax highlighting for YAML and Dockerfile blocks (#14911) (#14937)
Signed-off-by: JesseBot <jessebot@linux.com>
Co-authored-by: JesseBot <jessebot@linux.com>
2023-08-07 10:58:20 -04:00
gcp-cherry-pick-bot[bot]
8ea6650dd7 docs: Update Generators-Git.md (#14921) (#14934)
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:49:02 -04:00
github-actions[bot]
804d4b8ca6 Bump version to 2.8.0 (#14932)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-08-07 10:01:36 -04:00
gcp-cherry-pick-bot[bot]
f33c4e6884 fix(appset): typo in ARGOCD_APPLICATIONSET_CONTROLLER_ALLOWED_SCM_PROVIDERS (#14902) (#14913) (#14931)
Signed-off-by: gmuselli <geoffrey.muselli@gmail.com>
Co-authored-by: Geoffrey MUSELLI <geoffrey.muselli@gmail.com>
2023-08-07 09:54:37 -04:00
gcp-cherry-pick-bot[bot]
d1be2979a4 fix: Change underscore (_) back to plus (+) to get valid SemVer when when reading tags from OCI registry (#14537) (#14908)
* fix: Change underscore (_) back to plus (+) to get valid SemVer when reading tags from OCI registry



* Add test coverage for SemVer tags in TestGetTagsFromUrl



---------

Signed-off-by: xashr <saschasynaos@gmail.com>
Co-authored-by: xashr <103113861+xashr@users.noreply.github.com>
2023-08-04 16:18:16 -04:00
gcp-cherry-pick-bot[bot]
0872b762fb chore: fix non-deterministic test (#14905) (#14906)
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-04 12:58:48 -04:00
gcp-cherry-pick-bot[bot]
eeb846169d fix(ui): Fixes health icon positioning (#14708) (#14852) (#14901)
* fix: Fixes health icon positioning #14708



* fix: Fixes alignment of app health application status panel #14708



* fix: Added line height to App Status to fix its  positioning #14708



---------

Signed-off-by: ashinsabu3 <ashin.sabu@harness.io>
Co-authored-by: Ashin Sabu <139749674+ashinsabu3@users.noreply.github.com>
2023-08-04 11:05:23 -04:00
gcp-cherry-pick-bot[bot]
3535ab9400 chore: wrap ComparisonError messages (#14886) (#14890)
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 19:16:22 -04:00
github-actions[bot]
1ee5010d6d Bump version to 2.8.0-rc7 (#14879)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-08-03 10:51:06 -04:00
gcp-cherry-pick-bot[bot]
cdcbe1b667 docs: add ignoreDifferences name and namespace fields (#14741) (#14805)
* 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:34:36 -04:00
gcp-cherry-pick-bot[bot]
fd7c905b3a docs: Update application.yaml (#14742) (#14809)
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:02 -04:00
gcp-cherry-pick-bot[bot]
8b7ad25121 docs: Update Controlling-Resource-Modification.md (#14751) (#14812)
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:26:37 -04:00
gcp-cherry-pick-bot[bot]
01e75ae295 fix: Repo URL link for unsupported sources links to https://<argocd>/null/path/to/chart (#14861) (#14873)
* Fix #14860

Fix #14860



* Update USERS.md



---------

Signed-off-by: Talia Stocks <928827+taliastocks@users.noreply.github.com>
Co-authored-by: Talia Stocks <928827+taliastocks@users.noreply.github.com>
2023-08-03 10:25:57 -04:00
Michael Merrill
e671cd447f feat: Adding kubelogin capability to argocd-k8s-auth (#9460) (#10700) (#14866)
Signed-off-by: mmerrill3 <jjpaacks@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-08-03 10:25:16 -04:00
gcp-cherry-pick-bot[bot]
7dd040c095 chore: revert #12255 (#14858) (#14863)
This reverts commit c651bd8de5.

Due to the imminent release of 2.8, this needs to be rolled back since
the proposed fix in #14210 cannot make it in time.

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
2023-08-02 17:26:43 -04:00
gcp-cherry-pick-bot[bot]
b4e29cff73 chore: add more tests in proxy extension headers (#14842) (#14853)
Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>
Co-authored-by: Leonardo Luz Almeida <leoluz@users.noreply.github.com>
2023-08-02 11:20:43 -04:00
gcp-cherry-pick-bot[bot]
0d5ef9c835 chore: Add header support for proxy extension requests (#14800) (#14841)
* chore: add server URL in the header of proxy extensions



* feat: add header support for proxy extension requests



* Address review comments



* address review comments



* Address review comments



* Address review comments



---------

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>
Co-authored-by: Leonardo Luz Almeida <leoluz@users.noreply.github.com>
2023-08-01 20:43:32 -04:00
gcp-cherry-pick-bot[bot]
2e92d12760 docs: Change Generator docs for List Generator to note any key/value pairs can be used (#14825) (#14832)
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:55:58 -04:00
gcp-cherry-pick-bot[bot]
806c46f508 fix: ManagedResources API should not return diff for hooks (#14816) (#14830)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Co-authored-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2023-08-01 09:07:47 -07:00
gcp-cherry-pick-bot[bot]
deb11bb2cf fix: Correct broken forever option in pod logs viewer. Fixes #14762 (#14763) (#14803)
Signed-off-by: Alex Collins <alex_collins@intuit.com>
Co-authored-by: Alex Collins <alexec@users.noreply.github.com>
2023-07-31 19:26:17 -04:00
gcp-cherry-pick-bot[bot]
17737ebbe6 fix(ui): no hyphen for "create job" action + nice icon (#14776) (#14777) (#14802)
* chore(actions): space instead of hyphen in action name (#14776)



* new field for backwards-compatibility



* align icons for maximum synergy



* delete unused function



* revert unnecessary changes



* Update docs/operator-manual/upgrading/2.7-2.8.md



---------

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-31 19:25:07 -04:00
gcp-cherry-pick-bot[bot]
ca1d49062a docs: Clarify that security policy covers last 3 versions (cherry-pick #14786) (#14791)
* docs: Clarify that security policy covers last 3 versions (#14786)

* docs: Clarify that security policy covers last 3 versions

Signed-off-by: Kostis Kapelonis <kostis@codefresh.io>

* Update SECURITY.md

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

---------

Signed-off-by: Kostis Kapelonis <kostis@codefresh.io>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* docs: Clarify that security policy covers last 3 versions (#14786)

* docs: Clarify that security policy covers last 3 versions

Signed-off-by: Kostis Kapelonis <kostis@codefresh.io>

* Update SECURITY.md

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

---------

Signed-off-by: Kostis Kapelonis <kostis@codefresh.io>
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: 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:51 -04:00
gcp-cherry-pick-bot[bot]
f17eb7896a fix(controller): cache deadlock on delete and re-add cluster (#14780) (#14798)
Signed-off-by: Nathan Romriell <nateromriell@gmail.com>
Co-authored-by: Nathan Romriell <nathan@modsy.com>
2023-07-31 16:37:44 -04:00
Geoffrey MUSELLI
d94e07820f feat(appset): Restrict scm provider urls (#14286) (#14779)
* 9353: Restrict scm provider urls



* 9353: Enforce restriction



* 9353: Fix after review



* 9353: Remove comment



* 9353: Fix units tests



* 9353: Code review, update comment



* 9353: Code review, update comment 2



* 9353: Remove doc issues



* 9353: Fix e2e



* 9353: Fix e2e goTemplate



* 9353: Fix e2e pullRequestGenerator



---------

Signed-off-by: gmuselli <geoffrey.muselli@gmail.com>
Signed-off-by: Geoffrey Muselli <geoffrey.muselli@gmail.com>
2023-07-30 23:57:19 -04:00
gcp-cherry-pick-bot[bot]
7852e44a2a docs: Add missing value (#14538) (#14774)
Signed-off-by: felix <felix@psy-coding.com>
Co-authored-by: Felix <github@felixglaeske.de>
2023-07-28 15:18:38 -04:00
Michael Crenshaw
40fda394eb fix: bad merge (#14759)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-07-27 15:41:27 -07:00
gcp-cherry-pick-bot[bot]
0dc3003da0 fix: OCI dependency url can't contain part of repository (#14699) (#14756)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Co-authored-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2023-07-27 16:18:43 -04:00
gcp-cherry-pick-bot[bot]
34f6e86980 fix(ui): display valuesobject if set (#14257) (#14755)
* fix: display valuesobject if set

With #11538 we now have the ability to set helm values as an object
instead of a string, but we also need to be able to correctly display
it in the UI if it is set.



* fix: set valuesobject on save

If `valuesObject` is present, set it to the value of
`input.spec.source.helm.values` on save, as an unmarshaled json string.



* fix: set `helm.values` to empty string on save

If `valuesObject` exists, set `input.spec.source.helm.values` to an
empty string once `valuesObject` has been unmarshalled from the
values input. This is to prevent unnecessary duplication of the values.



* chore: eslint



* chore: eslint



* fix: deep clone app

This is so that we can conditionally set `source.helm.values` without
inadvertently affecting other parts of the app. Only when the edit
button is pressed do we toggle `source.helm.values`.



* chore: eslint



---------

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
2023-07-27 14:49:03 -04:00
github-actions[bot]
ec382b14a1 Bump version to 2.8.0-rc6 (#14754)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-07-27 13:57:25 -04:00
gcp-cherry-pick-bot[bot]
bae90d4079 fix(sso): Set redirectURI for gitea, google, oauth Dex connectors (#11237) (#14736)
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:15:47 -04:00
gcp-cherry-pick-bot[bot]
bd016de57a fix(server): handle PATCH in http/s server (#2677) (#14530) (#14731)
Signed-off-by: mmerrill3 <jjpaacks@gmail.com>
Co-authored-by: Michael Merrill <jjpaacks@gmail.com>
2023-07-27 10:14:32 -04:00
gcp-cherry-pick-bot[bot]
927c04d8d5 fix(controller): log failed attempts to update operation state (#14273) (#14730)
* 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:14:07 -04:00
gcp-cherry-pick-bot[bot]
3f42c538a1 fix: manifest generation error with null annotations (#14336) (#14680) (#14735)
* fix: manifest generation error with null annotations



* fix test



* fix unit tests



---------

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>
Co-authored-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>
2023-07-26 17:09:15 -04:00
gcp-cherry-pick-bot[bot]
826a11fdbe chore: Upgrade semver to avoid cve (#14710) (#14713)
Signed-off-by: Yi Cai <yicai@redhat.com>
Co-authored-by: Yi Cai <yicai@redhat.com>
2023-07-26 10:19:45 -04:00
Yuan Tang
cfbbcef6f0 chore: Print in-cluster svr addr disabled warning when server starts (#14686)
* 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:46:22 -04:00
gcp-cherry-pick-bot[bot]
d9975b2fde feat(deep-links): sprig support (#14660) (#14676)
Signed-off-by: daftping <21245083+daftping@users.noreply.github.com>
Co-authored-by: daftping <21245083+daftping@users.noreply.github.com>
2023-07-24 13:26:40 -04:00
gcp-cherry-pick-bot[bot]
dd61b74f72 fix: ApplicationSet Controller crashes when tag is not closed; panic: Cannot find end tag="}}"(#14227) (#14651) (#14656)
* ApplicationSet bug fix



* Update applicationset/utils/utils_test.go



* oops



---------

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: schakrad <58915923+schakrad@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-07-24 11:40:18 -04:00
gcp-cherry-pick-bot[bot]
f21034c2df fix(ui): The default pod group filter should be removed if fewer than 15 pods (#14590) (#14671)
Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>
Co-authored-by: asingh <11219262+ashutosh16@users.noreply.github.com>
2023-07-24 10:39:49 -04:00
gcp-cherry-pick-bot[bot]
d8dcc97f95 fix: webhook handler fails to refresh when alternate application namespaces are configured (#13976) (#14652)
* 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:30:55 -04:00
gcp-cherry-pick-bot[bot]
5d71e25ed4 fix(ui): Drop ready from Completed container status (#14434) (#14629) (#14646)
Signed-off-by: schakrad <58915923+schakrad@users.noreply.github.com>
Co-authored-by: schakrad <58915923+schakrad@users.noreply.github.com>
2023-07-21 10:35:33 -04:00
gcp-cherry-pick-bot[bot]
f7a80fcb51 docs(deep-links): Fix link to pkg.go.dev to not return 404 (#14595) (#14639)
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:49 -04:00
gcp-cherry-pick-bot[bot]
ffe3d47528 docs: Skip export keyword in notification docs (#14633) (#14642)
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:42 -04:00
gcp-cherry-pick-bot[bot]
2f0e6e78c1 fix(ui): Fix Destination Cluster URL/Name Drop down not updating destination field (#13813) (#14216) (#14626)
* 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:44:06 -04:00
github-actions[bot]
eee1a8add2 Bump version to 2.8.0-rc5 (#14606)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-07-19 13:33:29 -04:00
gcp-cherry-pick-bot[bot]
c71664849a fix(api): return 404 when the app is not found if a project is specified (#13393) (#13394) (#14600)
* fix(api): return 404 when the app is not found if a project is specified (#13393)



simplify, respond 404 on project specified but doesn't match, always fetch app



handle project updates



* handle new endpoint, fix bad merge



* 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-07-19 09:23:02 -04:00
gcp-cherry-pick-bot[bot]
27e9c13fb8 fix(cli): allow argocd cli app command for multi source apps (#14256) (#14586)
Signed-off-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>
Co-authored-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>
2023-07-18 16:35:09 -04:00
github-actions[bot]
982300a006 Bump version to 2.8.0-rc4 (#14580)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-07-18 16:24:05 -04:00
gcp-cherry-pick-bot[bot]
68a0d00f97 fix(cli): argocd CLI RBAC validation doesn't work on actions (#13911) (#14578) (#14581)
* #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:21:58 -04:00
gcp-cherry-pick-bot[bot]
af7f8af362 fix: Remove executable bit from default file mode (#14497) (#14576)
* Fix file/directory opening mode



* Fix TestUntgz/preserves_file_mode



* Mention file mode fix in 2.7-2.8 release docs



---------

Signed-off-by: ramikg <72725910+ramikg@users.noreply.github.com>
Co-authored-by: Rami <72725910+ramikg@users.noreply.github.com>
2023-07-18 13:30:01 -04:00
gcp-cherry-pick-bot[bot]
dacb2873f0 fix(server): not need send application if it is not under enabled namespaces (#14479) (#14575)
* 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-07-18 10:38:39 -04:00
gcp-cherry-pick-bot[bot]
b0a1d82309 fix: Correct pod log viewer to support short log lines. Fixes #14402 (#14543) (#14561)
Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>
Co-authored-by: Yuan Tang <terrytangyuan@gmail.com>
2023-07-18 08:49:10 -04:00
Noah Elzner
9612f73dbd chore: Generate SLSA provenance for SBOM (#14438) (cherry-pick #14507) (#14559)
* chore: Generate SLSA provenance for SBOM (#14438) (#14507)

* Add provenance generation for sbom

Signed-off-by: Noah Elzner <78953604+enteraga6@users.noreply.github.com>

* upload SBOM

Signed-off-by: Noah Elzner <78953604+enteraga6@users.noreply.github.com>

* Remove cosign setup

Signed-off-by: Noah Elzner <78953604+enteraga6@users.noreply.github.com>

* include hashes in generate-sbom output

Signed-off-by: Noah Elzner <78953604+enteraga6@users.noreply.github.com>

* Replace Cosign Verification command with SLSA command in docs

Signed-off-by: Noah Elzner <78953604+enteraga6@users.noreply.github.com>

* Remove id-token write permission - no longer needed

Signed-off-by: Noah Elzner <78953604+enteraga6@users.noreply.github.com>

---------

Signed-off-by: Noah Elzner <78953604+enteraga6@users.noreply.github.com>
Signed-off-by: Noah Elzner <elzner@google.com>

* change source tag in sbom verification command to v2.8.0

Signed-off-by: Noah Elzner <78953604+enteraga6@users.noreply.github.com>

---------

Signed-off-by: Noah Elzner <78953604+enteraga6@users.noreply.github.com>
Signed-off-by: Noah Elzner <elzner@google.com>
2023-07-18 08:34:38 -04:00
gcp-cherry-pick-bot[bot]
5dd9bdc37c fix(controller): populate ignoreDifferences in sync status (#14542) (#14557)
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 15:50:12 -04:00
gcp-cherry-pick-bot[bot]
80ea9798ca fix(appset): normalize app spec before applying (#14481) (#14554)
* 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:39 -04:00
gcp-cherry-pick-bot[bot]
05645b051e docs: Correct example ClusterRole to allow Events in any namespace (#14544) (#14550)
Fixes: #14477

Signed-off-by: Dimitar Georgievski <dgeorgievski@gmail.com>
Co-authored-by: Dimitar Georgievski <dgeorgievski@gmail.com>
2023-07-17 12:56:33 -04:00
gcp-cherry-pick-bot[bot]
9a79b19bdf fix: 'argocd-server-tls' Secret should be loaded from informer (#14522) (#14546)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Co-authored-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2023-07-17 12:01:43 -04:00
gcp-cherry-pick-bot[bot]
667800737d fix(security): don't allow app enumeration via RevisionChartDetails (#14512) (#14517)
* fix(security): don't allow app enumeration via RevisionChartDetails



* better app name



---------

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-15 09:56:36 -04:00
gcp-cherry-pick-bot[bot]
6cece3f550 docs: improve ignoreResourceUpdates docs (cherry-pick #14475) (#14504)
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-07-13 11:59:25 -04:00
gcp-cherry-pick-bot[bot]
50325a908a chore: improve ignoreResourceUpdates logging (#14476) (#14500)
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-07-13 10:45:57 -04:00
gcp-cherry-pick-bot[bot]
b5b443499b chore(deps): bump library/golang from 1.20.5 to 1.20.6 (#14480) (#14484)
Signed-off-by: fengshunli <1171313930@qq.com>
Co-authored-by: fsl <1171313930@qq.com>
2023-07-12 15:07:27 -04:00
github-actions[bot]
5d1d64fe83 Bump version to 2.8.0-rc3 (#14474)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-07-12 12:52:39 -04:00
gcp-cherry-pick-bot[bot]
83fa035f55 fix(cli): fix tracking annotation diff for non-namespaced resources (#13924) (#14473)
Signed-off-by: Maxime Brunet <max@brnt.mx>
Co-authored-by: Maxime Brunet <max@brnt.mx>
2023-07-12 09:36:17 -04:00
Ishita Sequeira
3ddee6d73f feat(appset): Add support for self-signed TLS / Certificates for Gitlab Scm Provider (#14348) (#14462)
* Add support for self-signed TLS / Certificates for Gitlab Scm Provider



* Add support for self-signed TLS / Certificates for Gitlab Pull Request



* Add TLS configuration for SCM and Pull request Gitlab Provider



* rebase with master



* add params to argocd-cmd-params-cm and docs



* fix generated manifests



* Address comments for docs



* Add test cases to validate insecure mode and ca cert combinations



* simplify (#24)



---------

Signed-off-by: ishitasequeira <ishiseq29@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-11 16:35:36 -04:00
gcp-cherry-pick-bot[bot]
53c582bced docs: Update SLSA verification commands (#14437) (#14454)
* 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:12 -04:00
gcp-cherry-pick-bot[bot]
00d995d3fa fix: trigger refresh on changed ignoreDifferences (#12607) (#14403)
* fix: trigger refresh on changed ignoreDifferences



* make the tests mean things



---------

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-11 10:49:08 -04:00
gcp-cherry-pick-bot[bot]
b1efb745f0 fix: Fix pod log viewer scrollbars (#14199) (#14418)
* 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:59 -04:00
gcp-cherry-pick-bot[bot]
d8c6e19501 fix(action): copy metadata on create-job action (#14232) (#14233) (#14453)
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-11 10:37:34 -04:00
gcp-cherry-pick-bot[bot]
ca03596ea4 docs: fix typo (#14412) (#14413)
Signed-off-by: yukinakanaka <yuki.nakamura@mapbox.com>
Co-authored-by: Yuki Nakamura <yuki.nakamura@mapbox.com>
2023-07-10 14:04:40 -04:00
gcp-cherry-pick-bot[bot]
7c4eee26ef fix: adds WebSocket ping to interactive terminal (#14191) (cherry-pick #14192) (#14399)
* fix: adds WebSocket ping to interactive terminal (#14191) (#14192)

This adds a WebSocket ping message on a 5-second interval, sent
from the server to the client. This ensures that the interactive
terminal will remain open and won't be closed by load balancers
that are reaping idle connections.

Signed-off-by: Edmund Rhudy <erhudy@users.noreply.github.com>

* fix: adds WebSocket ping to interactive terminal (#14191) (#14192)

This adds a WebSocket ping message on a 5-second interval, sent
from the server to the client. This ensures that the interactive
terminal will remain open and won't be closed by load balancers
that are reaping idle connections.

Signed-off-by: Edmund Rhudy <erhudy@users.noreply.github.com>

---------

Signed-off-by: Edmund Rhudy <erhudy@users.noreply.github.com>
Co-authored-by: Edmund Rhudy <erhudy@users.noreply.github.com>
2023-07-07 11:00:13 -04:00
gcp-cherry-pick-bot[bot]
d36d31b367 fix: Update bitbucket.org rsa ssh key (#14392) (#14396)
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:06 -04:00
gcp-cherry-pick-bot[bot]
d75aaaa0f7 fix(sharding): recurring info logs to debug (#14383) (#14385)
Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>
Co-authored-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>
2023-07-06 19:39:54 -04:00
gcp-cherry-pick-bot[bot]
e03a7b76dd docs: managedFieldsManagers example in docs needs double-quotes (#14324) (#14371)
* 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:39 -04:00
gcp-cherry-pick-bot[bot]
5cde94c9ab docs: Added information about scopes (#2782) (#14354) (#14380)
* Added information about scopes in the docs



* Apply suggestions from code review




---------

Signed-off-by: Christian Hernandez <christian@chernand.io>
Signed-off-by: Christian Hernandez <christianh814@users.noreply.github.com>
Co-authored-by: Christian Hernandez <christianh814@users.noreply.github.com>
Co-authored-by: Dan Garfield <dan@codefresh.io>
2023-07-06 13:49:18 -04:00
gcp-cherry-pick-bot[bot]
f2b61ed39f docs: typofix (#14344) (#14355)
Signed-off-by: Julien Bouquillon <julien.bouquillon@sg.social.gouv.fr>
Co-authored-by: Julien Bouquillon <contact@revolunet.com>
2023-07-05 21:41:17 -04:00
github-actions[bot]
d24b263601 Bump version to 2.8.0-rc2 (#14352)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-07-05 15:24:10 -04:00
gcp-cherry-pick-bot[bot]
2d6d30c1df fix: Change disallowed application destination message (#14284) (#14307) (#14326)
* change disallowed application destinations message



* Changed e2e tests



---------

Signed-off-by: michaelkot97 <michael.kot97@gmail.com>
Co-authored-by: Michael Kotelnikov <36506417+michaelkotelnikov@users.noreply.github.com>
2023-07-03 12:52:07 -07:00
gcp-cherry-pick-bot[bot]
fa74998f45 feat: upgrade dexIDP from 2.36.0 -> 2.37.0 (#14305) (#14310) (#14312)
* 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>
Co-authored-by: Venkata Mutyala <venkata@venkatamutyala.com>
2023-07-02 19:07:18 -07:00
gcp-cherry-pick-bot[bot]
d11142e321 fix: deadlock in controller (#14304) (#14306)
Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>
Co-authored-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>
2023-07-01 13:45:03 -07:00
gcp-cherry-pick-bot[bot]
e45ff37aad docs: Adding explanation for CMP yaml/json generation (must be K8S object) (#9471) (#14295) (#14301)
Signed-off-by: Christian Hernandez <christian@chernand.io>
Co-authored-by: Christian Hernandez <christianh814@users.noreply.github.com>
2023-06-30 19:38:17 -04:00
gcp-cherry-pick-bot[bot]
9ec2a2e93c docs: Clarify "SSH known host public keys" text (#13537) (#14297)
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:52 -04:00
gcp-cherry-pick-bot[bot]
5470a48c82 docs: change to the correct property for the dex server value (#14279) (#14291)
* fix: change to the correct property for dex server



* Update argocd-cmd-params-cm.yaml



---------

Signed-off-by: bjarneo <bjarneo@users.noreply.github.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: bjarneo <bjarneo@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-06-30 11:40:08 -04:00
gcp-cherry-pick-bot[bot]
a147c498e9 docs: explicit that ArgoCD hooks replaces the Helm ones (#14283) (#14287)
* 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:24 -04:00
gcp-cherry-pick-bot[bot]
1477b0d874 fix: Correctly verify signatures when targetRevision is a branch name (#14214) (#14235)
* fix: Correctly verify signatures when targetRevision is a branch name



* Add more e2e tests



* Fix a bug and add unit test



---------

Signed-off-by: jannfis <jann@mistrust.net>
Co-authored-by: jannfis <jann@mistrust.net>
2023-06-29 17:38:13 -04:00
github-actions[bot]
be263caab3 Bump version to 2.8.0-rc1 (#14225)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-06-27 10:57:17 -04:00
3682 changed files with 89989 additions and 518107 deletions

View File

@@ -8,7 +8,6 @@ ignore:
- "pkg/client/.*"
- "vendor/.*"
- "test/.*"
- "**/mocks/*"
coverage:
status:
# we've found this not to be useful

View File

@@ -18,10 +18,8 @@ hack/
docs/
examples/
.github/
!test/container
!test/e2e/testdata
!test/fixture
!test/remote
!test/container
!hack/installers
!hack/gpg-wrapper.sh
!hack/git-verify-wrapper.sh

18
.gitattributes vendored
View File

@@ -1,18 +0,0 @@
**/*.pb.go linguist-generated=true
**/mocks/*.go linguist-generated=true
assets/swagger.json linguist-generated=true
docs/operator-manual/resource_actions_builtin.md linguist-generated=true
docs/operator-manual/server-commands/argocd-*.md linguist-generated=true
docs/user-guide/commands/argocd_*.md linguist-generated=true
manifests/core-install.yaml linguist-generated=true
manifests/core-install-with-hydrator.yaml linguist-generated=true
manifests/crds/*-crd.yaml linguist-generated=true
manifests/ha/install.yaml linguist-generated=true
manifests/ha/install-with-hydrator.yaml linguist-generated=true
manifests/ha/namespace-install.yaml linguist-generated=true
manifests/ha/namespace-install-with-hydrator.yaml linguist-generated=true
manifests/install.yaml linguist-generated=true
manifests/install-with-hydrator.yaml linguist-generated=true
manifests/namespace-install.yaml linguist-generated=true
manifests/namespace-install-with-hydrator.yaml linguist-generated=true
pkg/apis/api-rules/violation_exceptions.list linguist-generated=true

View File

@@ -2,7 +2,7 @@
name: Bug report
about: Create a report to help us improve
title: ''
labels: ['bug', 'triage/pending']
labels: 'bug'
assignees: ''
---
@@ -10,9 +10,9 @@ assignees: ''
Checklist:
- [ ] I've searched in the docs and FAQ for my answer: https://bit.ly/argocd-faq.
- [ ] I've included steps to reproduce the bug.
- [ ] I've pasted the output of `argocd version`.
* [ ] I've searched in the docs and FAQ for my answer: https://bit.ly/argocd-faq.
* [ ] I've included steps to reproduce the bug.
* [ ] I've pasted the output of `argocd version`.
**Describe the bug**

View File

@@ -2,10 +2,9 @@
name: Enhancement proposal
about: Propose an enhancement for this project
title: ''
labels: ['enhancement', 'triage/pending']
labels: 'enhancement'
assignees: ''
---
# Summary
What change you think needs making.
@@ -16,4 +15,4 @@ Please give examples of your use case, e.g. when would you use this.
# Proposal
How do you think this should be implemented?
How do you think this should be implemented?

View File

@@ -1,43 +0,0 @@
---
name: New Dev Tool Request
about: This is a request for adding a new tool for setting up a dev environment.
title: ''
labels: ['component:dev-env', 'triage/pending']
assignees: ''
---
Checklist:
- [ ] I am willing to maintain this tool, or have another Argo CD maintainer who is.
- [ ] I have another Argo CD maintainer who is willing to help maintain this tool (there needs to be at least two maintainers willing to maintain this tool)
- [ ] I have a lead sponsor who is a core Argo CD maintainer
- [ ] There is a PR which adds said tool - this is so that the maintainers can assess the impact of having this in the tree
- [ ] I have given a motivation why this should be added
### The proposer
<-- The username(s) of the person(s) proposing the tool -->
### The proposed tool
<!-- The tool itself, with a link to the tools website -->
### Motivation
<!-- Why this tool would be useful to have in the tree. -->
### Link to PR (Optional)
<!-- A PR adding the tool to the tree -->
### Lead Sponsor(s)
Final approval requires sponsorship from at least one core maintainer.
- @<sponsor-1>
### Co-sponsors
These will be the co-maintainers of the specified tool.
- @<sponsor-1>

View File

@@ -9,6 +9,12 @@ assignees: ''
Target RC1 date: ___. __, ____
Target GA date: ___. __, ____
- [ ] Create new section in the [Release Planning doc](https://docs.google.com/document/d/1trJIomcgXcfvLw0aYnERrFWfPjQOfYMDJOCh1S8nMBc/edit?usp=sharing)
- [ ] Schedule a Release Planning meeting roughly two weeks before the scheduled Release freeze date by adding it to the community calendar (or delegate this task to someone with write access to the community calendar)
- [ ] Include Zoom link in the invite
- [ ] Post in #argo-cd and #argo-contributors one week before the meeting
- [ ] Post again one hour before the meeting
- [ ] At the meeting, remove issues/PRs from the project's column for that release which have not been “claimed” by at least one Approver (add it to the next column if Approver requests that)
- [ ] 1wk before feature freeze post in #argo-contributors that PRs must be merged by DD-MM-YYYY to be included in the release - ask approvers to drop items from milestone they cant merge
- [ ] At least two days before RC1 date, draft RC blog post and submit it for review (or delegate this task)
- [ ] Cut RC1 (or delegate this task to an Approver and coordinate timing)
@@ -22,6 +28,5 @@ Target GA date: ___. __, ____
- [ ] At release date, evaluate if any bugs justify delaying the release. If not, cut the release (or delegate this task to an Approver and coordinate timing)
- [ ] If unreleased changes are on the release branch for {current minor version minus 3}, cut a final patch release for that series (or delegate this task to an Approver and coordinate timing)
- [ ] After the release, post in #argo-cd that the {current minor version minus 3} has reached EOL (example: https://cloud-native.slack.com/archives/C01TSERG0KZ/p1667336234059729)
- [ ] Update the last patch release of the EOL minor release series to say that the version is EOL
- [ ] (For the next release champion) Review the [items scheduled for the next release](https://github.com/orgs/argoproj/projects/25). If any item does not have an assignee who can commit to finish the feature, move it to the next release.
- [ ] (For the next release champion) Schedule a time mid-way through the release cycle to review items again.
- [ ] (For the next release champion) Schedule a time mid-way through the release cycle to review items again.

View File

@@ -1,11 +1,10 @@
---
name: Security log
about: Propose adding security-related logs or tagging existing logs with security fields
title: 'seclog: [Event Description]'
labels: ['security', 'triage/pending']
assignees: ''
title: "seclog: [Event Description]"
labels: security-log
assignees: notfromstatefarm
---
# Event to be logged
Specify the event that needs to be logged or existing logs that need to be tagged.
@@ -17,3 +16,4 @@ What security level should these events be logged under? Refer to https://argo-c
# Common Weakness Enumeration
Is there an associated [CWE](https://cwe.mitre.org/) that could be tagged as well?

3
.github/cherry-pick-bot.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
enabled: true
preservePullRequestTitle: true

View File

@@ -1,15 +0,0 @@
module.exports = {
platform: 'github',
gitAuthor: 'renovate[bot] <renovate[bot]@users.noreply.github.com>',
autodiscover: false,
allowPostUpgradeCommandTemplating: true,
allowedPostUpgradeCommands: ["make mockgen"],
extends: [
"github>argoproj/argo-cd//renovate-presets/commons.json5",
"github>argoproj/argo-cd//renovate-presets/custom-managers/shell.json5",
"github>argoproj/argo-cd//renovate-presets/custom-managers/yaml.json5",
"github>argoproj/argo-cd//renovate-presets/fix/disable-all-updates.json5",
"github>argoproj/argo-cd//renovate-presets/devtool.json5",
"github>argoproj/argo-cd//renovate-presets/docs.json5"
]
}

View File

@@ -4,13 +4,8 @@ updates:
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 20
ignore:
- dependency-name: k8s.io/*
groups:
otel:
patterns:
- "go.opentelemetry.io/*"
- package-ecosystem: "github-actions"
directory: "/"
@@ -22,21 +17,10 @@ updates:
schedule:
interval: "daily"
# Disabled since this code is rarely used.
# - package-ecosystem: "npm"
# directory: "/ui-test/"
# schedule:
# interval: "daily"
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "daily"
ignore:
# We use consistent go and node versions across a lot of different files, and updating via dependabot would cause
# drift among those files, instead we let renovate bot handle them.
- dependency-name: "library/golang"
- dependency-name: "library/node"
- package-ecosystem: "docker"
directory: "/test/container/"
@@ -53,8 +37,7 @@ updates:
schedule:
interval: "daily"
# Disabled since this code is rarely used.
# - package-ecosystem: "docker"
# directory: "/ui-test/"
# schedule:
# interval: "daily"
- package-ecosystem: "docker"
directory: "/ui-test/"
schedule:
interval: "daily"

View File

@@ -1,24 +1,21 @@
<!--
Note on DCO:
If the DCO action in the integration test fails, one or more of your commits are not signed off. Please click on the *Details* link next to the DCO action for instructions on how to resolve this.
-->
Checklist:
* [ ] Either (a) I've created an [enhancement proposal](https://github.com/argoproj/argo-cd/issues/new/choose) and discussed it with the community, (b) this is a bug fix, or (c) this does not need to be in the release notes.
* [ ] The title of the PR states what changed and the related issues number (used for the release note).
* [ ] The title of the PR conforms to the [Title of the PR](https://argo-cd.readthedocs.io/en/latest/developer-guide/submit-your-pr/#title-of-the-pr)
* [ ] 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?
* [ ] I've updated documentation as required by this PR.
* [ ] Optional. My organization is added to USERS.md.
* [ ] I have signed off all my commits as required by [DCO](https://github.com/argoproj/argoproj/blob/master/community/CONTRIBUTING.md#legal)
* [ ] I have written unit and/or e2e tests for my change. PRs without these are unlikely to be merged.
* [ ] My build is green ([troubleshooting builds](https://argo-cd.readthedocs.io/en/latest/developer-guide/ci/)).
* [ ] My build is green ([troubleshooting builds](https://argo-cd.readthedocs.io/en/latest/developer-guide/ci/)).
* [ ] My new feature complies with the [feature status](https://github.com/argoproj/argoproj/blob/master/community/feature-status.md) guidelines.
* [ ] I have added a brief description of why this PR is necessary and/or what this PR solves.
* [ ] Optional. My organization is added to USERS.md.
* [ ] Optional. For bug fixes, I've indicated what older releases this fix should be cherry-picked into (this may or may not happen depending on risk/complexity).
<!-- Please see [Contribution FAQs](https://argo-cd.readthedocs.io/en/latest/developer-guide/faq/) if you have questions about your pull-request. -->
Please see [Contribution FAQs](https://argo-cd.readthedocs.io/en/latest/developer-guide/faq/) if you have questions about your pull-request.

View File

@@ -6,18 +6,17 @@
| codeql.yaml | CodeQL analysis |
| image-reuse.yaml | Build, push, and Sign container images |
| image.yaml | Build container image for PR's & publish for push events |
| init-release.yaml | Build manifests and version then create a PR for release branch|
| pr-title-check.yaml| Lint PR for semantic information |
| init-release.yaml | Build manifests and version then create a PR for release branch|
| release.yaml | Build images, cli-binaries, provenances, and post actions |
| scorecard.yaml | Generate scorecard for supply-chain security |
| update-snyk.yaml | Scheduled snyk reports |
# Reusable workflows
## image-reuse.yaml
- The reusable 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.21
- 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
- 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

@@ -1,89 +0,0 @@
name: Bump major version
on:
workflow_dispatch: {}
permissions: {}
jobs:
prepare-release:
permissions:
contents: write # for peter-evans/create-pull-request to create branch
pull-requests: write # for peter-evans/create-pull-request to create a PR
name: Automatically update major version
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
# Get the current major version from go.mod and save it as a variable.
- name: Get target version
id: get-target-version
run: |
set -ue
CURRENT_VERSION=$(grep 'module github.com/argoproj/argo-cd' go.mod | awk '{print $2}' | sed 's/.*\/v//')
echo "TARGET_VERSION=$((CURRENT_VERSION + 1))" >> $GITHUB_OUTPUT
- name: Copy source code to GOPATH
run: |
mkdir -p ~/go/src/github.com/argoproj
cp -a ../argo-cd ~/go/src/github.com/argoproj
- name: Run script to bump the version
run: |
hack/bump-major-version.sh
working-directory: /home/runner/go/src/github.com/argoproj/argo-cd
- name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Add ~/go/bin to PATH
run: |
echo "/home/runner/go/bin" >> $GITHUB_PATH
- name: Add /usr/local/bin to PATH
run: |
echo "/usr/local/bin" >> $GITHUB_PATH
- name: Download & vendor dependencies
run: |
# We need to vendor go modules for codegen yet
go mod download
go mod vendor -v
working-directory: /home/runner/go/src/github.com/argoproj/argo-cd
- name: Install toolchain for codegen
run: |
make install-codegen-tools-local
make install-go-tools-local
working-directory: /home/runner/go/src/github.com/argoproj/argo-cd
# We install kustomize in the dist directory
- name: Add dist to PATH
run: |
echo "/home/runner/work/argo-cd/argo-cd/dist" >> $GITHUB_PATH
- name: Run codegen
run: |
set -x
export GOPATH=$(go env GOPATH)
make codegen-local
working-directory: /home/runner/go/src/github.com/argoproj/argo-cd
- name: Copy changes back
run: |
# Copy the contents back, but skip the .git directory
rsync -a --exclude=.git /home/runner/go/src/github.com/argoproj/argo-cd/ ../argo-cd
- name: Create pull request
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with:
commit-message: "Bump major version to ${{ steps.get-target-version.outputs.TARGET_VERSION }}"
title: "Bump major version to ${{ steps.get-target-version.outputs.TARGET_VERSION }}"
body: |
Congrats! You've just bumped the major version to ${{ steps.get-target-version.outputs.TARGET_VERSION }}.
Next steps:
- [ ] Merge this PR
- [ ] Add an upgrade guide to the docs for this version
branch: bump-major-version
branch-suffix: random
signoff: true

View File

@@ -1,121 +0,0 @@
name: Cherry Pick Single
on:
workflow_call:
inputs:
merge_commit_sha:
required: true
type: string
description: "The merge commit SHA to cherry-pick"
version_number:
required: true
type: string
description: "The version number (from cherry-pick/ label)"
pr_number:
required: true
type: string
description: "The original PR number"
pr_title:
required: true
type: string
description: "The original PR title"
secrets:
CHERRYPICK_APP_ID:
required: true
CHERRYPICK_APP_PRIVATE_KEY:
required: true
jobs:
cherry-pick:
name: Cherry Pick to ${{ inputs.version_number }}
runs-on: ubuntu-latest
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1
with:
app-id: ${{ secrets.CHERRYPICK_APP_ID }}
private-key: ${{ secrets.CHERRYPICK_APP_PRIVATE_KEY }}
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
token: ${{ steps.generate-token.outputs.token }}
- name: Configure Git
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Cherry pick commit
id: cherry-pick
run: |
set -e
MERGE_COMMIT="${{ inputs.merge_commit_sha }}"
TARGET_BRANCH="release-${{ inputs.version_number }}"
echo "🍒 Cherry-picking commit $MERGE_COMMIT to branch $TARGET_BRANCH"
# Check if target branch exists
if ! git show-ref --verify --quiet "refs/remotes/origin/$TARGET_BRANCH"; then
echo "❌ Target branch '$TARGET_BRANCH' does not exist"
exit 1
fi
# Create new branch for cherry-pick
CHERRY_PICK_BRANCH="cherry-pick-${{ inputs.pr_number }}-to-${TARGET_BRANCH}"
git checkout -b "$CHERRY_PICK_BRANCH" "origin/$TARGET_BRANCH"
# Perform cherry-pick
if git cherry-pick -m 1 "$MERGE_COMMIT"; then
echo "✅ Cherry-pick successful"
# Extract Signed-off-by from the cherry-pick commit
SIGNOFF=$(git log -1 --pretty=format:"%B" | grep -E '^Signed-off-by:' || echo "")
# Push the new branch
git push origin "$CHERRY_PICK_BRANCH"
# Save data for PR creation
echo "branch_name=$CHERRY_PICK_BRANCH" >> "$GITHUB_OUTPUT"
echo "signoff=$SIGNOFF" >> "$GITHUB_OUTPUT"
echo "target_branch=$TARGET_BRANCH" >> "$GITHUB_OUTPUT"
else
echo "❌ Cherry-pick failed due to conflicts"
git cherry-pick --abort
exit 1
fi
- name: Create Pull Request
run: |
# Create cherry-pick PR
TITLE="${PR_TITLE} (cherry-pick #${{ inputs.pr_number }} for ${{ inputs.version_number }})"
BODY=$(cat <<EOF
Cherry-picked ${PR_TITLE} (#${{ inputs.pr_number }})
${{ steps.cherry-pick.outputs.signoff }}
EOF
)
gh pr create \
--title "$TITLE" \
--body "$BODY" \
--base "${{ steps.cherry-pick.outputs.target_branch }}" \
--head "${{ steps.cherry-pick.outputs.branch_name }}"
# Comment on original PR
gh pr comment ${{ inputs.pr_number }} \
--body "🍒 Cherry-pick PR created for ${{ inputs.version_number }}: #$(gh pr list --head ${{ steps.cherry-pick.outputs.branch_name }} --json number --jq '.[0].number')"
env:
PR_TITLE: ${{ inputs.pr_title }}
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
- name: Comment on failure
if: failure()
run: |
gh pr comment ${{ inputs.pr_number }} \
--body "❌ Cherry-pick failed for ${{ inputs.version_number }}. Please check the [workflow logs](https://github.com/argoproj/argo-cd/actions/runs/${{ github.run_id }}) for details."
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}

View File

@@ -1,53 +0,0 @@
name: Cherry Pick
on:
pull_request_target:
branches:
- master
types: ["labeled", "closed"]
jobs:
find-labels:
name: Find Cherry Pick Labels
if: |
github.event.pull_request.merged == true && (
(github.event.action == 'labeled' && startsWith(github.event.label.name, 'cherry-pick/')) ||
(github.event.action == 'closed' && contains(toJSON(github.event.pull_request.labels.*.name), 'cherry-pick/'))
)
runs-on: ubuntu-latest
outputs:
labels: ${{ steps.extract-labels.outputs.labels }}
steps:
- name: Extract cherry-pick labels
id: extract-labels
run: |
if [[ "${{ github.event.action }}" == "labeled" ]]; then
# Label was just added - use it directly
LABEL_NAME="${{ github.event.label.name }}"
VERSION="${LABEL_NAME#cherry-pick/}"
CHERRY_PICK_DATA='[{"label":"'$LABEL_NAME'","version":"'$VERSION'"}]'
else
# PR was closed - find all cherry-pick labels
CHERRY_PICK_DATA=$(echo '${{ toJSON(github.event.pull_request.labels) }}' | jq -c '[.[] | select(.name | startswith("cherry-pick/")) | {label: .name, version: (.name | sub("cherry-pick/"; ""))}]')
fi
echo "labels=$CHERRY_PICK_DATA" >> "$GITHUB_OUTPUT"
echo "Found cherry-pick data: $CHERRY_PICK_DATA"
cherry-pick:
name: Cherry Pick
needs: find-labels
if: needs.find-labels.outputs.labels != '[]'
strategy:
matrix:
include: ${{ fromJSON(needs.find-labels.outputs.labels) }}
fail-fast: false
uses: ./.github/workflows/cherry-pick-single.yml
with:
merge_commit_sha: ${{ github.event.pull_request.merge_commit_sha }}
version_number: ${{ matrix.version }}
pr_number: ${{ github.event.pull_request.number }}
pr_title: ${{ github.event.pull_request.title }}
secrets:
CHERRYPICK_APP_ID: ${{ vars.CHERRYPICK_APP_ID }}
CHERRYPICK_APP_PRIVATE_KEY: ${{ secrets.CHERRYPICK_APP_PRIVATE_KEY }}

View File

@@ -1,5 +1,5 @@
name: Integration tests
on:
on:
push:
branches:
- 'master'
@@ -13,75 +13,46 @@ on:
env:
# Golang version to use across CI steps
# renovate: datasource=golang-version packageName=golang
GOLANG_VERSION: '1.25.3'
GOLANG_VERSION: '1.20'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
cancel-in-progress: true
permissions:
contents: read
jobs:
changes:
runs-on: ubuntu-latest
outputs:
backend: ${{ steps.filter.outputs.backend_any_changed }}
frontend: ${{ steps.filter.outputs.frontend_any_changed }}
docs: ${{ steps.filter.outputs.docs_any_changed }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
id: filter
with:
# Any file which is not under docs/, ui/ or is not a markdown file is counted as a backend file
files_yaml: |
backend:
- '!ui/**'
- '!**.md'
- '!**/*.md'
- '!docs/**'
frontend:
- 'ui/**'
- Dockerfile
docs:
- 'docs/**'
check-go:
name: Ensure Go modules synchronicity
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-22.04
needs:
- changes
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Download all Go modules
run: |
go mod download
- name: Check for tidiness of go.mod and go.sum
- name: Check for tidyness of go.mod and go.sum
run: |
go mod tidy
git diff --exit-code -- .
build-go:
name: Build & cache Go code
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-22.04
needs:
- changes
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Restore go build cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -93,46 +64,40 @@ jobs:
lint-go:
permissions:
contents: read # for actions/checkout to fetch code
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
contents: read # for actions/checkout to fetch code
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
name: Lint Go code
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-22.04
needs:
- changes
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Run golangci-lint
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
uses: golangci/golangci-lint-action@639cd343e1d3b897ff35927a75193d57cfcba299 # v3.6.0
with:
# renovate: datasource=go packageName=github.com/golangci/golangci-lint versioning=regex:^v(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)?$
version: v2.5.0
args: --verbose
version: v1.51.0
args: --timeout 10m --exclude SA5011 --verbose
test-go:
name: Run unit tests for Go packages
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-22.04
needs:
- build-go
- changes
env:
GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
steps:
- name: Create checkout directory
run: mkdir -p ~/go/src/github.com/argoproj
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Create symlink in GOPATH
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
- name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Install required packages
@@ -152,7 +117,7 @@ jobs:
run: |
echo "/usr/local/bin" >> $GITHUB_PATH
- name: Restore go build cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -172,31 +137,34 @@ jobs:
go mod download
- name: Run all unit tests
run: make test-local
- name: Generate code coverage artifacts
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
name: code-coverage
path: coverage.out
- name: Generate test results artifacts
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
name: test-results
path: test-results
path: test-results/
test-go-race:
name: Run unit tests with -race for Go packages
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-22.04
needs:
- build-go
- changes
env:
GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
steps:
- name: Create checkout directory
run: mkdir -p ~/go/src/github.com/argoproj
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Create symlink in GOPATH
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
- name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Install required packages
@@ -216,7 +184,7 @@ jobs:
run: |
echo "/usr/local/bin" >> $GITHUB_PATH
- name: Restore go build cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -237,22 +205,19 @@ jobs:
- name: Run all unit tests
run: make test-race-local
- name: Generate test results artifacts
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
name: race-results
path: test-results/
codegen:
name: Check changes to generated code
if: ${{ needs.changes.outputs.backend == 'true' || needs.changes.outputs.docs == 'true'}}
runs-on: ubuntu-22.04
needs:
- changes
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Create symlink in GOPATH
@@ -295,22 +260,17 @@ jobs:
build-ui:
name: Build, test & lint UI code
# We run UI logic for backend changes so that we have a complete set of coverage documents to send to codecov.
if: ${{ needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true' }}
runs-on: ubuntu-22.04
needs:
- changes
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Setup NodeJS
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
with:
# renovate: datasource=node-version packageName=node versioning=node
node-version: '22.9.0'
node-version: '20.3.1'
- name: Restore node dependency cache
id: cache-dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with:
path: ui/node_modules
key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }}
@@ -325,130 +285,103 @@ jobs:
NODE_ENV: production
NODE_ONLINE_ENV: online
HOST_ARCH: amd64
# If we're on the master branch, set the codecov token so that we upload bundle analysis
CODECOV_TOKEN: ${{ github.ref == 'refs/heads/master' && secrets.CODECOV_TOKEN || '' }}
working-directory: ui/
- name: Run ESLint
run: yarn lint
working-directory: ui/
shellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- run: |
sudo apt-get install shellcheck
shellcheck -e SC2059 -e SC2154 -e SC2034 -e SC2016 -e SC1091 $(find . -type f -name '*.sh' | grep -v './ui/node_modules') | tee sc.log
test ! -s sc.log
analyze:
name: Process & analyze test artifacts
if: ${{ needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true' }}
runs-on: ubuntu-22.04
needs:
- test-go
- build-ui
- changes
- test-e2e
env:
sonar_secret: ${{ secrets.SONAR_TOKEN }}
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
with:
fetch-depth: 0
- name: Restore node dependency cache
id: cache-dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with:
path: ui/node_modules
key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }}
- name: Remove other node_modules directory
run: |
rm -rf ui/node_modules/argo-ui/node_modules
- name: Get e2e code coverage
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
- name: Create test-results directory
run: |
mkdir -p test-results
- name: Get code coverage artifiact
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
with:
name: e2e-code-coverage
path: e2e-code-coverage
- name: Get unit test code coverage
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
name: code-coverage
- name: Get test result artifact
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
with:
name: test-results
path: test-results
- name: combine-go-coverage
# We generate coverage reports for all Argo CD components, but only the applicationset-controller,
# app-controller, repo-server, and commit-server report contain coverage data. The other components currently
# don't shut down gracefully, so no coverage data is produced. Once those components are fixed, we can add
# references to their coverage output directories.
run: |
go tool covdata percent -i=test-results,e2e-code-coverage/applicationset-controller,e2e-code-coverage/repo-server,e2e-code-coverage/app-controller,e2e-code-coverage/commit-server -o test-results/full-coverage.out
- name: Upload code coverage information to codecov.io
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4
with:
files: test-results/full-coverage.out
fail_ci_if_error: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- name: Upload test results to Codecov
if: github.ref == 'refs/heads/master' && github.event_name == 'push' && github.repository == 'argoproj/argo-cd'
uses: codecov/test-results-action@47f89e9acb64b76debcd5ea40642d25a4adced9f # v1.1.1
with:
file: test-results/junit.xml
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
file: coverage.out
- name: Perform static code analysis using SonarCloud
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
uses: SonarSource/sonarqube-scan-action@1a6d90ebcb0e6a6b1d87e37ba693fe453195ae25 # v5.3.1
SCANNER_VERSION: 4.2.0.1873
SCANNER_PATH: /tmp/cache/scanner
OS: linux
run: |
# We do not use the provided action, because it does contain an old
# version of the scanner, and also takes time to build.
set -e
mkdir -p ${SCANNER_PATH}
export SONAR_USER_HOME=${SCANNER_PATH}/.sonar
if [[ ! -x "${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner" ]]; then
curl -Ol https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${SCANNER_VERSION}-${OS}.zip
unzip -qq -o sonar-scanner-cli-${SCANNER_VERSION}-${OS}.zip -d ${SCANNER_PATH}
fi
chmod +x ${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner
chmod +x ${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/jre/bin/java
# Explicitly set NODE_MODULES
export NODE_MODULES=${PWD}/ui/node_modules
export NODE_PATH=${PWD}/ui/node_modules
${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner
if: env.sonar_secret != ''
test-e2e:
name: Run end-to-end tests
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: oracle-vm-16cpu-64gb-x86-64
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
# latest: true means that this version mush upload the coverage report to codecov.io
# We designate the latest version because we only collect code coverage for that version.
k3s:
- version: v1.33.1
latest: true
- version: v1.32.1
latest: false
- version: v1.31.0
latest: false
- version: v1.30.4
latest: false
needs:
k3s-version: [v1.27.2, v1.26.0, v1.25.4, v1.24.3]
needs:
- build-go
- changes
env:
GOPATH: /home/ubuntu/go
ARGOCD_FAKE_IN_CLUSTER: 'true'
ARGOCD_SSH_DATA_PATH: '/tmp/argo-e2e/app/config/ssh'
ARGOCD_TLS_DATA_PATH: '/tmp/argo-e2e/app/config/tls'
ARGOCD_E2E_SSH_KNOWN_HOSTS: '../fixture/certs/ssh_known_hosts'
ARGOCD_E2E_K3S: 'true'
ARGOCD_IN_CI: 'true'
ARGOCD_E2E_APISERVER_PORT: '8088'
ARGOCD_APPLICATION_NAMESPACES: 'argocd-e2e-external,argocd-e2e-external-2'
ARGOCD_SERVER: '127.0.0.1:8088'
GOPATH: /home/runner/go
ARGOCD_FAKE_IN_CLUSTER: "true"
ARGOCD_SSH_DATA_PATH: "/tmp/argo-e2e/app/config/ssh"
ARGOCD_TLS_DATA_PATH: "/tmp/argo-e2e/app/config/tls"
ARGOCD_E2E_SSH_KNOWN_HOSTS: "../fixture/certs/ssh_known_hosts"
ARGOCD_E2E_K3S: "true"
ARGOCD_IN_CI: "true"
ARGOCD_E2E_APISERVER_PORT: "8088"
ARGOCD_APPLICATION_NAMESPACES: "argocd-e2e-external"
ARGOCD_SERVER: "127.0.0.1:8088"
GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
steps:
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
with:
large-packages: false
docker-images: false
swap-storage: false
tool-cache: false
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: GH actions workaround - Kill XSP4 process
@@ -456,24 +389,24 @@ jobs:
sudo pkill mono || true
- name: Install K3S
env:
INSTALL_K3S_VERSION: ${{ matrix.k3s.version }}+k3s1
INSTALL_K3S_VERSION: ${{ matrix.k3s-version }}+k3s1
run: |
set -x
curl -sfL https://get.k3s.io | sh -
sudo chmod -R a+rw /etc/rancher/k3s
sudo mkdir -p $HOME/.kube && sudo chown -R ubuntu $HOME/.kube
sudo mkdir -p $HOME/.kube && sudo chown -R runner $HOME/.kube
sudo k3s kubectl config view --raw > $HOME/.kube/config
sudo chown ubuntu $HOME/.kube/config
sudo chown runner $HOME/.kube/config
sudo chmod go-r $HOME/.kube/config
kubectl version
- name: Restore go build cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
- name: Add ~/go/bin to PATH
run: |
echo "/home/ubuntu/go/bin" >> $GITHUB_PATH
echo "/home/runner/go/bin" >> $GITHUB_PATH
- name: Add /usr/local/bin to PATH
run: |
echo "/usr/local/bin" >> $GITHUB_PATH
@@ -493,13 +426,13 @@ 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.43.0
docker pull ghcr.io/dexidp/dex:v2.37.0
docker pull argoproj/argo-cd-ci-builder:v1.0.0
docker pull redis:8.2.1-alpine
docker pull redis:7.0.11-alpine
- name: Create target directory for binaries in the build-process
run: |
mkdir -p dist
chown ubuntu dist
chown runner dist
- name: Run E2E server and wait for it being available
timeout-minutes: 30
run: |
@@ -508,7 +441,7 @@ jobs:
# port 8080 which is not visible in netstat -tulpen, but still there
# with a HTTP listener. We have API server listening on port 8088
# instead.
make start-e2e-local COVERAGE_ENABLED=true 2>&1 | sed -r "s/[[:cntrl:]]\[[0-9]{1,3}m//g" > /tmp/e2e-server.log &
make start-e2e-local 2>&1 | sed -r "s/[[:cntrl:]]\[[0-9]{1,3}m//g" > /tmp/e2e-server.log &
count=1
until curl -f http://127.0.0.1:8088/healthz; do
sleep 10;
@@ -522,40 +455,9 @@ jobs:
run: |
set -x
make test-e2e-local
goreman run stop-all || echo "goreman trouble"
sleep 30
- name: Upload e2e coverage report
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: e2e-code-coverage
path: /tmp/coverage
if: ${{ matrix.k3s.latest }}
- name: Upload e2e-server logs
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
name: e2e-server-k8s${{ matrix.k3s.version }}.log
name: e2e-server-k8s${{ matrix.k3s-version }}.log
path: /tmp/e2e-server.log
if: ${{ failure() }}
# workaround for status checks -- check this one job instead of each individual E2E job in the matrix
# this allows us to skip the entire matrix when it doesn't need to run while still having accurate status checks
# see:
# https://github.com/argoproj/argo-workflows/pull/12006
# https://github.com/orgs/community/discussions/9141#discussioncomment-2296809
# https://github.com/orgs/community/discussions/26822#discussioncomment-3305794
test-e2e-composite-result:
name: E2E Tests - Composite result
if: ${{ always() }}
needs:
- test-e2e
- changes
runs-on: ubuntu-22.04
steps:
- run: |
result="${{ needs.test-e2e.result }}"
# mark as successful even if skipped
if [[ $result == "success" || $result == "skipped" ]]; then
exit 0
else
exit 1
fi

View File

@@ -23,23 +23,18 @@ jobs:
actions: read # for github/codeql-action/init to get workflow details
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/autobuild to send a status report
if: github.repository == 'argoproj/argo-cd' || vars.enable_codeql
if: github.repository == 'argoproj/argo-cd'
# CodeQL runs on ubuntu-latest and windows-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
# Use correct go version. https://github.com/github/codeql-action/issues/1842#issuecomment-1704398087
- name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version-file: go.mod
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@8fcfedf57053e09257688fce7a0beeb18b1b9ae3 # v2.17.2
uses: github/codeql-action/init@8aff97f12c99086bdb92ff62ae06dbbcdf07941b # v2.1.33
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
@@ -47,7 +42,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@8fcfedf57053e09257688fce7a0beeb18b1b9ae3 # v2.17.2
uses: github/codeql-action/autobuild@8aff97f12c99086bdb92ff62ae06dbbcdf07941b # v2.1.33
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -61,4 +56,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@8fcfedf57053e09257688fce7a0beeb18b1b9ae3 # v2.17.2
uses: github/codeql-action/analyze@8aff97f12c99086bdb92ff62ae06dbbcdf07941b # v2.1.33

View File

@@ -17,9 +17,11 @@ on:
platforms:
required: true
type: string
default: linux/amd64
push:
required: true
type: boolean
default: false
target:
required: false
type: string
@@ -56,27 +58,28 @@ jobs:
image-digest: ${{ steps.image.outputs.digest }}
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.3.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
if: ${{ github.ref_type == 'tag'}}
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.3.0
if: ${{ github.ref_type != 'tag'}}
- name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1
with:
go-version: ${{ inputs.go-version }}
cache: false
- name: Install cosign
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
uses: sigstore/cosign-installer@d13028333d784fcc802b67ec924bcebe75aa0a5f # v3.1.0
with:
cosign-release: 'v2.0.0'
- uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
- uses: docker/setup-buildx-action@ecf95283f03858871ff00b787d79c419715afc34 # v2.7.0
- name: Setup tags for container image as a CSV type
run: |
@@ -103,7 +106,7 @@ jobs:
echo 'EOF' >> $GITHUB_ENV
- name: Login to Quay.io
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
with:
registry: quay.io
username: ${{ secrets.quay_username }}
@@ -111,7 +114,7 @@ jobs:
if: ${{ inputs.quay_image_name && inputs.push }}
- name: Login to GitHub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
with:
registry: ghcr.io
username: ${{ secrets.ghcr_username }}
@@ -119,7 +122,7 @@ jobs:
if: ${{ inputs.ghcr_image_name && inputs.push }}
- name: Login to dockerhub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
with:
username: ${{ secrets.docker_username }}
password: ${{ secrets.docker_password }}
@@ -132,17 +135,9 @@ jobs:
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
echo "GIT_TREE_STATE=$(if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)" >> $GITHUB_ENV
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
with:
large-packages: false
docker-images: false
swap-storage: false
tool-cache: false
- name: Build and push container image
id: image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 #v6.18.0
uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 #v4.1.1
with:
context: .
platforms: ${{ inputs.platforms }}

View File

@@ -7,7 +7,7 @@ on:
pull_request:
branches:
- master
types: [labeled, unlabeled, opened, synchronize, reopened]
types: [ labeled, unlabeled, opened, synchronize, reopened ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -25,7 +25,7 @@ jobs:
image-tag: ${{ steps.image.outputs.tag}}
platforms: ${{ steps.platforms.outputs.platforms }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Set image tag for ghcr
run: echo "tag=$(cat ./VERSION)-${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
@@ -46,14 +46,13 @@ jobs:
needs: [set-vars]
permissions:
contents: read
packages: write # for pushing packages to GHCR, which is used by cd.apps.argoproj.io to avoid polluting Quay with tags
packages: write # for pushing packages to GHCR, which is used by cd.apps.argoproj.io to avoid polluting Quay with tags
id-token: write # for creating OIDC tokens for signing.
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name != 'push' }}
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)
# renovate: datasource=golang-version packageName=golang
go-version: 1.25.3
go-version: 1.20
platforms: ${{ needs.set-vars.outputs.platforms }}
push: false
@@ -61,7 +60,7 @@ jobs:
needs: [set-vars]
permissions:
contents: read
packages: write # for pushing packages to GHCR, which is used by cd.apps.argoproj.io to avoid polluting Quay with tags
packages: write # for pushing packages to GHCR, which is used by cd.apps.argoproj.io to avoid polluting Quay with tags
id-token: write # for creating OIDC tokens for signing.
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
uses: ./.github/workflows/image-reuse.yaml
@@ -69,8 +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)
# renovate: datasource=golang-version packageName=golang
go-version: 1.25.3
go-version: 1.20
platforms: ${{ needs.set-vars.outputs.platforms }}
push: true
secrets:
@@ -88,7 +86,7 @@ jobs:
packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0
with:
image: ghcr.io/argoproj/argo-cd/argocd
digest: ${{ needs.build-and-publish.outputs.image-digest }}
@@ -101,12 +99,12 @@ jobs:
- build-and-publish
- set-vars
permissions:
contents: write # for git to push upgrade commit if not already deployed
packages: write # for pushing packages to GHCR, which is used by cd.apps.argoproj.io to avoid polluting Quay with tags
contents: write # for git to push upgrade commit if not already deployed
packages: write # for pushing packages to GHCR, which is used by cd.apps.argoproj.io to avoid polluting Quay with tags
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.3.0
- run: git clone "https://$TOKEN@github.com/argoproj/argoproj-deployments"
env:
TOKEN: ${{ secrets.TOKEN }}
@@ -116,3 +114,4 @@ jobs:
git config --global user.name 'CI'
git diff --exit-code && echo 'Already deployed' || (git commit -am 'Upgrade argocd to ${{ needs.set-vars.outputs.image-tag }}' && git push)
working-directory: argoproj-deployments/argocd

View File

@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.2.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
@@ -64,7 +64,7 @@ jobs:
git stash pop
- name: Create pull request
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 # v5.0.2
with:
commit-message: "Bump version to ${{ inputs.TARGET_VERSION }}"
title: "Bump version to ${{ inputs.TARGET_VERSION }} on ${{ inputs.TARGET_BRANCH }} branch"

View File

@@ -12,8 +12,8 @@ permissions: {}
# workflow being trigger a number of times. This limits it
# to one run per PR.
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }}
cancel-in-progress: true
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
validate:
@@ -23,7 +23,7 @@ jobs:
name: Validate PR Title
runs-on: ubuntu-latest
steps:
- uses: thehanimo/pr-title-checker@7fbfe05602bdd86f926d3fb3bccb6f3aed43bc70 # v1.4.3
- uses: thehanimo/pr-title-checker@0cf5902181e78341bb97bb06646396e5bd354b3f # v1.4.0
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
configuration_path: ".github/pr-title-checker-config.json"

View File

@@ -10,8 +10,7 @@ on:
permissions: {}
env:
# renovate: datasource=golang-version packageName=golang
GOLANG_VERSION: '1.25.3' # 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:
@@ -24,82 +23,43 @@ 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)
# renovate: datasource=golang-version packageName=golang
go-version: 1.25.3
go-version: 1.20
platforms: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
push: true
secrets:
quay_username: ${{ secrets.RELEASE_QUAY_USERNAME }}
quay_password: ${{ secrets.RELEASE_QUAY_TOKEN }}
setup-variables:
name: Setup Release Variables
if: github.repository == 'argoproj/argo-cd'
runs-on: ubuntu-22.04
outputs:
is_pre_release: ${{ steps.var.outputs.is_pre_release }}
is_latest_release: ${{ steps.var.outputs.is_latest_release }}
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup variables
id: var
run: |
set -xue
# Fetch all tag information
git fetch --prune --tags --force
LATEST_RELEASE_TAG=$(git -c 'versionsort.suffix=-rc' tag --list --sort=version:refname | grep -v '-' | tail -n1)
PRE_RELEASE=false
# Check if latest tag is a pre-release
if echo ${{ github.ref_name }} | grep -E -- '-rc[0-9]+$';then
PRE_RELEASE=true
fi
IS_LATEST=false
# Ensure latest release tag matches github.ref_name
if [[ $LATEST_RELEASE_TAG == ${{ github.ref_name }} ]];then
IS_LATEST=true
fi
echo "is_pre_release=$PRE_RELEASE" >> $GITHUB_OUTPUT
echo "is_latest_release=$IS_LATEST" >> $GITHUB_OUTPUT
argocd-image-provenance:
needs: [argocd-image]
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)
# 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@v2.1.0
with:
image: quay.io/argoproj/argocd
digest: ${{ needs.argocd-image.outputs.image-digest }}
secrets:
registry-username: ${{ secrets.RELEASE_QUAY_USERNAME }}
registry-password: ${{ secrets.RELEASE_QUAY_TOKEN }}
needs: [argocd-image]
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)
# 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.9.0
with:
image: quay.io/argoproj/argocd
digest: ${{ needs.argocd-image.outputs.image-digest }}
secrets:
registry-username: ${{ secrets.RELEASE_QUAY_USERNAME }}
registry-password: ${{ secrets.RELEASE_QUAY_TOKEN }}
goreleaser:
needs:
- setup-variables
- argocd-image
- argocd-image-provenance
permissions:
contents: write # used for uploading assets
if: github.repository == 'argoproj/argo-cd'
runs-on: ubuntu-22.04
env:
GORELEASER_MAKE_LATEST: ${{ needs.setup-variables.outputs.is_latest_release }}
outputs:
hashes: ${{ steps.hash.outputs.hashes }}
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
@@ -107,17 +67,19 @@ jobs:
- name: Fetch all tags
run: git fetch --force --tags
- name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
cache: false
- name: Set GORELEASER_PREVIOUS_TAG # Workaround, GoReleaser uses 'git-describe' to determine a previous tag. Our tags are created in release branches.
- name: Set GORELEASER_PREVIOUS_TAG # Workaround, GoReleaser uses 'git-describe' to determine a previous tag. Our tags are created in realease branches.
run: |
set -xue
GORELEASER_PREVIOUS_TAG=$(go run hack/get-previous-release/get-previous-version-for-release-notes.go ${{ github.ref_name }}) || exit 1
echo "GORELEASER_PREVIOUS_TAG=$GORELEASER_PREVIOUS_TAG" >> $GITHUB_ENV
if echo ${{ github.ref_name }} | grep -E -- '-rc1+$';then
echo "GORELEASER_PREVIOUS_TAG=$(git -c 'versionsort.suffix=-rc' tag --list --sort=version:refname | tail -n 2 | head -n 1)" >> $GITHUB_ENV
else
echo "This is not the first release on the branch, Using GoReleaser defaults"
fi
- name: Setup Golang
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Set environment variables for ldflags
id: set_ldflag
@@ -125,16 +87,8 @@ jobs:
echo "KUBECTL_VERSION=$(go list -m k8s.io/client-go | head -n 1 | rev | cut -d' ' -f1 | rev)" >> $GITHUB_ENV
echo "GIT_TREE_STATE=$(if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)" >> $GITHUB_ENV
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
with:
large-packages: false
docker-images: false
swap-storage: false
tool-cache: false
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
uses: goreleaser/goreleaser-action@336e29918d653399e599bfca99fadc1d7ffbc9f7 # v4.3.0
id: run-goreleaser
with:
version: latest
@@ -147,7 +101,7 @@ jobs:
- name: Generate subject for provenance
id: hash
env:
ARTIFACTS: '${{ steps.run-goreleaser.outputs.artifacts }}'
ARTIFACTS: "${{ steps.run-goreleaser.outputs.artifacts }}"
run: |
set -euo pipefail
@@ -166,10 +120,10 @@ 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@v2.1.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0
with:
base64-subjects: '${{ needs.goreleaser.outputs.hashes }}'
provenance-name: 'argocd-cli.intoto.jsonl'
base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
provenance-name: "argocd-cli.intoto.jsonl"
upload-assets: true
generate-sbom:
@@ -180,21 +134,20 @@ jobs:
permissions:
contents: write # Needed for release uploads
outputs:
hashes: ${{ steps.sbom-hash.outputs.hashes }}
hashes: ${{ steps.sbom-hash.outputs.hashes}}
if: github.repository == 'argoproj/argo-cd'
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.2.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1
with:
go-version: ${{ env.GOLANG_VERSION }}
cache: false
- name: Generate SBOM (spdx)
id: spdx-builder
@@ -205,7 +158,7 @@ jobs:
SIGS_BOM_VERSION: v0.2.1
# comma delimited list of project relative folders to inspect for package
# managers (gomod, yarn, npm).
PROJECT_FOLDERS: '.,./ui'
PROJECT_FOLDERS: ".,./ui"
# full qualified name of the docker image to be inspected
DOCKER_IMAGE: quay.io/argoproj/argocd:${{ github.ref_name }}
run: |
@@ -236,7 +189,7 @@ jobs:
echo "hashes=$(sha256sum /tmp/sbom.tar.gz | base64 -w0)" >> "$GITHUB_OUTPUT"
- name: Upload SBOM
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -250,16 +203,15 @@ jobs:
id-token: write # Needed for provenance signing and ID
contents: write # Needed for release uploads
if: github.repository == 'argoproj/argo-cd'
# Must be referenced 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@v2.1.0
# 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.9.0
with:
base64-subjects: '${{ needs.generate-sbom.outputs.hashes }}'
provenance-name: 'argocd-sbom.intoto.jsonl'
base64-subjects: "${{ needs.generate-sbom.outputs.hashes }}"
provenance-name: "argocd-sbom.intoto.jsonl"
upload-assets: true
post-release:
needs:
- setup-variables
- argocd-image
- goreleaser
- generate-sbom
@@ -268,11 +220,9 @@ jobs:
pull-requests: write # Needed to create PR for VERSION update.
if: github.repository == 'argoproj/argo-cd'
runs-on: ubuntu-22.04
env:
TAG_STABLE: ${{ needs.setup-variables.outputs.is_latest_release }}
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.2.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
@@ -283,6 +233,27 @@ jobs:
git config --global user.email 'ci@argoproj.com'
git config --global user.name 'CI'
- name: Check if tag is the latest version and not a pre-release
run: |
set -xue
# Fetch all tag information
git fetch --prune --tags --force
LATEST_TAG=$(git -c 'versionsort.suffix=-rc' tag --list --sort=version:refname | tail -n1)
PRE_RELEASE=false
# Check if latest tag is a pre-release
if echo $LATEST_TAG | grep -E -- '-rc[0-9]+$';then
PRE_RELEASE=true
fi
# Ensure latest tag matches github.ref_name & not a pre-release
if [[ $LATEST_TAG == ${{ github.ref_name }} ]] && [[ $PRE_RELEASE != 'true' ]];then
echo "TAG_STABLE=true" >> $GITHUB_ENV
else
echo "TAG_STABLE=false" >> $GITHUB_ENV
fi
- name: Update stable tag to latest version
run: |
git tag -f stable ${{ github.ref_name }}
@@ -294,13 +265,11 @@ jobs:
set -xue
SOURCE_TAG=${{ github.ref_name }}
VERSION_REF="${SOURCE_TAG#*v}"
COMMIT_HASH=$(git rev-parse HEAD)
if echo "$VERSION_REF" | grep -E -- '^[0-9]+\.[0-9]+\.0-rc1';then
VERSION=$(awk 'BEGIN {FS=OFS="."} {$2++; print}' <<< "${VERSION_REF%-rc1}")
echo "Updating VERSION to: $VERSION"
echo "UPDATE_VERSION=true" >> $GITHUB_ENV
echo "NEW_VERSION=$VERSION" >> $GITHUB_ENV
echo "COMMIT_HASH=$COMMIT_HASH" >> $GITHUB_ENV
else
echo "Not updating VERSION"
echo "UPDATE_VERSION=false" >> $GITHUB_ENV
@@ -309,17 +278,13 @@ jobs:
- name: Update VERSION on master branch
run: |
echo ${{ env.NEW_VERSION }} > VERSION
# Replace the 'project-release: vX.X.X-rcX' line in SECURITY-INSIGHTS.yml
sed -i "s/project-release: v.*$/project-release: v${{ env.NEW_VERSION }}/" SECURITY-INSIGHTS.yml
# Update the 'commit-hash: XXXXXXX' line in SECURITY-INSIGHTS.yml
sed -i "s/commit-hash: .*/commit-hash: ${{ env.COMMIT_HASH }}/" SECURITY-INSIGHTS.yml
if: ${{ env.UPDATE_VERSION == 'true' }}
- name: Create PR to update VERSION on master branch
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 # v5.0.2
with:
commit-message: Bump version in master
title: 'chore: Bump version in master'
title: "chore: Bump version in master"
body: All images built from master should indicate which version we are on track for.
signoff: true
branch: update-version

View File

@@ -1,39 +0,0 @@
name: Renovate
on:
schedule:
- cron: '0 * * * *'
workflow_dispatch: {}
permissions:
contents: read
jobs:
renovate:
runs-on: ubuntu-latest
if: github.repository == 'argoproj/argo-cd'
steps:
- name: Get token
id: get_token
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
with:
app-id: ${{ vars.RENOVATE_APP_ID }}
private-key: ${{ secrets.RENOVATE_APP_PRIVATE_KEY }}
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 5.0.0
# Some codegen commands require Go to be setup
- name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
# renovate: datasource=golang-version packageName=golang
go-version: 1.25.3
- name: Self-hosted Renovate
uses: renovatebot/github-action@ea850436a5fe75c0925d583c7a02c60a5865461d #43.0.20
with:
configurationFile: .github/configs/renovate-config.js
token: '${{ steps.get_token.outputs.token }}'
env:
LOG_LEVEL: 'debug'
RENOVATE_REPOSITORIES: '${{ github.repository }}'

View File

@@ -30,12 +30,12 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031 # v2.2.0
with:
results_file: results.sarif
results_format: sarif
@@ -54,7 +54,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
name: SARIF file
path: results.sarif
@@ -62,6 +62,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@8fcfedf57053e09257688fce7a0beeb18b1b9ae3 # v2.17.2
uses: github/codeql-action/upload-sarif@3ebbd71c74ef574dbc558c82f70e52732c8b44fe # v2.2.1
with:
sarif_file: results.sarif

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Build reports

7
.gitignore vendored
View File

@@ -1,14 +1,12 @@
.vscode/
.idea/
.DS_Store
.run/
vendor/
dist/*
ui/dist/app/*
!ui/dist/app/gitkeep
site/
*.iml
.tilt-bin/
# delve debug binaries
cmd/**/debug
debug.test
@@ -20,15 +18,10 @@ node_modules/
.kube/
./test/cmp/*.sock
.envrc.remote
.mirrord/
.*.swp
rerunreport.txt
# ignore built binaries
cmd/argocd/argocd
cmd/argocd-application-controller/argocd-application-controller
cmd/argocd-repo-server/argocd-repo-server
cmd/argocd-server/argocd-server
# ignore generated `.argocd-helm-dep-up` marker file; this should not be committed to git
reposerver/repository/testdata/**/.argocd-helm-dep-up

21
.gitpod.Dockerfile vendored Normal file
View File

@@ -0,0 +1,21 @@
FROM gitpod/workspace-full@sha256:d5787229cd062aceae91109f1690013d3f25062916492fb7f444d13de3186178
USER root
RUN curl -o /usr/local/bin/kubectl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" && \
chmod +x /usr/local/bin/kubectl
RUN curl -L https://github.com/kubernetes-sigs/kubebuilder/releases/download/v2.3.1/kubebuilder_2.3.1_$(go env GOOS)_$(go env GOARCH).tar.gz | \
tar -xz -C /tmp/ && mv /tmp/kubebuilder_2.3.1_$(go env GOOS)_$(go env GOARCH) /usr/local/kubebuilder
ENV GOCACHE=/go-build-cache
RUN apt-get install redis-server -y
RUN go install github.com/mattn/goreman@latest
RUN chown -R gitpod:gitpod /go-build-cache
USER gitpod
ENV ARGOCD_REDIS_LOCAL=true
ENV KUBECONFIG=/tmp/kubeconfig

6
.gitpod.yml Normal file
View File

@@ -0,0 +1,6 @@
image:
file: .gitpod.Dockerfile
tasks:
- init: make mod-download-local dep-ui-local && GO111MODULE=off go install github.com/mattn/goreman@latest
command: make start-test-k8s

View File

@@ -1,253 +0,0 @@
formatters:
enable:
- gofumpt
- goimports
settings:
goimports:
local-prefixes:
- github.com/argoproj/argo-cd/v3
issues:
max-issues-per-linter: 0
max-same-issues: 0
linters:
enable:
- errorlint
- exptostd
- gocritic
- gomodguard
- govet
- importas
- misspell
- noctx
- perfsprint
- revive
- staticcheck
- testifylint
- thelper
- tparallel
- unparam
- usestdlibvars
- usetesting
- whitespace
exclusions:
rules:
- linters:
- unparam
path: (.+)_test\.go
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
warn-unused: true
settings:
gocritic:
enable-all: true
# Most of these should probably be enabled one-by-one.
disabled-checks:
- appendAssign
- appendCombine # Leave disabled, multi-line assigns can be more readable.
- assignOp # Leave disabled, assign operations can be more confusing than helpful.
- commentedOutCode
- deferInLoop
- exitAfterDefer
- hugeParam
- importShadow
- paramTypeCombine # Leave disabled, there are too many failures to be worth fixing.
- rangeValCopy
- tooManyResultsChecker
- unnamedResult
- whyNoLint
gomodguard:
blocked:
modules:
- github.com/golang-jwt/jwt/v4:
recommendations:
- github.com/golang-jwt/jwt/v5
- github.com/imdario/mergo:
recommendations:
- dario.cat/mergo
reason: '`github.com/imdario/mergo` has been renamed.'
- github.com/pkg/errors:
recommendations:
- errors
govet:
disable:
- fieldalignment
- shadow
enable-all: true
importas:
alias:
- pkg: github.com/golang-jwt/jwt/v5
alias: jwtgo
- pkg: k8s.io/api/apps/v1
alias: appsv1
- pkg: k8s.io/api/core/v1
alias: corev1
- pkg: k8s.io/api/rbac/v1
alias: rbacv1
- pkg: k8s.io/apimachinery/pkg/api/errors
alias: apierrors
- pkg: k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1
alias: apiextensionsv1
- pkg: k8s.io/apimachinery/pkg/apis/meta/v1
alias: metav1
- pkg: k8s.io/client-go/informers/core/v1
alias: informersv1
- pkg: errors
alias: stderrors
- pkg: github.com/argoproj/argo-cd/v3/util/io
alias: utilio
nolintlint:
require-specific: true
perfsprint:
# Optimizes even if it requires an int or uint type cast.
int-conversion: true
# Optimizes into `err.Error()` even if it is only equivalent for non-nil errors.
err-error: true
# Optimizes `fmt.Errorf`.
errorf: true
# Optimizes `fmt.Sprintf` with only one argument.
sprintf1: true
# Optimizes into strings concatenation.
strconcat: true
revive:
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md
rules:
- name: bool-literal-in-expr
- name: blank-imports
disabled: true
- name: context-as-argument
arguments:
- allowTypesBefore: '*testing.T,testing.TB'
- name: context-keys-type
disabled: true
- name: dot-imports
disabled: true
- name: duplicated-imports
- name: early-return
arguments:
- preserveScope
- name: empty-block
disabled: true
- name: error-naming
disabled: true
- name: error-return
- name: error-strings
disabled: true
- name: errorf
- name: identical-branches
- name: if-return
- name: increment-decrement
- name: indent-error-flow
arguments:
- preserveScope
- name: modifies-parameter
- name: optimize-operands-order
- name: range
- name: receiver-naming
- name: redefines-builtin-id
disabled: true
- name: redundant-import-alias
- name: superfluous-else
arguments:
- preserveScope
- name: time-equal
- name: time-naming
disabled: true
- name: unexported-return
disabled: true
- name: unnecessary-stmt
- name: unreachable-code
- name: unused-parameter
- name: use-any
- name: useless-break
- name: var-declaration
- name: var-naming
arguments:
- - ID
- - VM
- - skipPackageNameChecks: true
upperCaseConst: true
staticcheck:
checks:
- all
- -SA5011
- -ST1003
- -ST1016
testifylint:
enable-all: true
disable:
- go-require
unused:
field-writes-are-uses: false
exported-fields-are-used: false
usetesting:
os-mkdir-temp: false
output:
show-stats: false
version: "2"

View File

@@ -1,5 +1,3 @@
version: 2
project_name: argocd
before:
@@ -16,16 +14,16 @@ builds:
flags:
- -v
ldflags:
- -X github.com/argoproj/argo-cd/v3/common.version={{ .Version }}
- -X github.com/argoproj/argo-cd/v3/common.buildDate={{ .Date }}
- -X github.com/argoproj/argo-cd/v3/common.gitCommit={{ .FullCommit }}
- -X github.com/argoproj/argo-cd/v3/common.gitTreeState={{ .Env.GIT_TREE_STATE }}
- -X github.com/argoproj/argo-cd/v3/common.kubectlVersion={{ .Env.KUBECTL_VERSION }}
- -X github.com/argoproj/argo-cd/v2/common.version={{ .Version }}
- -X github.com/argoproj/argo-cd/v2/common.buildDate={{ .Date }}
- -X github.com/argoproj/argo-cd/v2/common.gitCommit={{ .FullCommit }}
- -X github.com/argoproj/argo-cd/v2/common.gitTreeState={{ .Env.GIT_TREE_STATE }}
- -X github.com/argoproj/argo-cd/v2/common.kubectlVersion={{ .Env.KUBECTL_VERSION }}
- -extldflags="-static"
goos:
- linux
- windows
- darwin
- windows
goarch:
- amd64
- arm64
@@ -45,18 +43,17 @@ builds:
archives:
- id: argocd-archive
ids:
- argocd-cli
builds:
- argocd-cli
name_template: |-
{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}
formats: [binary]
format: binary
checksum:
name_template: 'cli_checksums.txt'
algorithm: sha256
release:
make_latest: '{{ .Env.GORELEASER_MAKE_LATEST }}'
prerelease: auto
draft: false
header: |
@@ -80,28 +77,25 @@ release:
All Argo CD container images are signed by cosign. A Provenance is generated for container images and CLI binaries which meet the SLSA Level 3 specifications. See the [documentation](https://argo-cd.readthedocs.io/en/stable/operator-manual/signed-release-assets) on how to verify.
## Release Notes Blog Post
For a detailed breakdown of the key changes and improvements in this release, check out the [official blog post](https://blog.argoproj.io/argo-cd-v3-0-release-candidate-a0b933f4e58f)
## Upgrading
If upgrading from a different minor version, be sure to read the [upgrading](https://argo-cd.readthedocs.io/en/stable/operator-manual/upgrading/overview/) documentation.
footer: |
**Full Changelog**: https://github.com/argoproj/argo-cd/compare/{{ .PreviousTag }}...{{ .Tag }}
<a href="https://argoproj.github.io/cd/"><img src="https://raw.githubusercontent.com/argoproj/argo-site/master/content/pages/cd/gitops-cd.png" width="25%" ></a>
snapshot: #### To be removed for PR
version_template: '2.6.0'
name_template: "2.6.0"
changelog:
use: github
use:
github
sort: asc
abbrev: 0
groups: # Regex use RE2 syntax as defined here: https://github.com/google/re2/wiki/Syntax.
- title: 'Breaking Changes'
regexp: '^.*?(\([[:word:]]+\))??!:.+$'
order: 0
- title: 'Features'
regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
order: 100
@@ -120,5 +114,8 @@ changelog:
exclude:
- '^test:'
- '^.*?Bump(\([[:word:]]+\))?.+$'
- '^.*?\[Bot\](\([[:word:]]+\))?.+$'
- '^.*?[Bot](\([[:word:]]+\))?.+$'
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json

View File

@@ -1,92 +0,0 @@
dir: '{{.InterfaceDir}}/mocks'
filename: '{{.InterfaceName}}.go'
packages:
github.com/argoproj/argo-cd/v3/applicationset/generators:
interfaces:
Generator: {}
github.com/argoproj/argo-cd/v3/applicationset/services:
interfaces:
Repos: {}
github.com/argoproj/argo-cd/v3/applicationset/services/scm_provider:
interfaces:
AWSCodeCommitClient: {}
AWSTaggingClient: {}
AzureDevOpsClientFactory: {}
github.com/argoproj/argo-cd/v3/applicationset/utils:
interfaces:
Renderer: {}
github.com/argoproj/argo-cd/v3/commitserver/apiclient:
interfaces:
CommitServiceClient: {}
github.com/argoproj/argo-cd/v3/commitserver/commit:
interfaces:
RepoClientFactory: {}
github.com/argoproj/argo-cd/v3/controller/cache:
interfaces:
LiveStateCache: {}
github.com/argoproj/argo-cd/v3/controller/hydrator:
interfaces:
Dependencies: {}
RepoGetter: {}
github.com/argoproj/argo-cd/v3/pkg/apiclient/cluster:
interfaces:
ClusterServiceServer: {}
github.com/argoproj/argo-cd/v3/pkg/apiclient/session:
interfaces:
SessionServiceClient: {}
SessionServiceServer: {}
github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned/typed/application/v1alpha1:
interfaces:
AppProjectInterface: {}
github.com/argoproj/argo-cd/v3/reposerver/apiclient:
interfaces:
RepoServerServiceClient: {}
RepoServerService_GenerateManifestWithFilesClient: {}
github.com/argoproj/argo-cd/v3/server/application:
interfaces:
Broadcaster: {}
github.com/argoproj/argo-cd/v3/server/extension:
interfaces:
ApplicationGetter: {}
ExtensionMetricsRegistry: {}
ProjectGetter: {}
RbacEnforcer: {}
SettingsGetter: {}
UserGetter: {}
github.com/argoproj/argo-cd/v3/util/db:
interfaces:
ArgoDB: {}
RepoCredsDB: {}
github.com/argoproj/argo-cd/v3/util/git:
interfaces:
Client: {}
github.com/argoproj/argo-cd/v3/util/helm:
interfaces:
Client: {}
github.com/argoproj/argo-cd/v3/util/io:
interfaces:
TempPaths: {}
github.com/argoproj/argo-cd/v3/util/notification/argocd:
interfaces:
Service: {}
github.com/argoproj/argo-cd/v3/util/oci:
interfaces:
Client: {}
github.com/argoproj/argo-cd/v3/util/workloadidentity:
interfaces:
TokenProvider: {}
github.com/argoproj/gitops-engine/pkg/cache:
interfaces:
ClusterCache: {}
github.com/argoproj/gitops-engine/pkg/diff:
interfaces:
ServerSideDryRunner: {}
github.com/microsoft/azure-devops-go-api/azuredevops/v7/git:
config:
dir: applicationset/services/scm_provider/azure_devops/git/mocks
interfaces:
Client: {}
pkgname: mocks
structname: '{{.InterfaceName}}'
template-data:
unroll-variadic: true

View File

@@ -2,11 +2,10 @@ version: 2
formats: all
mkdocs:
fail_on_warning: false
configuration: mkdocs.yml
python:
install:
- requirements: docs/requirements.txt
build:
os: "ubuntu-22.04"
tools:
python: "3.12"
python: "3.7"

View File

@@ -1,20 +0,0 @@
# All
** @argoproj/argocd-approvers
# Docs
/docs/** @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
/USERS.md @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
/README.md @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
/mkdocs.yml @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
# CI
/.codecov.yml @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
/.github/** @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
/.goreleaser.yaml @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
/sonar-project.properties @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
# CLI
/cmd/argocd/** @argoproj/argocd-approvers @argoproj/argocd-approvers-cli
/cmd/main.go @argoproj/argocd-approvers @argoproj/argocd-approvers-cli
# Also include @argoproj/argocd-approvers-docs to avoid requiring CLI approvers for docs-only PRs.
/docs/operator-manual/ @argoproj/argocd-approvers @argoproj/argocd-approvers-docs @argoproj/argocd-approvers-cli

View File

@@ -1 +0,0 @@
Please refer to [the Contribution Guide](https://argo-cd.readthedocs.io/en/latest/developer-guide/code-contributions/)

View File

@@ -1,14 +1,12 @@
ARG BASE_IMAGE=docker.io/library/ubuntu:25.04@sha256:27771fb7b40a58237c98e8d3e6b9ecdd9289cec69a857fccfb85ff36294dac20
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.25.3@sha256:6bac879c5b77e0fc9c556a5ed8920e89dab1709bd510a854903509c828f67f96 AS builder
FROM docker.io/library/golang:1.20.10@sha256:ed6c4a5918b0a1ffb97970f6493d742dc5c7ebf3ccbd417c215d52830b57b994 AS builder
WORKDIR /tmp
RUN echo 'deb http://archive.debian.org/debian buster-backports main' >> /etc/apt/sources.list
RUN echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list
RUN apt-get update && apt-get install --no-install-recommends -y \
openssh-server \
@@ -25,10 +23,12 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
WORKDIR /tmp
COPY hack/install.sh hack/tool-versions.sh ./
COPY hack/installers installers
RUN ./install.sh helm && \
RUN ./install.sh helm-linux && \
INSTALL_PATH=/usr/local/bin ./install.sh kustomize
####################################################################################################
@@ -40,8 +40,8 @@ LABEL org.opencontainers.image.source="https://github.com/argoproj/argo-cd"
USER root
ENV ARGOCD_USER_ID=999 \
DEBIAN_FRONTEND=noninteractive
ENV ARGOCD_USER_ID=999
ENV DEBIAN_FRONTEND=noninteractive
RUN groupadd -g $ARGOCD_USER_ID argocd && \
useradd -r -u $ARGOCD_USER_ID -g argocd argocd && \
@@ -51,17 +51,15 @@ RUN groupadd -g $ARGOCD_USER_ID argocd && \
apt-get update && \
apt-get dist-upgrade -y && \
apt-get install -y \
git git-lfs tini gpg tzdata connect-proxy && \
git git-lfs tini gpg tzdata && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY hack/gpg-wrapper.sh \
hack/git-verify-wrapper.sh \
entrypoint.sh \
/usr/local/bin/
COPY hack/gpg-wrapper.sh /usr/local/bin/gpg-wrapper.sh
COPY hack/git-verify-wrapper.sh /usr/local/bin/git-verify-wrapper.sh
COPY --from=builder /usr/local/bin/helm /usr/local/bin/helm
COPY --from=builder /usr/local/bin/kustomize /usr/local/bin/kustomize
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
# keep uid_entrypoint.sh for backward compatibility
RUN ln -s /usr/local/bin/entrypoint.sh /usr/local/bin/uid_entrypoint.sh
@@ -85,7 +83,7 @@ WORKDIR /home/argocd
####################################################################################################
# Argo CD UI stage
####################################################################################################
FROM --platform=$BUILDPLATFORM docker.io/library/node:23.0.0@sha256:e643c0b70dca9704dff42e12b17f5b719dbe4f95e6392fc2dfa0c5f02ea8044d AS argocd-ui
FROM --platform=$BUILDPLATFORM docker.io/library/node:20.3.1@sha256:2f0b0c15f97441defa812268ee943bbfaaf666ea6cf7cac62ee3f127906b35c6 AS argocd-ui
WORKDIR /src
COPY ["ui/package.json", "ui/yarn.lock", "./"]
@@ -103,25 +101,23 @@ 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.25.3@sha256:6bac879c5b77e0fc9c556a5ed8920e89dab1709bd510a854903509c828f67f96 AS argocd-build
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.20.10@sha256:ed6c4a5918b0a1ffb97970f6493d742dc5c7ebf3ccbd417c215d52830b57b994 AS argocd-build
WORKDIR /go/src/github.com/argoproj/argo-cd
COPY go.* ./
RUN mkdir -p gitops-engine
COPY gitops-engine/go.* ./gitops-engine
RUN go mod download
# Perform the build
COPY . .
COPY --from=argocd-ui /src/dist/app /go/src/github.com/argoproj/argo-cd/ui/dist/app
ARG TARGETOS \
TARGETARCH
ARG TARGETOS
ARG TARGETARCH
# These build args are optional; if not specified the defaults will be taken from the Makefile
ARG GIT_TAG \
BUILD_DATE \
GIT_TREE_STATE \
GIT_COMMIT
ARG GIT_TAG
ARG BUILD_DATE
ARG GIT_TREE_STATE
ARG GIT_COMMIT
RUN GIT_COMMIT=$GIT_COMMIT \
GIT_TREE_STATE=$GIT_TREE_STATE \
GIT_TAG=$GIT_TAG \
@@ -134,7 +130,6 @@ RUN GIT_COMMIT=$GIT_COMMIT \
# Final image
####################################################################################################
FROM argocd-base
ENTRYPOINT ["/usr/bin/tini", "--"]
COPY --from=argocd-build /go/src/github.com/argoproj/argo-cd/dist/argocd* /usr/local/bin/
USER root
@@ -145,7 +140,7 @@ RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-server && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-dex && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-notifications && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-applicationset-controller && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-k8s-auth && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-commit-server
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-k8s-auth
USER $ARGOCD_USER_ID
ENTRYPOINT ["/usr/bin/tini", "--"]

View File

@@ -1,62 +0,0 @@
FROM docker.io/library/golang:1.25.3@sha256:6bac879c5b77e0fc9c556a5ed8920e89dab1709bd510a854903509c828f67f96
ENV DEBIAN_FRONTEND=noninteractive
RUN echo 'deb http://archive.debian.org/debian buster-backports main' >> /etc/apt/sources.list
RUN apt-get update && apt-get install --no-install-recommends -y \
curl \
openssh-server \
nginx \
unzip \
fcgiwrap \
git \
git-lfs \
make \
wget \
gcc \
sudo \
zip \
tini \
gpg \
tzdata \
connect-proxy
RUN go install github.com/go-delve/delve/cmd/dlv@latest
COPY hack/install.sh hack/tool-versions.sh ./
COPY hack/installers installers
RUN ./install.sh helm && \
INSTALL_PATH=/usr/local/bin ./install.sh kustomize
COPY hack/gpg-wrapper.sh \
hack/git-verify-wrapper.sh \
entrypoint.sh \
/usr/local/bin/
# support for mounting configuration from a configmap
WORKDIR /app/config/ssh
RUN touch ssh_known_hosts && \
ln -s /app/config/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts
WORKDIR /app/config
RUN mkdir -p tls && \
mkdir -p gpg/source && \
mkdir -p gpg/keys
COPY .tilt-bin/argocd_linux /usr/local/bin/argocd
RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-server && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-repo-server && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-application-controller && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-dex && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-notifications && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-applicationset-controller && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-commit-server
# directory for Tilt restart file
RUN mkdir -p /tilt
# overridden by Tiltfile
ENTRYPOINT ["/usr/bin/tini", "-s", "--", "dlv", "exec", "--continue", "--accept-multiclient", "--headless", "--listen=:2345", "--api-version=2"]

View File

@@ -1,9 +0,0 @@
FROM node:20
WORKDIR /app/ui
COPY ui /app/ui
RUN yarn install
ENTRYPOINT ["yarn", "start"]

194
Makefile
View File

@@ -1,19 +1,9 @@
PACKAGE=github.com/argoproj/argo-cd/v3/common
PACKAGE=github.com/argoproj/argo-cd/v2/common
CURRENT_DIR=$(shell pwd)
DIST_DIR=${CURRENT_DIR}/dist
CLI_NAME=argocd
BIN_NAME=argocd
UNAME_S:=$(shell uname)
IS_DARWIN:=$(if $(filter Darwin, $(UNAME_S)),true,false)
# When using OSX/Darwin, you might need to enable CGO for local builds
DEFAULT_CGO_FLAG:=0
ifeq ($(IS_DARWIN),true)
DEFAULT_CGO_FLAG:=1
endif
CGO_FLAG?=${DEFAULT_CGO_FLAG}
GEN_RESOURCES_CLI_NAME=argocd-resources-gen
HOST_OS:=$(shell go env GOOS)
@@ -32,32 +22,14 @@ KUBECTL_VERSION=$(shell go list -m k8s.io/client-go | head -n 1 | rev | cut -d'
GOPATH?=$(shell if test -x `which go`; then go env GOPATH; else echo "$(HOME)/go"; fi)
GOCACHE?=$(HOME)/.cache/go-build
# Docker command to use
DOCKER?=docker
ifeq ($(DOCKER),podman)
PODMAN_ARGS=--userns keep-id
else
PODMAN_ARGS=
endif
DOCKER_SRCDIR?=$(GOPATH)/src
DOCKER_WORKDIR?=/go/src/github.com/argoproj/argo-cd
# Allows you to control which Docker network the test-util containers attach to.
# This is particularly useful if you are running Kubernetes in Docker (e.g., k3d)
# and want the test containers to reach the Kubernetes API via an already-existing Docker network.
DOCKER_NETWORK ?= default
ifneq ($(DOCKER_NETWORK),default)
DOCKER_NETWORK_ARG := --network $(DOCKER_NETWORK)
else
DOCKER_NETWORK_ARG :=
endif
ARGOCD_PROCFILE?=Procfile
# pointing to python 3.7 to match https://github.com/argoproj/argo-cd/blob/master/.readthedocs.yml
MKDOCS_DOCKER_IMAGE?=python:3.7-alpine
# Strict mode has been disabled in latest versions of mkdocs-material.
# Thus pointing to the older image of mkdocs-material matching the version used by argo-cd.
MKDOCS_DOCKER_IMAGE?=squidfunk/mkdocs-material:4.1.1
MKDOCS_RUN_ARGS?=
# Configuration for building argocd-test-tools image
@@ -77,7 +49,7 @@ ARGOCD_E2E_DEX_PORT?=5556
ARGOCD_E2E_YARN_HOST?=localhost
ARGOCD_E2E_DISABLE_AUTH?=
ARGOCD_E2E_TEST_TIMEOUT?=90m
ARGOCD_E2E_TEST_TIMEOUT?=45m
ARGOCD_IN_CI?=false
ARGOCD_TEST_E2E?=true
@@ -104,7 +76,7 @@ SUDO?=
# Runs any command in the argocd-test-utils container in server mode
# Server mode container will start with uid 0 and drop privileges during runtime
define run-in-test-server
$(SUDO) $(DOCKER) run --rm -it \
$(SUDO) docker run --rm -it \
--name argocd-test-server \
-u $(CONTAINER_UID):$(CONTAINER_GID) \
-e USER_ID=$(CONTAINER_UID) \
@@ -124,19 +96,18 @@ define run-in-test-server
-v ${GOPATH}/pkg/mod:/go/pkg/mod${VOLUME_MOUNT} \
-v ${GOCACHE}:/tmp/go-build-cache${VOLUME_MOUNT} \
-v ${HOME}/.kube:/home/user/.kube${VOLUME_MOUNT} \
-v /tmp:/tmp${VOLUME_MOUNT} \
-w ${DOCKER_WORKDIR} \
-p ${ARGOCD_E2E_APISERVER_PORT}:8080 \
-p 4000:4000 \
-p 5000:5000 \
$(DOCKER_NETWORK_ARG)\
$(PODMAN_ARGS) \
$(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG) \
bash -c "$(1)"
endef
# Runs any command in the argocd-test-utils container in client mode
define run-in-test-client
$(SUDO) $(DOCKER) run --rm -it \
$(SUDO) docker run --rm -it \
--name argocd-test-client \
-u $(CONTAINER_UID):$(CONTAINER_GID) \
-e HOME=/home/user \
@@ -149,16 +120,15 @@ define run-in-test-client
-v ${GOPATH}/pkg/mod:/go/pkg/mod${VOLUME_MOUNT} \
-v ${GOCACHE}:/tmp/go-build-cache${VOLUME_MOUNT} \
-v ${HOME}/.kube:/home/user/.kube${VOLUME_MOUNT} \
-v /tmp:/tmp${VOLUME_MOUNT} \
-w ${DOCKER_WORKDIR} \
$(DOCKER_NETWORK_ARG)\
$(PODMAN_ARGS) \
$(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG) \
bash -c "$(1)"
endef
#
define exec-in-test-server
$(SUDO) $(DOCKER) exec -it -u $(CONTAINER_UID):$(CONTAINER_GID) -e ARGOCD_E2E_RECORD=$(ARGOCD_E2E_RECORD) -e ARGOCD_E2E_K3S=$(ARGOCD_E2E_K3S) argocd-test-server $(1)
$(SUDO) docker exec -it -u $(CONTAINER_UID):$(CONTAINER_GID) -e ARGOCD_E2E_RECORD=$(ARGOCD_E2E_RECORD) -e ARGOCD_E2E_K3S=$(ARGOCD_E2E_K3S) argocd-test-server $(1)
endef
PATH:=$(PATH):$(PWD)/hack
@@ -167,23 +137,12 @@ PATH:=$(PATH):$(PWD)/hack
DOCKER_PUSH?=false
IMAGE_NAMESPACE?=
# perform static compilation
DEFAULT_STATIC_BUILD:=true
ifeq ($(IS_DARWIN),true)
DEFAULT_STATIC_BUILD:=false
endif
STATIC_BUILD?=${DEFAULT_STATIC_BUILD}
STATIC_BUILD?=true
# build development images
DEV_IMAGE?=false
ARGOCD_GPG_ENABLED?=true
ARGOCD_E2E_APISERVER_PORT?=8080
ifeq (${COVERAGE_ENABLED}, true)
# We use this in the cli-local target to enable code coverage for e2e tests.
COVERAGE_FLAG=-cover
else
COVERAGE_FLAG=
endif
override LDFLAGS += \
-X ${PACKAGE}.version=${VERSION} \
-X ${PACKAGE}.buildDate=${BUILD_DATE} \
@@ -216,25 +175,29 @@ endif
.PHONY: all
all: cli image
.PHONY: mockgen
mockgen:
./hack/generate-mock.sh
# We have some legacy requirements for being checked out within $GOPATH.
# The ensure-gopath target can be used as dependency to ensure we are running
# within these boundaries.
.PHONY: ensure-gopath
ensure-gopath:
ifneq ("$(PWD)","$(LEGACY_PATH)")
@echo "Due to legacy requirements for codegen, repository needs to be checked out within \$$GOPATH"
@echo "Location of this repo should be '$(LEGACY_PATH)' but is '$(PWD)'"
@exit 1
endif
.PHONY: gogen
gogen:
gogen: ensure-gopath
export GO111MODULE=off
go generate ./...
go generate ./util/argo/...
.PHONY: protogen
protogen: mod-vendor-local protogen-fast
.PHONY: protogen-fast
protogen-fast:
protogen: ensure-gopath mod-vendor-local
export GO111MODULE=off
./hack/generate-proto.sh
.PHONY: openapigen
openapigen:
openapigen: ensure-gopath
export GO111MODULE=off
./hack/update-openapi.sh
@@ -249,29 +212,19 @@ notification-docs:
.PHONY: clientgen
clientgen:
clientgen: ensure-gopath
export GO111MODULE=off
./hack/update-codegen.sh
.PHONY: clidocsgen
clidocsgen:
clidocsgen: ensure-gopath
go run tools/cmd-docs/main.go
.PHONY: actionsdocsgen
actionsdocsgen:
hack/generate-actions-list.sh
.PHONY: resourceiconsgen
resourceiconsgen:
hack/generate-icons-typescript.sh
.PHONY: codegen-local
codegen-local: mod-vendor-local mockgen gogen protogen clientgen openapigen clidocsgen actionsdocsgen resourceiconsgen manifests-local notification-docs notification-catalog
codegen-local: ensure-gopath mod-vendor-local gogen protogen clientgen openapigen clidocsgen manifests-local notification-docs notification-catalog
rm -rf vendor/
.PHONY: codegen-local-fast
codegen-local-fast: mockgen gogen protogen-fast clientgen openapigen clidocsgen manifests-local notification-docs notification-catalog
.PHONY: codegen
codegen: test-tools-image
$(call run-in-test-client,make codegen-local)
@@ -282,11 +235,11 @@ cli: test-tools-image
.PHONY: cli-local
cli-local: clean-debug
CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -gcflags="all=-N -l" $(COVERAGE_FLAG) -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${CLI_NAME} ./cmd
CGO_ENABLED=0 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${CLI_NAME} ./cmd
.PHONY: gen-resources-cli-local
gen-resources-cli-local: clean-debug
CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${GEN_RESOURCES_CLI_NAME} ./hack/gen-resources/cmd
CGO_ENABLED=0 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${GEN_RESOURCES_CLI_NAME} ./hack/gen-resources/cmd
.PHONY: release-cli
release-cli: clean-debug build-ui
@@ -301,8 +254,8 @@ release-cli: clean-debug build-ui
.PHONY: test-tools-image
test-tools-image:
ifndef SKIP_TEST_TOOLS_IMAGE
$(SUDO) $(DOCKER) build --build-arg UID=$(CONTAINER_UID) -t $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE) -f test/container/Dockerfile .
$(SUDO) $(DOCKER) tag $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE) $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG)
$(SUDO) docker build --build-arg UID=$(CONTAINER_UID) -t $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE) -f test/container/Dockerfile .
$(SUDO) docker tag $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE) $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG)
endif
.PHONY: manifests-local
@@ -316,25 +269,25 @@ manifests: test-tools-image
# consolidated binary for cli, util, server, repo-server, controller
.PHONY: argocd-all
argocd-all: clean-debug
CGO_ENABLED=${CGO_FLAG} GOOS=${GOOS} GOARCH=${GOARCH} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${BIN_NAME} ./cmd
CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${BIN_NAME} ./cmd
.PHONY: server
server: clean-debug
CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-server ./cmd
CGO_ENABLED=0 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-server ./cmd
.PHONY: repo-server
repo-server:
CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-repo-server ./cmd
CGO_ENABLED=0 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-repo-server ./cmd
.PHONY: controller
controller:
CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-application-controller ./cmd
CGO_ENABLED=0 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-application-controller ./cmd
.PHONY: build-ui
build-ui:
DOCKER_BUILDKIT=1 $(DOCKER) build -t argocd-ui --platform=$(TARGET_ARCH) --target argocd-ui .
DOCKER_BUILDKIT=1 docker build -t argocd-ui --platform=$(TARGET_ARCH) --target argocd-ui .
find ./ui/dist -type f -not -name gitkeep -delete
$(DOCKER) run -v ${CURRENT_DIR}/ui/dist/app:/tmp/app --rm -t argocd-ui sh -c 'cp -r ./dist/app/* /tmp/app/'
docker run -v ${CURRENT_DIR}/ui/dist/app:/tmp/app --rm -t argocd-ui sh -c 'cp -r ./dist/app/* /tmp/app/'
.PHONY: image
ifeq ($(DEV_IMAGE), true)
@@ -343,29 +296,29 @@ ifeq ($(DEV_IMAGE), true)
# the dist directory is under .dockerignore.
IMAGE_TAG="dev-$(shell git describe --always --dirty)"
image: build-ui
DOCKER_BUILDKIT=1 $(DOCKER) build --platform=$(TARGET_ARCH) -t argocd-base --target argocd-base .
CGO_ENABLED=${CGO_FLAG} GOOS=linux GOARCH=amd64 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd ./cmd
DOCKER_BUILDKIT=1 docker build --platform=$(TARGET_ARCH) -t argocd-base --target argocd-base .
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd ./cmd
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-server
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-application-controller
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-repo-server
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-cmp-server
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-dex
cp Dockerfile.dev dist
DOCKER_BUILDKIT=1 $(DOCKER) build --platform=$(TARGET_ARCH) -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) -f dist/Dockerfile.dev dist
DOCKER_BUILDKIT=1 docker build --platform=$(TARGET_ARCH) -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) -f dist/Dockerfile.dev dist
else
image:
DOCKER_BUILDKIT=1 $(DOCKER) build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) --platform=$(TARGET_ARCH) .
DOCKER_BUILDKIT=1 docker build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) --platform=$(TARGET_ARCH) .
endif
@if [ "$(DOCKER_PUSH)" = "true" ] ; then $(DOCKER) push $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) ; fi
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) ; fi
.PHONY: armimage
armimage:
$(DOCKER) build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG)-arm .
docker build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG)-arm .
.PHONY: builder-image
builder-image:
$(DOCKER) build -t $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) --target builder .
@if [ "$(DOCKER_PUSH)" = "true" ] ; then $(DOCKER) push $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) ; fi
docker build -t $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) --target builder .
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) ; fi
.PHONY: mod-download
mod-download: test-tools-image
@@ -383,6 +336,11 @@ mod-vendor: test-tools-image
mod-vendor-local: mod-download-local
go mod vendor
# Deprecated - replace by install-tools-local
.PHONY: install-lint-tools
install-lint-tools:
./hack/install.sh lint-tools
# Run linter on the code
.PHONY: lint
lint: test-tools-image
@@ -394,7 +352,7 @@ lint-local:
golangci-lint --version
# NOTE: If you get a "Killed" OOM message, try reducing the value of GOGC
# See https://github.com/golangci/golangci-lint#memory-usage-of-golangci-lint
GOGC=$(ARGOCD_LINT_GOGC) GOMAXPROCS=2 golangci-lint run --fix --verbose
GOGC=$(ARGOCD_LINT_GOGC) GOMAXPROCS=2 golangci-lint run --fix --verbose --timeout 3000s
.PHONY: lint-ui
lint-ui: test-tools-image
@@ -428,9 +386,9 @@ test: test-tools-image
.PHONY: test-local
test-local:
if test "$(TEST_MODULE)" = ""; then \
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES=`go list ./... | grep -v 'test/e2e'` ./hack/test.sh -args -test.gocoverdir="$(PWD)/test-results"; \
./hack/test.sh -coverprofile=coverage.out `go list ./... | grep -v 'test/e2e'`; \
else \
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES="$(TEST_MODULE)" ./hack/test.sh -args -test.gocoverdir="$(PWD)/test-results" "$(TEST_MODULE)"; \
./hack/test.sh -coverprofile=coverage.out "$(TEST_MODULE)"; \
fi
.PHONY: test-race
@@ -442,9 +400,9 @@ test-race: test-tools-image
.PHONY: test-race-local
test-race-local:
if test "$(TEST_MODULE)" = ""; then \
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES=`go list ./... | grep -v 'test/e2e'` ./hack/test.sh -race -args -test.gocoverdir="$(PWD)/test-results"; \
./hack/test.sh -race -coverprofile=coverage.out `go list ./... | grep -v 'test/e2e'`; \
else \
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES="$(TEST_MODULE)" ./hack/test.sh -race -args -test.gocoverdir="$(PWD)/test-results"; \
./hack/test.sh -race -coverprofile=coverage.out "$(TEST_MODULE)"; \
fi
# Run the E2E test suite. E2E test servers (see start-e2e target) must be
@@ -458,7 +416,7 @@ test-e2e:
test-e2e-local: cli-local
# NO_PROXY ensures all tests don't go out through a proxy if one is configured on the test system
export GO111MODULE=off
DIST_DIR=${DIST_DIR} RERUN_FAILS=5 PACKAGES="./test/e2e" ARGOCD_E2E_RECORD=${ARGOCD_E2E_RECORD} ARGOCD_CONFIG_DIR=$(HOME)/.config/argocd-e2e ARGOCD_GPG_ENABLED=true NO_PROXY=* ./hack/test.sh -timeout $(ARGOCD_E2E_TEST_TIMEOUT) -v -args -test.gocoverdir="$(PWD)/test-results"
ARGOCD_E2E_RECORD=${ARGOCD_E2E_RECORD} ARGOCD_GPG_ENABLED=true NO_PROXY=* ./hack/test.sh -timeout $(ARGOCD_E2E_TEST_TIMEOUT) -v ./test/e2e
# Spawns a shell in the test server container for debugging purposes
debug-test-server: test-tools-image
@@ -471,7 +429,7 @@ debug-test-client: test-tools-image
# Starts e2e server in a container
.PHONY: start-e2e
start-e2e: test-tools-image
$(DOCKER) version
docker version
mkdir -p ${GOCACHE}
$(call run-in-test-server,make ARGOCD_PROCFILE=test/container/Procfile start-e2e-local)
@@ -480,7 +438,6 @@ start-e2e: test-tools-image
start-e2e-local: mod-vendor-local dep-ui-local cli-local
kubectl create ns argocd-e2e || true
kubectl create ns argocd-e2e-external || true
kubectl create ns argocd-e2e-external-2 || true
kubectl config set-context --current --namespace=argocd-e2e
kustomize build test/manifests/base | kubectl apply -f -
kubectl apply -f https://raw.githubusercontent.com/open-cluster-management/api/a6845f2ebcb186ec26b832f60c988537a58f3859/cluster/v1alpha1/0000_04_clusters.open-cluster-management.io_placementdecisions.crd.yaml
@@ -489,13 +446,6 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
mkdir -p /tmp/argo-e2e/app/config/gpg/keys && chmod 0700 /tmp/argo-e2e/app/config/gpg/keys
mkdir -p /tmp/argo-e2e/app/config/gpg/source && chmod 0700 /tmp/argo-e2e/app/config/gpg/source
mkdir -p /tmp/argo-e2e/app/config/plugin && chmod 0700 /tmp/argo-e2e/app/config/plugin
# create folders to hold go coverage results for each component
mkdir -p /tmp/coverage/app-controller
mkdir -p /tmp/coverage/api-server
mkdir -p /tmp/coverage/repo-server
mkdir -p /tmp/coverage/applicationset-controller
mkdir -p /tmp/coverage/notification
mkdir -p /tmp/coverage/commit-server
# set paths for locally managed ssh known hosts and tls certs data
ARGOCD_SSH_DATA_PATH=/tmp/argo-e2e/app/config/ssh \
ARGOCD_TLS_DATA_PATH=/tmp/argo-e2e/app/config/tls \
@@ -508,15 +458,11 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
ARGOCD_ZJWT_FEATURE_FLAG=always \
ARGOCD_IN_CI=$(ARGOCD_IN_CI) \
BIN_MODE=$(ARGOCD_BIN_MODE) \
ARGOCD_APPLICATION_NAMESPACES=argocd-e2e-external,argocd-e2e-external-2 \
ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACES=argocd-e2e-external,argocd-e2e-external-2 \
ARGOCD_APPLICATIONSET_CONTROLLER_TOKENREF_STRICT_MODE=true \
ARGOCD_APPLICATION_NAMESPACES=argocd-e2e-external \
ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACES=argocd-e2e-external \
ARGOCD_APPLICATIONSET_CONTROLLER_ALLOWED_SCM_PROVIDERS=http://127.0.0.1:8341,http://127.0.0.1:8342,http://127.0.0.1:8343,http://127.0.0.1:8344 \
ARGOCD_E2E_TEST=true \
ARGOCD_HYDRATOR_ENABLED=true \
ARGOCD_CLUSTER_CACHE_EVENTS_PROCESSING_INTERVAL=1ms \
goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START}
ls -lrt /tmp/coverage
# Cleans VSCode debug.test files from sub-dirs to prevent them from being included in by golang embed
.PHONY: clean-debug
@@ -529,7 +475,7 @@ clean: clean-debug
.PHONY: start
start: test-tools-image
$(DOCKER) version
docker version
$(call run-in-test-server,make ARGOCD_PROCFILE=test/container/Procfile start-local ARGOCD_START=${ARGOCD_START})
# Starts a local instance of ArgoCD
@@ -542,11 +488,9 @@ start-local: mod-vendor-local dep-ui-local cli-local
mkdir -p /tmp/argocd-local
mkdir -p /tmp/argocd-local/gpg/keys && chmod 0700 /tmp/argocd-local/gpg/keys
mkdir -p /tmp/argocd-local/gpg/source
REDIS_PASSWORD=$(shell kubectl get secret argocd-redis -o jsonpath='{.data.auth}' | base64 -d) \
ARGOCD_ZJWT_FEATURE_FLAG=always \
ARGOCD_IN_CI=false \
ARGOCD_GPG_ENABLED=$(ARGOCD_GPG_ENABLED) \
BIN_MODE=$(ARGOCD_BIN_MODE) \
ARGOCD_E2E_TEST=false \
ARGOCD_APPLICATION_NAMESPACES=$(ARGOCD_APPLICATION_NAMESPACES) \
goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START}
@@ -580,7 +524,7 @@ build-docs-local:
.PHONY: build-docs
build-docs:
$(DOCKER) run ${MKDOCS_RUN_ARGS} --rm -it -v ${CURRENT_DIR}:/docs -w /docs --entrypoint "" ${MKDOCS_DOCKER_IMAGE} sh -c 'pip install mkdocs; pip install $$(mkdocs get-deps); mkdocs build'
docker run ${MKDOCS_RUN_ARGS} --rm -it -v ${CURRENT_DIR}:/docs --entrypoint "" ${MKDOCS_DOCKER_IMAGE} sh -c 'pip install -r docs/requirements.txt; mkdocs build'
.PHONY: serve-docs-local
serve-docs-local:
@@ -588,7 +532,8 @@ serve-docs-local:
.PHONY: serve-docs
serve-docs:
$(DOCKER) run ${MKDOCS_RUN_ARGS} --rm -it -p 8000:8000 -v ${CURRENT_DIR}:/docs -w /docs --entrypoint "" ${MKDOCS_DOCKER_IMAGE} sh -c 'pip install mkdocs; pip install $$(mkdocs get-deps); mkdocs serve -a $$(ip route get 1 | awk '\''{print $$7}'\''):8000'
docker run ${MKDOCS_RUN_ARGS} --rm -it -p 8000:8000 -v ${CURRENT_DIR}/site:/site -w /site --entrypoint "" ${MKDOCS_DOCKER_IMAGE} python3 -m http.server --bind 0.0.0.0 8000
# Verify that kubectl can connect to your K8s cluster from Docker
.PHONY: verify-kube-connect
@@ -611,21 +556,17 @@ install-tools-local: install-test-tools-local install-codegen-tools-local instal
.PHONY: install-test-tools-local
install-test-tools-local:
./hack/install.sh kustomize
./hack/install.sh helm
./hack/install.sh gotestsum
./hack/install.sh oras
./hack/install.sh helm-linux
# Installs all tools required for running codegen (Linux packages)
.PHONY: install-codegen-tools-local
install-codegen-tools-local:
./hack/install.sh codegen-tools
./hack/install.sh codegen-go-tools
# Installs all tools required for running codegen (Go packages)
.PHONY: install-go-tools-local
install-go-tools-local:
./hack/install.sh codegen-go-tools
./hack/install.sh lint-tools
.PHONY: dep-ui
dep-ui: test-tools-image
@@ -643,7 +584,7 @@ list:
.PHONY: applicationset-controller
applicationset-controller:
GODEBUG="tarinsecurepath=0,zipinsecurepath=0" CGO_ENABLED=${CGO_FLAG} go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-applicationset-controller ./cmd
GODEBUG="tarinsecurepath=0,zipinsecurepath=0" CGO_ENABLED=0 go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-applicationset-controller ./cmd
.PHONY: checksums
checksums:
@@ -706,8 +647,9 @@ help:
@echo 'debug:'
@echo ' list -- list all make targets'
@echo ' install-tools-local -- install all the tools below'
@echo ' install-lint-tools(-local)'
@echo
@echo 'codegen:'
@echo ' codegen(-local) -- if using -local, run the following targets first'
@echo ' install-codegen-tools-local -- run this to install the codegen tools'
@echo ' install-go-tools-local -- run this to install go libraries for codegen'
@echo ' install-go-tools-local -- run this to install go libraries for codegen'

2
OWNERS
View File

@@ -1,12 +1,10 @@
owners:
- alexmt
- crenshaw-dev
- jessesuen
approvers:
- alexec
- alexmt
- gdsoumya
- jannfis
- jessesuen
- jgwest

View File

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

View File

@@ -7,12 +7,12 @@
[![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)
[![codecov](https://codecov.io/gh/argoproj/argo-cd/branch/master/graph/badge.svg)](https://codecov.io/gh/argoproj/argo-cd)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4486/badge)](https://bestpractices.coreinfrastructure.org/projects/4486)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/argoproj/argo-cd/badge)](https://scorecard.dev/viewer/?uri=github.com/argoproj/argo-cd)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/argoproj/argo-cd/badge)](https://api.securityscorecards.dev/projects/github.com/argoproj/argo-cd)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fargoproj%2Fargo-cd.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fargoproj%2Fargo-cd?ref=badge_shield)
**Social:**
[![Twitter Follow](https://img.shields.io/twitter/follow/argoproj?style=social)](https://twitter.com/argoproj)
[![Slack](https://img.shields.io/badge/slack-argoproj-brightgreen.svg?logo=slack)](https://argoproj.github.io/community/join-slack)
[![LinkedIn](https://img.shields.io/badge/LinkedIn-argoproj-blue.svg?logo=linkedin)](https://www.linkedin.com/company/argoproj/)
# Argo CD - Declarative Continuous Delivery for Kubernetes
@@ -56,7 +56,7 @@ Participation in the Argo CD project is governed by the [CNCF Code of Conduct](h
### Blogs and Presentations
1. [Awesome-Argo: A Curated List of Awesome Projects and Resources Related to Argo](https://github.com/terrytangyuan/awesome-argo)
1. [Unveil the Secret Ingredients of Continuous Delivery at Enterprise Scale with Argo CD](https://akuity.io/blog/secret-ingredients-of-continuous-delivery-at-enterprise-scale-with-argocd/)
1. [Unveil the Secret Ingredients of Continuous Delivery at Enterprise Scale with Argo CD](https://blog.akuity.io/unveil-the-secret-ingredients-of-continuous-delivery-at-enterprise-scale-with-argo-cd-7c5b4057ee49)
1. [GitOps Without Pipelines With ArgoCD Image Updater](https://youtu.be/avPUQin9kzU)
1. [Combining Argo CD (GitOps), Crossplane (Control Plane), And KubeVela (OAM)](https://youtu.be/eEcgn_gU3SM)
1. [How to Apply GitOps to Everything - Combining Argo CD and Crossplane](https://youtu.be/yrj4lmScKHQ)
@@ -85,5 +85,4 @@ Participation in the Argo CD project is governed by the [CNCF Code of Conduct](h
1. [Getting Started with ArgoCD for GitOps Deployments](https://youtu.be/AvLuplh1skA)
1. [Using Argo CD & Datree for Stable Kubernetes CI/CD Deployments](https://youtu.be/17894DTru2Y)
1. [How to create Argo CD Applications Automatically using ApplicationSet? "Automation of GitOps"](https://amralaayassen.medium.com/how-to-create-argocd-applications-automatically-using-applicationset-automation-of-the-gitops-59455eaf4f72)
1. [Progressive Delivery with Service Mesh Argo Rollouts with Istio](https://www.cncf.io/blog/2022/12/16/progressive-delivery-with-service-mesh-argo-rollouts-with-istio/)

View File

@@ -1,128 +0,0 @@
header:
schema-version: 1.0.0
expiration-date: '2024-10-31T00:00:00.000Z' # One year from initial release.
last-updated: '2023-10-27'
last-reviewed: '2023-10-27'
commit-hash: 06ef059f9fc7cf9da2dfaef2a505ee1e3c693485
project-url: https://github.com/argoproj/argo-cd
project-release: v3.3.0
changelog: https://github.com/argoproj/argo-cd/releases
license: https://github.com/argoproj/argo-cd/blob/master/LICENSE
project-lifecycle:
status: active
roadmap: https://github.com/orgs/argoproj/projects/25
bug-fixes-only: false
core-maintainers:
- https://github.com/argoproj/argoproj/blob/master/MAINTAINERS.md
release-cycle: https://argo-cd.readthedocs.io/en/stable/developer-guide/release-process-and-cadence/
release-process: https://argo-cd.readthedocs.io/en/stable/developer-guide/release-process-and-cadence/#release-process
contribution-policy:
accepts-pull-requests: true
accepts-automated-pull-requests: true
automated-tools-list:
- automated-tool: dependabot
action: allowed
path:
- /
- automated-tool: snyk-report
action: allowed
path:
- docs/snyk
comment: |
This tool runs Snyk and generates a report of vulnerabilities in the project's dependencies. The report is
placed in the project's documentation. The workflow is defined here:
https://github.com/argoproj/argo-cd/blob/master/.github/workflows/update-snyk.yaml
contributing-policy: https://argo-cd.readthedocs.io/en/stable/developer-guide/code-contributions/
code-of-conduct: https://github.com/cncf/foundation/blob/master/code-of-conduct.md
documentation:
- https://argo-cd.readthedocs.io/
distribution-points:
- https://github.com/argoproj/argo-cd/releases
- https://quay.io/repository/argoproj/argocd
security-artifacts:
threat-model:
threat-model-created: true
evidence-url:
- https://github.com/argoproj/argoproj/blob/master/docs/argo_threat_model.pdf
- https://github.com/argoproj/argoproj/blob/master/docs/end_user_threat_model.pdf
self-assessment:
self-assessment-created: false
comment: |
An extensive self-assessment was performed for CNCF graduation. Because the self-assessment process was evolving
at the time, no standardized document has been published.
security-testing:
- tool-type: sca
tool-name: Dependabot
tool-version: "2"
tool-url: https://github.com/dependabot
integration:
ad-hoc: false
ci: false
before-release: false
tool-rulesets:
- https://github.com/argoproj/argo-cd/blob/master/.github/dependabot.yml
- tool-type: sca
tool-name: Snyk
tool-version: latest
tool-url: https://snyk.io/
integration:
ad-hoc: true
ci: true
before-release: false
- tool-type: sast
tool-name: CodeQL
tool-version: latest
tool-url: https://codeql.github.com/
integration:
ad-hoc: false
ci: true
before-release: false
comment: |
We use the default configuration with the latest version.
security-assessments:
- auditor-name: Trail of Bits
auditor-url: https://trailofbits.com
auditor-report: https://github.com/argoproj/argoproj/blob/master/docs/argo_security_final_report.pdf
report-year: 2021
- auditor-name: Ada Logics
auditor-url: https://adalogics.com
auditor-report: https://github.com/argoproj/argoproj/blob/master/docs/argo_security_audit_2022.pdf
report-year: 2022
- auditor-name: Ada Logics
auditor-url: https://adalogics.com
auditor-report: https://github.com/argoproj/argoproj/blob/master/docs/audit_fuzzer_adalogics_2022.pdf
report-year: 2022
comment: |
Part of the audit was performed by Ada Logics, focussed on fuzzing.
- auditor-name: Chainguard
auditor-url: https://chainguard.dev
auditor-report: https://github.com/argoproj/argoproj/blob/master/docs/software_supply_chain_slsa_assessment_chainguard_2023.pdf
report-year: 2023
comment: |
Confirmed the project's release process as achieving SLSA (v0.1) level 3.
security-contacts:
- type: email
value: cncf-argo-security@lists.cncf.io
primary: true
vulnerability-reporting:
accepts-vulnerability-reports: true
email-contact: cncf-argo-security@lists.cncf.io
security-policy: https://github.com/argoproj/argo-cd/security/policy
bug-bounty-available: true
bug-bounty-url: https://hackerone.com/ibb/policy_scopes
out-scope:
- vulnerable and outdated components # See https://github.com/argoproj/argo-cd/blob/master/SECURITY.md#a-word-about-security-scanners
- security logging and monitoring failures
dependencies:
third-party-packages: true
dependencies-lists:
- https://github.com/argoproj/argo-cd/blob/master/go.mod
- https://github.com/argoproj/argo-cd/blob/master/Dockerfile
- https://github.com/argoproj/argo-cd/blob/master/ui/package.json
sbom:
- sbom-file: https://github.com/argoproj/argo-cd/releases # Every release's assets include SBOMs.
sbom-format: SPDX
dependencies-lifecycle:
policy-url: https://argo-cd.readthedocs.io/en/stable/developer-guide/release-process-and-cadence/#dependencies-lifecycle-policy
env-dependencies-policy:
policy-url: https://argo-cd.readthedocs.io/en/stable/developer-guide/release-process-and-cadence/#dependencies-lifecycle-policy

View File

@@ -50,7 +50,7 @@ of releasing it within a patch branch for the currently supported releases.
## Reporting a Vulnerability
If you find a security related bug in Argo CD, we kindly ask you for responsible
If you find a security related bug in ArgoCD, we kindly ask you for responsible
disclosure and for giving us appropriate time to react, analyze and develop a
fix to mitigate the found security vulnerability.

291
Tiltfile
View File

@@ -1,291 +0,0 @@
load('ext://restart_process', 'docker_build_with_restart')
load('ext://uibutton', 'cmd_button', 'location')
# add ui button in web ui to run make codegen-local (top nav)
cmd_button(
'make codegen-local',
argv=['sh', '-c', 'make codegen-local'],
location=location.NAV,
icon_name='terminal',
text='make codegen-local',
)
cmd_button(
'make test-local',
argv=['sh', '-c', 'make test-local'],
location=location.NAV,
icon_name='science',
text='make test-local',
)
# add ui button in web ui to run make codegen-local (top nav)
cmd_button(
'make cli-local',
argv=['sh', '-c', 'make cli-local'],
location=location.NAV,
icon_name='terminal',
text='make cli-local',
)
# detect cluster architecture for build
cluster_version = decode_yaml(local('kubectl version -o yaml'))
platform = cluster_version['serverVersion']['platform']
arch = platform.split('/')[1]
# build the argocd binary on code changes
code_deps = [
'applicationset',
'cmd',
'cmpserver',
'commitserver',
'common',
'controller',
'notification-controller',
'pkg',
'reposerver',
'server',
'util',
'go.mod',
'go.sum',
]
local_resource(
'build',
'CGO_ENABLED=0 GOOS=linux GOARCH=' + arch + ' go build -gcflags="all=-N -l" -mod=readonly -o .tilt-bin/argocd_linux cmd/main.go',
deps = code_deps,
allow_parallel=True,
)
# deploy the argocd manifests
k8s_yaml(kustomize('manifests/dev-tilt'))
# build dev image
docker_build_with_restart(
'argocd',
context='.',
dockerfile='Dockerfile.tilt',
entrypoint=[
"/usr/bin/tini",
"-s",
"--",
"dlv",
"exec",
"--continue",
"--accept-multiclient",
"--headless",
"--listen=:2345",
"--api-version=2"
],
platform=platform,
live_update=[
sync('.tilt-bin/argocd_linux', '/usr/local/bin/argocd'),
],
only=[
'.tilt-bin',
'hack',
'entrypoint.sh',
],
restart_file='/tilt/.restart-proc'
)
# build image for argocd-cli jobs
docker_build(
'argocd-job',
context='.',
dockerfile='Dockerfile.tilt',
platform=platform,
only=[
'.tilt-bin',
'hack',
'entrypoint.sh',
]
)
# track argocd-server resources and port forward
k8s_resource(
workload='argocd-server',
objects=[
'argocd-server:serviceaccount',
'argocd-server:role',
'argocd-server:rolebinding',
'argocd-cm:configmap',
'argocd-cmd-params-cm:configmap',
'argocd-gpg-keys-cm:configmap',
'argocd-rbac-cm:configmap',
'argocd-ssh-known-hosts-cm:configmap',
'argocd-tls-certs-cm:configmap',
'argocd-secret:secret',
'argocd-server-network-policy:networkpolicy',
'argocd-server:clusterrolebinding',
'argocd-server:clusterrole',
],
port_forwards=[
'8080:8080',
'9345:2345',
'8083:8083'
],
)
# track crds
k8s_resource(
new_name='cluster-resources',
objects=[
'applications.argoproj.io:customresourcedefinition',
'applicationsets.argoproj.io:customresourcedefinition',
'appprojects.argoproj.io:customresourcedefinition',
'argocd:namespace'
]
)
# track argocd-repo-server resources and port forward
k8s_resource(
workload='argocd-repo-server',
objects=[
'argocd-repo-server:serviceaccount',
'argocd-repo-server-network-policy:networkpolicy',
],
port_forwards=[
'8081:8081',
'9346:2345',
'8084:8084'
],
)
# track argocd-redis resources and port forward
k8s_resource(
workload='argocd-redis',
objects=[
'argocd-redis:serviceaccount',
'argocd-redis:role',
'argocd-redis:rolebinding',
'argocd-redis-network-policy:networkpolicy',
],
port_forwards=[
'6379:6379',
],
)
# track argocd-applicationset-controller resources
k8s_resource(
workload='argocd-applicationset-controller',
objects=[
'argocd-applicationset-controller:serviceaccount',
'argocd-applicationset-controller-network-policy:networkpolicy',
'argocd-applicationset-controller:role',
'argocd-applicationset-controller:rolebinding',
'argocd-applicationset-controller:clusterrolebinding',
'argocd-applicationset-controller:clusterrole',
],
port_forwards=[
'9347:2345',
'8085:8080',
'7000:7000'
],
)
# track argocd-application-controller resources
k8s_resource(
workload='argocd-application-controller',
objects=[
'argocd-application-controller:serviceaccount',
'argocd-application-controller-network-policy:networkpolicy',
'argocd-application-controller:role',
'argocd-application-controller:rolebinding',
'argocd-application-controller:clusterrolebinding',
'argocd-application-controller:clusterrole',
],
port_forwards=[
'9348:2345',
'8086:8082',
],
)
# track argocd-notifications-controller resources
k8s_resource(
workload='argocd-notifications-controller',
objects=[
'argocd-notifications-controller:serviceaccount',
'argocd-notifications-controller-network-policy:networkpolicy',
'argocd-notifications-controller:role',
'argocd-notifications-controller:rolebinding',
'argocd-notifications-cm:configmap',
'argocd-notifications-secret:secret',
],
port_forwards=[
'9349:2345',
'8087:9001',
],
)
# track argocd-dex-server resources
k8s_resource(
workload='argocd-dex-server',
objects=[
'argocd-dex-server:serviceaccount',
'argocd-dex-server-network-policy:networkpolicy',
'argocd-dex-server:role',
'argocd-dex-server:rolebinding',
],
)
# track argocd-commit-server resources
k8s_resource(
workload='argocd-commit-server',
objects=[
'argocd-commit-server:serviceaccount',
'argocd-commit-server-network-policy:networkpolicy',
],
port_forwards=[
'9350:2345',
'8088:8087',
'8089:8086',
],
)
# docker for ui
docker_build(
'argocd-ui',
context='.',
dockerfile='Dockerfile.ui.tilt',
entrypoint=['sh', '-c', 'cd /app/ui && yarn start'],
only=['ui'],
live_update=[
sync('ui', '/app/ui'),
run('sh -c "cd /app/ui && yarn install"', trigger=['/app/ui/package.json', '/app/ui/yarn.lock']),
],
)
# track argocd-ui resources and port forward
k8s_resource(
workload='argocd-ui',
port_forwards=[
'4000:4000',
],
)
# linting
local_resource(
'lint',
'make lint-local',
deps = code_deps,
allow_parallel=True,
resource_deps=['vendor']
)
local_resource(
'lint-ui',
'make lint-ui-local',
deps = [
'ui',
],
allow_parallel=True,
)
local_resource(
'vendor',
'go mod vendor',
deps = [
'go.mod',
'go.sum',
],
)

131
USERS.md
View File

@@ -5,90 +5,58 @@ PR with your organization name if you are using Argo CD.
Currently, the following organizations are **officially** using Argo CD:
1. [100ms](https://www.100ms.ai/)
1. [127Labs](https://127labs.com/)
1. [3Rein](https://www.3rein.com/)
1. [42 School](https://42.fr/)
1. [4data](https://4data.ch/)
1. [7shifts](https://www.7shifts.com/)
1. [Adevinta](https://www.adevinta.com/)
1. [Adfinis](https://adfinis.com)
1. [Adobe](https://www.adobe.com/)
1. [Adventure](https://jp.adventurekk.com/)
1. [Adyen](https://www.adyen.com)
1. [AirQo](https://airqo.net/)
1. [Akuity](https://akuity.io/)
1. [Alarm.com](https://alarm.com/)
1. [Alauda](https://alauda.io/)
1. [Albert Heijn](https://ah.nl/)
1. [Alibaba Group](https://www.alibabagroup.com/)
1. [Allianz Direct](https://www.allianzdirect.de/)
1. [AlphaSense](https://www.alpha-sense.com/)
1. [Amadeus IT Group](https://amadeus.com/)
1. [Ambassador Labs](https://www.getambassador.io/)
1. [Ancestry](https://www.ancestry.com/)
1. [Andgo Systems](https://www.andgosystems.com/)
1. [ANSTO - Australian Synchrotron](https://www.synchrotron.org.au/)
1. [Ant Group](https://www.antgroup.com/)
1. [AppDirect](https://www.appdirect.com)
1. [Arctiq Inc.](https://www.arctiq.ca)
1. [Artemis Health by Nomi Health](https://www.artemishealth.com/)
1. [Arturia](https://www.arturia.com)
1. [ARZ Allgemeines Rechenzentrum GmbH](https://www.arz.at/)
1. [Augury](https://www.augury.com/)
1. [Autodesk](https://www.autodesk.com)
1. [Axians ACSP](https://www.axians.fr)
1. [Axual B.V.](https://axual.com)
1. [Back Market](https://www.backmarket.com)
1. [Bajaj Finserv Health Ltd.](https://www.bajajfinservhealth.in)
1. [Baloise](https://www.baloise.com)
1. [Batumbu](https://batumbu.id)
1. [BCDevExchange DevOps Platform](https://bcdevexchange.org/DevOpsPlatform)
1. [Beat](https://thebeat.co/en/)
1. [Beez Innovation Labs](https://www.beezlabs.com/)
1. [Bedag Informatik AG](https://www.bedag.ch/)
1. [Beleza Na Web](https://www.belezanaweb.com.br/)
1. [Believable Bots](https://believablebots.io)
1. [Bayer AG](https://bayer.com)
1. [BigPanda](https://bigpanda.io)
1. [BioBox Analytics](https://biobox.io)
1. [BMW Group](https://www.bmwgroup.com/)
1. [Boozt](https://www.booztgroup.com/)
1. [Bosch](https://www.bosch.com/)
1. [Boticario](https://www.boticario.com.br/)
1. [Broker Consulting, a.s.](https://www.bcas.cz/en/)
1. [Bulder Bank](https://bulderbank.no)
1. [Cabify](https://cabify.com/en)
1. [CAM](https://cam-inc.co.jp)
1. [Camptocamp](https://camptocamp.com)
1. [Candis](https://www.candis.io)
1. [Capital One](https://www.capitalone.com)
1. [Capptain LTD](https://capptain.co/)
1. [CARFAX Europe](https://www.carfax.eu)
1. [CARFAX](https://www.carfax.com)
1. [Carrefour Group](https://www.carrefour.com)
1. [CARFAX Europe](https://www.carfax.eu)
1. [Casavo](https://casavo.com)
1. [Celonis](https://www.celonis.com/)
1. [CERN](https://home.cern/)
1. [Chainnodes](https://chainnodes.org)
1. [Chargetrip](https://chargetrip.com)
1. [Chainnodes](https://chainnodes.org)
1. [Chime](https://www.chime.com)
1. [Chronicle Labs](https://chroniclelabs.org)
1. [Cisco ET&I](https://eti.cisco.com/)
1. [Close](https://www.close.com/)
1. [Cloud Posse](https://www.cloudposse.com/)
1. [Cloud Scale](https://cloudscaleinc.com/)
1. [CloudScript](https://www.cloudscript.com.br/)
1. [CloudGeometry](https://www.cloudgeometry.io/)
1. [Cloudmate](https://cloudmt.co.kr/)
1. [Cloudogu](https://cloudogu.com/)
1. [Cobalt](https://www.cobalt.io/)
1. [Codefresh](https://www.codefresh.io/)
1. [Codility](https://www.codility.com/)
1. [Cognizant](https://www.cognizant.com/)
1. [Commonbond](https://commonbond.co/)
1. [Compatio.AI](https://compatio.ai/)
1. [Contlo](https://contlo.com/)
1. [Coralogix](https://coralogix.com/)
1. [Crédit Agricole CIB](https://www.ca-cib.com)
1. [CROZ d.o.o.](https://croz.net/)
@@ -97,18 +65,12 @@ Currently, the following organizations are **officially** using Argo CD:
1. [D2iQ](https://www.d2iq.com)
1. [DaoCloud](https://daocloud.io/)
1. [Datarisk](https://www.datarisk.io/)
1. [Daydream](https://daydream.ing)
1. [Deloitte](https://www.deloitte.com/)
1. [Deutsche Telekom AG](https://telekom.com)
1. [Deutsche Bank AG](https://www.deutsche-bank.de/)
1. [Devopsi - Poland Software/DevOps Consulting](https://devopsi.pl/)
1. [Devtron Labs](https://github.com/devtron-labs/devtron)
1. [DigitalEd](https://www.digitaled.com)
1. [DigitalOcean](https://www.digitalocean.com)
1. [Divar](https://divar.ir)
1. [Divistant](https://divistant.com)
1. [Dott](https://ridedott.com)
1. [Doubble](https://www.doubble.app)
1. [Doximity](https://www.doximity.com/)
1. [EDF Renewables](https://www.edf-re.com/)
1. [edX](https://edx.org)
@@ -120,26 +82,18 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Energisme](https://energisme.com/)
1. [enigmo](https://enigmo.co.jp/)
1. [Envoy](https://envoy.com/)
1. [eSave](https://esave.es/)
1. [Factorial](https://factorialhr.com/)
1. [Farfetch](https://www.farfetch.com)
1. [Faro](https://www.faro.com/)
1. [Fave](https://myfave.com)
1. [Flexport](https://www.flexport.com/)
1. [Flip](https://flip.id)
1. [Fly Security](https://www.flysecurity.com.br/)
1. [Fonoa](https://www.fonoa.com/)
1. [Fortra](https://www.fortra.com)
1. [freee](https://corp.freee.co.jp/en/company/)
1. [Freshop, Inc](https://www.freshop.com/)
1. [Future PLC](https://www.futureplc.com/)
1. [Flagler Health](https://www.flaglerhealth.io/)
1. [G DATA CyberDefense AG](https://www.gdata-software.com/)
1. [G-Research](https://www.gresearch.com/teams/open-source-software/)
1. [Garner](https://www.garnercorp.com)
1. [Generali Deutschland AG](https://www.generali.de/)
1. [Gepardec](https://gepardec.com/)
1. [Getir](https://getir.com)
1. [GetYourGuide](https://www.getyourguide.com/)
1. [Gitpod](https://www.gitpod.io)
1. [Gllue](https://gllue.com)
@@ -149,39 +103,29 @@ Currently, the following organizations are **officially** using Argo CD:
1. [GlueOps](https://glueops.dev)
1. [GMETRI](https://gmetri.com/)
1. [Gojek](https://www.gojek.io/)
1. [GoTo Financial](https://gotofinancial.com/)
1. [GoTo](https://www.goto.com/)
1. [GoTo Financial](https://gotofinancial.com/)
1. [Greenpass](https://www.greenpass.com.br/)
1. [Gridfuse](https://gridfuse.com/)
1. [Groww](https://groww.in)
1. [Grupo MasMovil](https://grupomasmovil.com/en/)
1. [Handelsbanken](https://www.handelsbanken.se)
1. [Hazelcast](https://hazelcast.com/)
1. [Healy](https://www.healyworld.net)
1. [Helio](https://helio.exchange)
1. [hetao101](https://www.hetao101.com/)
1. [Hetki](https://hetki.ai)
1. [hipages](https://hipages.com.au/)
1. [Hiya](https://hiya.com)
1. [Honestbank](https://honestbank.com)
1. [Hostinger](https://www.hostinger.com)
1. [Hotjar](https://www.hotjar.com)
1. [IABAI](https://www.iab.ai)
1. [IBM](https://www.ibm.com/)
1. [Ibotta](https://home.ibotta.com)
1. [Icelandair](https://www.icelandair.com)
1. [IFS](https://www.ifs.com)
1. [IITS-Consulting](https://iits-consulting.de)
1. [IllumiDesk](https://www.illumidesk.com)
1. [imaware](https://imaware.health)
1. [Indeed](https://indeed.com)
1. [Index Exchange](https://www.indexexchange.com/)
1. [Info Support](https://www.infosupport.com/)
1. [InsideBoard](https://www.insideboard.com)
1. [Instruqt](https://www.instruqt.com)
1. [Intel](https://www.intel.com)
1. [Intuit](https://www.intuit.com/)
1. [Jellysmack](https://www.jellysmack.com)
1. [Joblift](https://joblift.com/)
1. [JovianX](https://www.jovianx.com/)
1. [Kaltura](https://corp.kaltura.com/)
@@ -189,29 +133,22 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Karrot](https://www.daangn.com/)
1. [KarrotPay](https://www.daangnpay.com/)
1. [Kasa](https://kasa.co.kr/)
1. [Kave Home](https://kavehome.com)
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/)
1. [KompiTech GmbH](https://www.kompitech.com/)
1. [Kong Inc.](https://konghq.com/)
1. [KPMG](https://kpmg.com/uk)
1. [KubeSphere](https://github.com/kubesphere)
1. [Kurly](https://www.kurly.com/)
1. [Kvist](https://kvistsolutions.com)
1. [Kyriba](https://www.kyriba.com/)
1. [LeFigaro](https://www.lefigaro.fr/)
1. [Lely](https://www.lely.com/)
1. [LexisNexis](https://www.lexisnexis.com/)
1. [Lian Chu Securities](https://lczq.com)
1. [Liatrio](https://www.liatrio.com)
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. [LY Corporation](https://www.lycorp.co.jp/en/)
1. [Magic Leap](https://www.magicleap.com/)
1. [Majid Al Futtaim](https://www.majidalfuttaim.com/)
1. [Major League Baseball](https://mlb.com)
@@ -223,8 +160,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Meican](https://meican.com/)
1. [Meilleurs Agents](https://www.meilleursagents.com/)
1. [Mercedes-Benz Tech Innovation](https://www.mercedes-benz-techinnovation.com/)
1. [Mercedes-Benz.io](https://www.mercedes-benz.io/)
1. [Metacore Games](https://metacoregames.com/)
1. [Metanet](http://www.metanet.co.kr/en/)
1. [MindSpore](https://mindspore.cn)
1. [Mirantis](https://mirantis.com/)
@@ -233,28 +168,20 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Moengage](https://www.moengage.com/)
1. [Money Forward](https://corp.moneyforward.com/en/)
1. [MOO Print](https://www.moo.com/)
1. [Mozilla](https://www.mozilla.org)
1. [MTN Group](https://www.mtn.com/)
1. [Municipality of The Hague](https://www.denhaag.nl/)
1. [My Job Glasses](https://myjobglasses.com)
1. [Natura &Co](https://naturaeco.com/)
1. [Nethopper](https://nethopper.io)
1. [New Relic](https://newrelic.com/)
1. [Nextbasket](https://nextbasket.com)
1. [Nextdoor](https://nextdoor.com/)
1. [Next Fit Sistemas](https://nextfit.com.br/)
1. [Nikkei](https://www.nikkei.co.jp/nikkeiinfo/en/)
1. [Nitro](https://gonitro.com)
1. [NYCU, CS IT Center](https://it.cs.nycu.edu.tw)
1. [Objective](https://www.objective.com.br/)
1. [OCCMundial](https://occ.com.mx)
1. [Octadesk](https://octadesk.com)
1. [Octopus Deploy](https://octopus.com)
1. [Olfeo](https://www.olfeo.com/)
1. [omegaUp](https://omegaUp.com)
1. [Omni](https://omni.se/)
1. [Oncourse Home Solutions](https://oncoursehome.com/)
1. [Open Analytics](https://openanalytics.eu)
1. [openEuler](https://openeuler.org)
1. [openGauss](https://opengauss.org/)
1. [OpenGov](https://opengov.com)
@@ -266,112 +193,76 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Optoro](https://www.optoro.com/)
1. [Orbital Insight](https://orbitalinsight.com/)
1. [Oscar Health Insurance](https://hioscar.com/)
1. [Outpost24](https://outpost24.com/)
1. [p3r](https://www.p3r.one/)
1. [Packlink](https://www.packlink.com/)
1. [PagerDuty](https://www.pagerduty.com/)
1. [Pandosearch](https://www.pandosearch.com/en/home)
1. [Patreon](https://www.patreon.com/)
1. [PayIt](https://payitgov.com/)
1. [PayPay](https://paypay.ne.jp/)
1. [Paystack](https://paystack.com/)
1. [Peloton Interactive](https://www.onepeloton.com/)
1. [Percona](https://percona.com/)
1. [PGS](https://www.pgs.com)
1. [Pigment](https://www.gopigment.com/)
1. [Pipedrive](https://www.pipedrive.com/)
1. [Pipefy](https://www.pipefy.com/)
1. [Pipekit](https://pipekit.io/)
1. [Pismo](https://pismo.io/)
1. [PITS Globale Datenrettungsdienste](https://www.pitsdatenrettung.de/)
1. [Platform9 Systems](https://platform9.com/)
1. [Polarpoint.io](https://polarpoint.io)
1. [Pollinate](https://www.pollinate.global)
1. [PostFinance](https://github.com/postfinance)
1. [Preferred Networks](https://preferred.jp/en/)
1. [Previder BV](https://previder.nl)
1. [Priceline](https://priceline.com)
1. [Procore](https://www.procore.com)
1. [Productboard](https://www.productboard.com/)
1. [Prudential](https://prudential.com.sg)
1. [PT Boer Technology (Btech)](https://btech.id/)
1. [PUBG](https://www.pubg.com)
1. [Puzzle ITC](https://www.puzzle.ch/)
1. [Pvotal Technologies](https://pvotal.tech/)
1. [Qonto](https://qonto.com)
1. [QuintoAndar](https://quintoandar.com.br)
1. [Quipper](https://www.quipper.com/)
1. [RapidAPI](https://www.rapidapi.com/)
1. [rebuy](https://www.rebuy.de/)
1. [Recreation.gov](https://www.recreation.gov/)
1. [Red Hat](https://www.redhat.com/)
1. [Redpill Linpro](https://www.redpill-linpro.com/)
1. [Reenigne Cloud](https://reenigne.ca)
1. [reev.com](https://www.reev.com/)
1. [Relex Solutions](https://www.relexsolutions.com/)
1. [RightRev](https://rightrev.com/)
1. [Rijkswaterstaat](https://www.rijkswaterstaat.nl/en)
1. Rise
1. [Rise](https://www.risecard.eu/)
1. [Riskified](https://www.riskified.com/)
1. [Robotinfra](https://www.robotinfra.com)
1. [Rocket.Chat](https://rocket.chat)
1. [Rogo](https://rogodata.com)
1. [Rubin Observatory](https://www.lsst.org)
1. [Saildrone](https://www.saildrone.com/)
1. [Salad Technologies](https://salad.com/)
1. [Saloodo! GmbH](https://www.saloodo.com)
1. [Sap Labs](http://sap.com)
1. [Sauce Labs](https://saucelabs.com/)
1. [Schneider Electric](https://www.se.com)
1. [Schwarz IT](https://jobs.schwarz/it-mission)
1. [SCRM Lidl International Hub](https://scrm.lidl)
1. [SEEK](https://seek.com.au)
1. [SEKAI](https://www.sekai.io/)
1. [Semgrep](https://semgrep.com)
1. [Seznam.cz](https://o-seznam.cz/)
1. [Shield](https://shield.com)
1. [Shipfox](https://www.shipfox.io)
1. [Shock Media](https://www.shockmedia.nl)
1. [SI Analytics](https://si-analytics.ai)
1. [Sidewalk Entertainment](https://sidewalkplay.com/)
1. [Skit](https://skit.ai/)
1. [Skribble](https://skribble.com)
1. [Skyscanner](https://www.skyscanner.net/)
1. [Smart Pension](https://www.smartpension.co.uk/)
1. [Smilee.io](https://smilee.io)
1. [Smilegate Stove](https://www.onstove.com/)
1. [Smood.ch](https://www.smood.ch/)
1. [Snapp](https://snapp.ir/)
1. [Snyk](https://snyk.io/)
1. [Softway Medical](https://www.softwaymedical.fr/)
1. [Sophotech](https://sopho.tech)
1. [South China Morning Post (SCMP)](https://www.scmp.com/)
1. [Speee](https://speee.jp/)
1. [Spendesk](https://spendesk.com/)
1. [Splunk](https://splunk.com/)
1. [Spores Labs](https://spores.app)
1. [Statsig](https://statsig.com)
1. [SternumIOT](https://sternumiot.com)
1. [StreamNative](https://streamnative.io)
1. [Stuart](https://stuart.com/)
1. [Sumo Logic](https://sumologic.com/)
1. [Sutpc](http://www.sutpc.com/)
1. [Swiss Post](https://github.com/swisspost)
1. [Swissblock Technologies](https://swissblock.net/)
1. [Swisscom](https://www.swisscom.ch)
1. [Swissquote](https://github.com/swissquote)
1. [Syncier](https://syncier.com/)
1. [Synergy](https://synergy.net.au)
1. [Syself](https://syself.com)
1. [T-ROC Global](https://trocglobal.com/)
1. [TableCheck](https://tablecheck.com/)
1. [Tailor Brands](https://www.tailorbrands.com)
1. [Tamkeen Technologies](https://tamkeentech.sa/)
1. [TBC Bank](https://tbcbank.ge/)
1. [Techcombank](https://www.techcombank.com.vn/trang-chu)
1. [Technacy](https://www.technacy.it/)
1. [Telavita](https://www.telavita.com.br/)
1. [Tesla](https://tesla.com/)
1. [TextNow](https://www.textnow.com/)
1. [The Scale Factory](https://www.scalefactory.com/)
1. [ThousandEyes](https://www.thousandeyes.com/)
1. [Ticketmaster](https://ticketmaster.com)
@@ -381,30 +272,23 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Trendyol](https://www.trendyol.com/)
1. [tru.ID](https://tru.id)
1. [Trusting Social](https://trustingsocial.com/)
1. [Twilio Segment](https://segment.com/)
1. [Twilio SendGrid](https://sendgrid.com)
1. [tZERO](https://www.tzero.com/)
1. [U.S. Veterans Affairs Department](https://www.va.gov/)
1. [UBIO](https://ub.io/)
1. [UFirstGroup](https://www.ufirstgroup.com/en/)
1. [ungleich.ch](https://ungleich.ch/)
1. [Unifonic Inc](https://www.unifonic.com/)
1. [Universidad Mesoamericana](https://www.umes.edu.gt/)
1. [Upsider Inc.](https://up-sider.com/lp/)
1. [Urbantz](https://urbantz.com/)
1. [Vectra](https://www.vectra.ai)
1. [Veepee](https://www.veepee.com)
1. [Verkada](https://www.verkada.com)
1. [Viaduct](https://www.viaduct.ai/)
1. [VietMoney](https://vietmoney.vn/)
1. [Vinted](https://vinted.com/)
1. [Virtuo](https://www.govirtuo.com/)
1. [VISITS Technologies](https://visits.world/en)
1. [Viya](https://viya.me)
1. [Volvo Cars](https://www.volvocars.com/)
1. [Voyager Digital](https://www.investvoyager.com/)
1. [VSHN - The DevOps Company](https://vshn.ch/)
1. [Wakacje.pl](https://www.wakacje.pl/)
1. [Walkbase](https://www.walkbase.com/)
1. [Webstores](https://www.webstores.nl)
1. [Wehkamp](https://www.wehkamp.nl/)
@@ -416,12 +300,9 @@ Currently, the following organizations are **officially** using Argo CD:
1. [WooliesX](https://wooliesx.com.au/)
1. [Woolworths Group](https://www.woolworthsgroup.com.au/)
1. [WSpot](https://www.wspot.com.br/)
1. [X3M ads](https://x3mads.com)
1. [Yieldlab](https://www.yieldlab.de/)
1. [Youverify](https://youverify.co/)
1. [Yubo](https://www.yubo.live/)
1. [Yuno](https://y.uno/)
1. [ZDF](https://www.zdf.de/)
1. [Zimpler](https://www.zimpler.com/)
1. [ZipRecruiter](https://www.ziprecruiter.com/)
1. [ZOZO](https://corp.zozo.com/)

View File

@@ -1 +1 @@
3.3.0
2.8.6

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -4,8 +4,6 @@ import (
"context"
"fmt"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/types"
@@ -14,43 +12,43 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"github.com/argoproj/argo-cd/v3/common"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/applicationset/generators"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
// clusterSecretEventHandler is used when watching Secrets to check if they are ArgoCD Cluster Secrets, and if so
// requeue any related ApplicationSets.
type clusterSecretEventHandler struct {
// handler.EnqueueRequestForOwner
//handler.EnqueueRequestForOwner
Log log.FieldLogger
Client client.Client
}
func (h *clusterSecretEventHandler) Create(ctx context.Context, e event.CreateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
h.queueRelatedAppGenerators(ctx, q, e.Object)
func (h *clusterSecretEventHandler) Create(e event.CreateEvent, q workqueue.RateLimitingInterface) {
h.queueRelatedAppGenerators(q, e.Object)
}
func (h *clusterSecretEventHandler) Update(ctx context.Context, e event.UpdateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
h.queueRelatedAppGenerators(ctx, q, e.ObjectNew)
func (h *clusterSecretEventHandler) Update(e event.UpdateEvent, q workqueue.RateLimitingInterface) {
h.queueRelatedAppGenerators(q, e.ObjectNew)
}
func (h *clusterSecretEventHandler) Delete(ctx context.Context, e event.DeleteEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
h.queueRelatedAppGenerators(ctx, q, e.Object)
func (h *clusterSecretEventHandler) Delete(e event.DeleteEvent, q workqueue.RateLimitingInterface) {
h.queueRelatedAppGenerators(q, e.Object)
}
func (h *clusterSecretEventHandler) Generic(ctx context.Context, e event.GenericEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
h.queueRelatedAppGenerators(ctx, q, e.Object)
func (h *clusterSecretEventHandler) Generic(e event.GenericEvent, q workqueue.RateLimitingInterface) {
h.queueRelatedAppGenerators(q, e.Object)
}
// addRateLimitingInterface defines the Add method of workqueue.RateLimitingInterface, allow us to easily mock
// it for testing purposes.
type addRateLimitingInterface[T comparable] interface {
Add(item T)
type addRateLimitingInterface interface {
Add(item interface{})
}
func (h *clusterSecretEventHandler) queueRelatedAppGenerators(ctx context.Context, q addRateLimitingInterface[reconcile.Request], object client.Object) {
func (h *clusterSecretEventHandler) queueRelatedAppGenerators(q addRateLimitingInterface, object client.Object) {
// Check for label, lookup all ApplicationSets that might match the cluster, queue them all
if object.GetLabels()[common.LabelKeySecretType] != common.LabelValueSecretTypeCluster {
if object.GetLabels()[generators.ArgoCDSecretTypeLabel] != generators.ArgoCDSecretTypeCluster {
return
}
@@ -60,7 +58,7 @@ func (h *clusterSecretEventHandler) queueRelatedAppGenerators(ctx context.Contex
}).Info("processing event for cluster secret")
appSetList := &argoprojiov1alpha1.ApplicationSetList{}
err := h.Client.List(ctx, appSetList)
err := h.Client.List(context.Background(), appSetList)
if err != nil {
h.Log.WithError(err).Error("unable to list ApplicationSets")
return
@@ -68,6 +66,7 @@ func (h *clusterSecretEventHandler) queueRelatedAppGenerators(ctx context.Contex
h.Log.WithField("count", len(appSetList.Items)).Info("listed ApplicationSets")
for _, appSet := range appSetList.Items {
foundClusterGenerator := false
for _, generator := range appSet.Spec.Generators {
if generator.Clusters != nil {
@@ -110,6 +109,7 @@ func (h *clusterSecretEventHandler) queueRelatedAppGenerators(ctx context.Contex
}
}
if foundClusterGenerator {
// TODO: only queue the AppGenerator if the labels match this cluster
req := ctrl.Request{NamespacedName: types.NamespacedName{Namespace: appSet.Namespace, Name: appSet.Name}}
q.Add(req)

View File

@@ -3,39 +3,29 @@ package controllers
import (
"testing"
argocommon "github.com/argoproj/argo-cd/v3/common"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
argov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/applicationset/generators"
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
type mockAddRateLimitingInterface struct {
addedItems []reconcile.Request
}
// Add checks the type, and adds it to the internal list of received additions
func (obj *mockAddRateLimitingInterface) Add(item reconcile.Request) {
obj.addedItems = append(obj.addedItems, item)
}
func TestClusterEventHandler(t *testing.T) {
scheme := runtime.NewScheme()
err := argov1alpha1.AddToScheme(scheme)
require.NoError(t, err)
assert.Nil(t, err)
err = argov1alpha1.AddToScheme(scheme)
require.NoError(t, err)
assert.Nil(t, err)
tests := []struct {
name string
@@ -47,11 +37,11 @@ func TestClusterEventHandler(t *testing.T) {
name: "no application sets should mean no requests",
items: []argov1alpha1.ApplicationSet{},
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
},
},
},
@@ -61,7 +51,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "a cluster generator should produce a request",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set",
Namespace: "argocd",
},
@@ -75,11 +65,11 @@ func TestClusterEventHandler(t *testing.T) {
},
},
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
},
},
},
@@ -91,7 +81,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "multiple cluster generators should produce multiple requests",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set",
Namespace: "argocd",
},
@@ -104,7 +94,7 @@ func TestClusterEventHandler(t *testing.T) {
},
},
{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set2",
Namespace: "argocd",
},
@@ -118,11 +108,11 @@ func TestClusterEventHandler(t *testing.T) {
},
},
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
},
},
},
@@ -135,7 +125,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "non-cluster generator should not match",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set",
Namespace: "another-namespace",
},
@@ -148,7 +138,7 @@ func TestClusterEventHandler(t *testing.T) {
},
},
{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: "app-set-non-cluster",
Namespace: "argocd",
},
@@ -162,11 +152,11 @@ func TestClusterEventHandler(t *testing.T) {
},
},
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
},
},
},
@@ -178,7 +168,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "non-argo cd secret should not match",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set",
Namespace: "another-namespace",
},
@@ -192,7 +182,7 @@ func TestClusterEventHandler(t *testing.T) {
},
},
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-non-argocd-secret",
},
@@ -203,7 +193,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "a matrix generator with a cluster generator should produce a request",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set",
Namespace: "argocd",
},
@@ -223,11 +213,11 @@ func TestClusterEventHandler(t *testing.T) {
},
},
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
},
},
},
@@ -239,7 +229,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "a matrix generator with non cluster generator should not match",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set",
Namespace: "argocd",
},
@@ -259,11 +249,11 @@ func TestClusterEventHandler(t *testing.T) {
},
},
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
},
},
},
@@ -273,7 +263,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "a matrix generator with a nested matrix generator containing a cluster generator should produce a request",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set",
Namespace: "argocd",
},
@@ -309,11 +299,11 @@ func TestClusterEventHandler(t *testing.T) {
},
},
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
},
},
},
@@ -325,7 +315,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "a matrix generator with a nested matrix generator containing non cluster generator should not match",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set",
Namespace: "argocd",
},
@@ -360,11 +350,11 @@ func TestClusterEventHandler(t *testing.T) {
},
},
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
},
},
},
@@ -374,7 +364,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "a merge generator with a cluster generator should produce a request",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set",
Namespace: "argocd",
},
@@ -394,11 +384,11 @@ func TestClusterEventHandler(t *testing.T) {
},
},
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
},
},
},
@@ -410,7 +400,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "a matrix generator with non cluster generator should not match",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set",
Namespace: "argocd",
},
@@ -430,11 +420,11 @@ func TestClusterEventHandler(t *testing.T) {
},
},
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
},
},
},
@@ -444,7 +434,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "a merge generator with a nested merge generator containing a cluster generator should produce a request",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set",
Namespace: "argocd",
},
@@ -480,11 +470,11 @@ func TestClusterEventHandler(t *testing.T) {
},
},
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
},
},
},
@@ -496,7 +486,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "a merge generator with a nested merge generator containing non cluster generator should not match",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set",
Namespace: "argocd",
},
@@ -531,11 +521,11 @@ func TestClusterEventHandler(t *testing.T) {
},
},
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
},
},
},
@@ -544,7 +534,9 @@ func TestClusterEventHandler(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
appSetList := argov1alpha1.ApplicationSetList{
Items: test.items,
}
@@ -558,11 +550,28 @@ func TestClusterEventHandler(t *testing.T) {
mockAddRateLimitingInterface := mockAddRateLimitingInterface{}
handler.queueRelatedAppGenerators(t.Context(), &mockAddRateLimitingInterface, &test.secret)
handler.queueRelatedAppGenerators(&mockAddRateLimitingInterface, &test.secret)
assert.False(t, mockAddRateLimitingInterface.errorOccurred)
assert.ElementsMatch(t, mockAddRateLimitingInterface.addedItems, test.expectedRequests)
})
}
}
// Add checks the type, and adds it to the internal list of received additions
func (obj *mockAddRateLimitingInterface) Add(item interface{}) {
if req, ok := item.(ctrl.Request); ok {
obj.addedItems = append(obj.addedItems, req)
} else {
obj.errorOccurred = true
}
}
type mockAddRateLimitingInterface struct {
errorOccurred bool
addedItems []ctrl.Request
}
func TestNestedGeneratorHasClusterGenerator_NestedClusterGenerator(t *testing.T) {
@@ -572,7 +581,7 @@ func TestNestedGeneratorHasClusterGenerator_NestedClusterGenerator(t *testing.T)
hasClusterGenerator, err := nestedGeneratorHasClusterGenerator(nested)
require.NoError(t, err)
assert.Nil(t, err)
assert.True(t, hasClusterGenerator)
}
@@ -599,7 +608,7 @@ func TestNestedGeneratorHasClusterGenerator_NestedMergeGenerator(t *testing.T) {
hasClusterGenerator, err := nestedGeneratorHasClusterGenerator(nested)
require.NoError(t, err)
assert.Nil(t, err)
assert.True(t, hasClusterGenerator)
}
@@ -626,6 +635,6 @@ func TestNestedGeneratorHasClusterGenerator_NestedMergeGeneratorWithInvalidJSON(
hasClusterGenerator, err := nestedGeneratorHasClusterGenerator(nested)
require.Error(t, err)
assert.NotNil(t, err)
assert.False(t, hasClusterGenerator)
}

View File

@@ -1,11 +1,11 @@
package controllers
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
@@ -15,18 +15,17 @@ import (
"k8s.io/client-go/tools/record"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/argoproj/argo-cd/v3/applicationset/generators"
appsetmetrics "github.com/argoproj/argo-cd/v3/applicationset/metrics"
"github.com/argoproj/argo-cd/v3/applicationset/services/mocks"
argov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/applicationset/generators"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func TestRequeueAfter(t *testing.T) {
mockServer := &mocks.Repos{}
ctx := t.Context()
ctx := context.Background()
scheme := runtime.NewScheme()
err := argov1alpha1.AddToScheme(scheme)
require.NoError(t, err)
assert.Nil(t, err)
gvrToListKind := map[schema.GroupVersionResource]string{{
Group: "mallard.io",
Version: "v1",
@@ -35,20 +34,20 @@ func TestRequeueAfter(t *testing.T) {
appClientset := kubefake.NewSimpleClientset()
k8sClient := fake.NewClientBuilder().Build()
duckType := &unstructured.Unstructured{
Object: map[string]any{
Object: map[string]interface{}{
"apiVersion": "v2quack",
"kind": "Duck",
"metadata": map[string]any{
"metadata": map[string]interface{}{
"name": "mightyduck",
"namespace": "namespace",
"labels": map[string]any{"duck": "all-species"},
"labels": map[string]interface{}{"duck": "all-species"},
},
"status": map[string]any{
"decisions": []any{
map[string]any{
"status": map[string]interface{}{
"decisions": []interface{}{
map[string]interface{}{
"clusterName": "staging-01",
},
map[string]any{
map[string]interface{}{
"clusterName": "production-01",
},
},
@@ -56,14 +55,14 @@ func TestRequeueAfter(t *testing.T) {
},
}
fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, duckType)
scmConfig := generators.NewSCMConfig("", []string{""}, true, true, nil, true)
terminalGenerators := map[string]generators.Generator{
"List": generators.NewListGenerator(),
"Clusters": generators.NewClusterGenerator(ctx, k8sClient, appClientset, "argocd"),
"Git": generators.NewGitGenerator(mockServer, "namespace"),
"SCMProvider": generators.NewSCMProviderGenerator(fake.NewClientBuilder().WithObjects(&corev1.Secret{}).Build(), scmConfig),
"Clusters": generators.NewClusterGenerator(k8sClient, ctx, appClientset, "argocd"),
"Git": generators.NewGitGenerator(mockServer),
"SCMProvider": generators.NewSCMProviderGenerator(fake.NewClientBuilder().WithObjects(&corev1.Secret{}).Build(), generators.SCMAuthProviders{}, "", []string{""}),
"ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, fakeDynClient, appClientset, "argocd"),
"PullRequest": generators.NewPullRequestGenerator(k8sClient, scmConfig),
"PullRequest": generators.NewPullRequestGenerator(k8sClient, generators.SCMAuthProviders{}, "", []string{""}),
}
nestedGenerators := map[string]generators.Generator{
@@ -89,18 +88,15 @@ func TestRequeueAfter(t *testing.T) {
}
client := fake.NewClientBuilder().WithScheme(scheme).Build()
metrics := appsetmetrics.NewFakeAppsetMetrics()
r := ApplicationSetReconciler{
Client: client,
Scheme: scheme,
Recorder: record.NewFakeRecorder(0),
Generators: topLevelGenerators,
Metrics: metrics,
}
type args struct {
appset *argov1alpha1.ApplicationSet
requeueAfterOverride string
appset *argov1alpha1.ApplicationSet
}
tests := []struct {
name string
@@ -108,13 +104,11 @@ func TestRequeueAfter(t *testing.T) {
want time.Duration
wantErr assert.ErrorAssertionFunc
}{
{name: "Cluster", args: args{
appset: &argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{{Clusters: &argov1alpha1.ClusterGenerator{}}},
},
}, requeueAfterOverride: "",
}, want: generators.NoRequeueAfter, wantErr: assert.NoError},
{name: "Cluster", args: args{appset: &argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{{Clusters: &argov1alpha1.ClusterGenerator{}}},
},
}}, want: generators.NoRequeueAfter, wantErr: assert.NoError},
{name: "ClusterMergeNested", args: args{&argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{
@@ -129,7 +123,7 @@ func TestRequeueAfter(t *testing.T) {
}},
},
},
}, ""}, want: generators.DefaultRequeueAfter, wantErr: assert.NoError},
}}, want: generators.DefaultRequeueAfterSeconds, wantErr: assert.NoError},
{name: "ClusterMatrixNested", args: args{&argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{
@@ -144,65 +138,15 @@ func TestRequeueAfter(t *testing.T) {
}},
},
},
}, ""}, want: generators.DefaultRequeueAfter, wantErr: assert.NoError},
}}, want: generators.DefaultRequeueAfterSeconds, wantErr: assert.NoError},
{name: "ListGenerator", args: args{appset: &argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{{List: &argov1alpha1.ListGenerator{}}},
},
}}, want: generators.NoRequeueAfter, wantErr: assert.NoError},
{name: "DuckGenerator", args: args{appset: &argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{{ClusterDecisionResource: &argov1alpha1.DuckTypeGenerator{}}},
},
}}, want: generators.DefaultRequeueAfter, wantErr: assert.NoError},
{name: "OverrideRequeueDuck", args: args{
appset: &argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{{ClusterDecisionResource: &argov1alpha1.DuckTypeGenerator{}}},
},
}, requeueAfterOverride: "1h",
}, want: 1 * time.Hour, wantErr: assert.NoError},
{name: "OverrideRequeueGit", args: args{&argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{
{Git: &argov1alpha1.GitGenerator{}},
},
},
}, "1h"}, want: 1 * time.Hour, wantErr: assert.NoError},
{name: "OverrideRequeueMatrix", args: args{&argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{
{Clusters: &argov1alpha1.ClusterGenerator{}},
{Merge: &argov1alpha1.MergeGenerator{
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
{
Clusters: &argov1alpha1.ClusterGenerator{},
Git: &argov1alpha1.GitGenerator{},
},
},
}},
},
},
}, "5m"}, want: 5 * time.Minute, wantErr: assert.NoError},
{name: "OverrideRequeueMerge", args: args{&argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{
{Clusters: &argov1alpha1.ClusterGenerator{}},
{Merge: &argov1alpha1.MergeGenerator{
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
{
Clusters: &argov1alpha1.ClusterGenerator{},
Git: &argov1alpha1.GitGenerator{},
},
},
}},
},
},
}, "12s"}, want: 12 * time.Second, wantErr: assert.NoError},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv("ARGOCD_APPLICATIONSET_CONTROLLER_REQUEUE_AFTER", tt.args.requeueAfterOverride)
assert.Equalf(t, tt.want, r.getMinRequeueAfter(tt.args.appset), "getMinRequeueAfter(%v)", tt.args.appset)
})
}

View File

@@ -1,43 +0,0 @@
package template
import (
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"github.com/argoproj/argo-cd/v3/applicationset/utils"
appv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
)
func applyTemplatePatch(app *appv1.Application, templatePatch string) (*appv1.Application, error) {
appString, err := json.Marshal(app)
if err != nil {
return nil, fmt.Errorf("error while marhsalling Application %w", err)
}
convertedTemplatePatch, err := utils.ConvertYAMLToJSON(templatePatch)
if err != nil {
return nil, fmt.Errorf("error while converting template to json %q: %w", convertedTemplatePatch, err)
}
if err := json.Unmarshal([]byte(convertedTemplatePatch), &appv1.Application{}); err != nil {
return nil, fmt.Errorf("invalid templatePatch %q: %w", convertedTemplatePatch, err)
}
data, err := strategicpatch.StrategicMergePatch(appString, []byte(convertedTemplatePatch), appv1.Application{})
if err != nil {
return nil, fmt.Errorf("error while applying templatePatch template to json %q: %w", convertedTemplatePatch, err)
}
finalApp := appv1.Application{}
err = json.Unmarshal(data, &finalApp)
if err != nil {
return nil, fmt.Errorf("error while unmarhsalling patched application: %w", err)
}
// Prevent changes to the `project` field. This helps prevent malicious template patches
finalApp.Spec.Project = app.Spec.Project
return &finalApp, nil
}

View File

@@ -1,249 +0,0 @@
package template
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
appv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
)
func Test_ApplyTemplatePatch(t *testing.T) {
testCases := []struct {
name string
appTemplate *appv1.Application
templatePatch string
expectedApp *appv1.Application
}{
{
name: "patch with JSON",
appTemplate: &appv1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-guestbook",
Namespace: "namespace",
Finalizers: []string{appv1.ResourcesFinalizerName},
},
Spec: appv1.ApplicationSpec{
Project: "default",
Source: &appv1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: appv1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "guestbook",
},
},
},
templatePatch: `{
"metadata": {
"annotations": {
"annotation-some-key": "annotation-some-value"
}
},
"spec": {
"source": {
"helm": {
"valueFiles": [
"values.test.yaml",
"values.big.yaml"
]
}
},
"syncPolicy": {
"automated": {
"prune": true
}
}
}
}`,
expectedApp: &appv1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-guestbook",
Namespace: "namespace",
Finalizers: []string{appv1.ResourcesFinalizerName},
Annotations: map[string]string{
"annotation-some-key": "annotation-some-value",
},
},
Spec: appv1.ApplicationSpec{
Project: "default",
Source: &appv1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
Helm: &appv1.ApplicationSourceHelm{
ValueFiles: []string{
"values.test.yaml",
"values.big.yaml",
},
},
},
Destination: appv1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "guestbook",
},
SyncPolicy: &appv1.SyncPolicy{
Automated: &appv1.SyncPolicyAutomated{
Prune: true,
},
},
},
},
},
{
name: "patch with YAML",
appTemplate: &appv1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-guestbook",
Namespace: "namespace",
Finalizers: []string{appv1.ResourcesFinalizerName},
},
Spec: appv1.ApplicationSpec{
Project: "default",
Source: &appv1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: appv1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "guestbook",
},
},
},
templatePatch: `
metadata:
annotations:
annotation-some-key: annotation-some-value
spec:
source:
helm:
valueFiles:
- values.test.yaml
- values.big.yaml
syncPolicy:
automated:
prune: true`,
expectedApp: &appv1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-guestbook",
Namespace: "namespace",
Finalizers: []string{appv1.ResourcesFinalizerName},
Annotations: map[string]string{
"annotation-some-key": "annotation-some-value",
},
},
Spec: appv1.ApplicationSpec{
Project: "default",
Source: &appv1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
Helm: &appv1.ApplicationSourceHelm{
ValueFiles: []string{
"values.test.yaml",
"values.big.yaml",
},
},
},
Destination: appv1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "guestbook",
},
SyncPolicy: &appv1.SyncPolicy{
Automated: &appv1.SyncPolicyAutomated{
Prune: true,
},
},
},
},
},
{
name: "project field isn't overwritten",
appTemplate: &appv1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-guestbook",
Namespace: "namespace",
},
Spec: appv1.ApplicationSpec{
Project: "default",
Source: &appv1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: appv1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "guestbook",
},
},
},
templatePatch: `
spec:
project: my-project`,
expectedApp: &appv1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-guestbook",
Namespace: "namespace",
},
Spec: appv1.ApplicationSpec{
Project: "default",
Source: &appv1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: appv1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "guestbook",
},
},
},
},
}
for _, tc := range testCases {
tcc := tc
t.Run(tcc.name, func(t *testing.T) {
result, err := applyTemplatePatch(tcc.appTemplate, tcc.templatePatch)
require.NoError(t, err)
assert.Equal(t, *tcc.expectedApp, *result)
})
}
}
func TestError(t *testing.T) {
app := &appv1.Application{}
result, err := applyTemplatePatch(app, "hello world")
require.Error(t, err)
require.Nil(t, result)
}

View File

@@ -1,101 +0,0 @@
package template
import (
"fmt"
"sigs.k8s.io/controller-runtime/pkg/client"
log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v3/applicationset/generators"
"github.com/argoproj/argo-cd/v3/applicationset/utils"
argov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
)
func GenerateApplications(logCtx *log.Entry, applicationSetInfo argov1alpha1.ApplicationSet, g map[string]generators.Generator, renderer utils.Renderer, client client.Client) ([]argov1alpha1.Application, argov1alpha1.ApplicationSetReasonType, error) {
var res []argov1alpha1.Application
var firstError error
var applicationSetReason argov1alpha1.ApplicationSetReasonType
for _, requestedGenerator := range applicationSetInfo.Spec.Generators {
t, err := generators.Transform(requestedGenerator, g, applicationSetInfo.Spec.Template, &applicationSetInfo, map[string]any{}, client)
if err != nil {
logCtx.WithError(err).WithField("generator", requestedGenerator).
Error("error generating application from params")
if firstError == nil {
firstError = err
applicationSetReason = argov1alpha1.ApplicationSetReasonApplicationParamsGenerationError
}
continue
}
for _, a := range t {
tmplApplication := GetTempApplication(a.Template)
for _, p := range a.Params {
app, err := renderer.RenderTemplateParams(tmplApplication, applicationSetInfo.Spec.SyncPolicy, p, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions)
if err != nil {
logCtx.WithError(err).WithField("params", a.Params).WithField("generator", requestedGenerator).
Error("error generating application from params")
if firstError == nil {
firstError = err
applicationSetReason = argov1alpha1.ApplicationSetReasonRenderTemplateParamsError
}
continue
}
if applicationSetInfo.Spec.TemplatePatch != nil {
patchedApplication, err := renderTemplatePatch(renderer, app, applicationSetInfo, p)
if err != nil {
log.WithError(err).WithField("params", a.Params).WithField("generator", requestedGenerator).
Error("error generating application from params")
if firstError == nil {
firstError = err
applicationSetReason = argov1alpha1.ApplicationSetReasonRenderTemplateParamsError
}
continue
}
app = patchedApplication
}
// The app's namespace must be the same as the AppSet's namespace to preserve the appsets-in-any-namespace
// security boundary.
app.Namespace = applicationSetInfo.Namespace
res = append(res, *app)
}
}
if log.IsLevelEnabled(log.DebugLevel) {
logCtx.WithField("generator", requestedGenerator).Debugf("apps from generator: %+v", res)
} else {
logCtx.Infof("generated %d applications", len(res))
}
}
return res, applicationSetReason, firstError
}
func renderTemplatePatch(r utils.Renderer, app *argov1alpha1.Application, applicationSetInfo argov1alpha1.ApplicationSet, params map[string]any) (*argov1alpha1.Application, error) {
replacedTemplate, err := r.Replace(*applicationSetInfo.Spec.TemplatePatch, params, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions)
if err != nil {
return nil, fmt.Errorf("error replacing values in templatePatch: %w", err)
}
return applyTemplatePatch(app, replacedTemplate)
}
func GetTempApplication(applicationSetTemplate argov1alpha1.ApplicationSetTemplate) *argov1alpha1.Application {
var tmplApplication argov1alpha1.Application
tmplApplication.Annotations = applicationSetTemplate.Annotations
tmplApplication.Labels = applicationSetTemplate.Labels
tmplApplication.Namespace = applicationSetTemplate.Namespace
tmplApplication.Name = applicationSetTemplate.Name
tmplApplication.Spec = applicationSetTemplate.Spec
tmplApplication.Finalizers = applicationSetTemplate.Finalizers
return &tmplApplication
}

View File

@@ -1,350 +0,0 @@
package template
import (
"errors"
"maps"
"testing"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"github.com/argoproj/argo-cd/v3/applicationset/generators"
genmock "github.com/argoproj/argo-cd/v3/applicationset/generators/mocks"
"github.com/argoproj/argo-cd/v3/applicationset/utils"
rendmock "github.com/argoproj/argo-cd/v3/applicationset/utils/mocks"
"github.com/argoproj/argo-cd/v3/pkg/apis/application"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
)
func TestGenerateApplications(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
err = v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
for _, c := range []struct {
name string
params []map[string]any
template v1alpha1.ApplicationSetTemplate
generateParamsError error
rendererError error
expectErr bool
expectedReason v1alpha1.ApplicationSetReasonType
}{
{
name: "Generate two applications",
params: []map[string]any{{"name": "app1"}, {"name": "app2"}},
template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "name",
Namespace: "namespace",
Labels: map[string]string{"label_name": "label_value"},
},
Spec: v1alpha1.ApplicationSpec{},
},
expectedReason: "",
},
{
name: "Handles error from the generator",
generateParamsError: errors.New("error"),
expectErr: true,
expectedReason: v1alpha1.ApplicationSetReasonApplicationParamsGenerationError,
},
{
name: "Handles error from the render",
params: []map[string]any{{"name": "app1"}, {"name": "app2"}},
template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "name",
Namespace: "namespace",
Labels: map[string]string{"label_name": "label_value"},
},
Spec: v1alpha1.ApplicationSpec{},
},
rendererError: errors.New("error"),
expectErr: true,
expectedReason: v1alpha1.ApplicationSetReasonRenderTemplateParamsError,
},
} {
cc := c
app := v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "namespace",
},
TypeMeta: metav1.TypeMeta{
Kind: application.ApplicationKind,
APIVersion: "argoproj.io/v1alpha1",
},
}
t.Run(cc.name, func(t *testing.T) {
generatorMock := &genmock.Generator{}
generator := v1alpha1.ApplicationSetGenerator{
List: &v1alpha1.ListGenerator{},
}
generatorMock.EXPECT().GenerateParams(&generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).
Return(cc.params, cc.generateParamsError)
generatorMock.EXPECT().GetTemplate(&generator).
Return(&v1alpha1.ApplicationSetTemplate{})
rendererMock := &rendmock.Renderer{}
var expectedApps []v1alpha1.Application
if cc.generateParamsError == nil {
for _, p := range cc.params {
if cc.rendererError != nil {
rendererMock.EXPECT().RenderTemplateParams(GetTempApplication(cc.template), mock.AnythingOfType("*v1alpha1.ApplicationSetSyncPolicy"), p, false, []string(nil)).
Return(nil, cc.rendererError)
} else {
rendererMock.EXPECT().RenderTemplateParams(GetTempApplication(cc.template), mock.AnythingOfType("*v1alpha1.ApplicationSetSyncPolicy"), p, false, []string(nil)).
Return(&app, nil)
expectedApps = append(expectedApps, app)
}
}
}
generators := map[string]generators.Generator{
"List": generatorMock,
}
renderer := rendererMock
got, reason, err := GenerateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
Spec: v1alpha1.ApplicationSetSpec{
Generators: []v1alpha1.ApplicationSetGenerator{generator},
Template: cc.template,
},
},
generators,
renderer,
nil,
)
if cc.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
assert.Equal(t, expectedApps, got)
assert.Equal(t, cc.expectedReason, reason)
generatorMock.AssertNumberOfCalls(t, "GenerateParams", 1)
if cc.generateParamsError == nil {
rendererMock.AssertNumberOfCalls(t, "RenderTemplateParams", len(cc.params))
}
})
}
}
func TestMergeTemplateApplications(t *testing.T) {
for _, c := range []struct {
name string
params []map[string]any
template v1alpha1.ApplicationSetTemplate
overrideTemplate v1alpha1.ApplicationSetTemplate
expectedMerged v1alpha1.ApplicationSetTemplate
expectedApps []v1alpha1.Application
}{
{
name: "Generate app",
params: []map[string]any{{"name": "app1"}},
template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "name",
Namespace: "namespace",
Labels: map[string]string{"label_name": "label_value"},
},
Spec: v1alpha1.ApplicationSpec{},
},
overrideTemplate: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "test",
Labels: map[string]string{"foo": "bar"},
},
Spec: v1alpha1.ApplicationSpec{},
},
expectedMerged: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "test",
Namespace: "namespace",
Labels: map[string]string{"label_name": "label_value", "foo": "bar"},
},
Spec: v1alpha1.ApplicationSpec{},
},
expectedApps: []v1alpha1.Application{
{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "test",
Labels: map[string]string{"foo": "bar"},
},
Spec: v1alpha1.ApplicationSpec{},
},
},
},
} {
cc := c
t.Run(cc.name, func(t *testing.T) {
generatorMock := &genmock.Generator{}
generator := v1alpha1.ApplicationSetGenerator{
List: &v1alpha1.ListGenerator{},
}
generatorMock.EXPECT().GenerateParams(&generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).
Return(cc.params, nil)
generatorMock.EXPECT().GetTemplate(&generator).
Return(&cc.overrideTemplate)
rendererMock := &rendmock.Renderer{}
rendererMock.EXPECT().RenderTemplateParams(GetTempApplication(cc.expectedMerged), mock.AnythingOfType("*v1alpha1.ApplicationSetSyncPolicy"), cc.params[0], false, []string(nil)).
Return(&cc.expectedApps[0], nil)
generators := map[string]generators.Generator{
"List": generatorMock,
}
renderer := rendererMock
got, _, _ := GenerateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
Spec: v1alpha1.ApplicationSetSpec{
Generators: []v1alpha1.ApplicationSetGenerator{generator},
Template: cc.template,
},
},
generators,
renderer,
nil,
)
assert.Equal(t, cc.expectedApps, got)
})
}
}
// Test app generation from a go template application set using a pull request generator
func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) {
for _, cases := range []struct {
name string
params []map[string]any
template v1alpha1.ApplicationSetTemplate
expectedApp []v1alpha1.Application
}{
{
name: "Generate an application from a go template application set manifest using a pull request generator",
params: []map[string]any{
{
"number": "1",
"title": "title1",
"branch": "branch1",
"branch_slug": "branchSlug1",
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb",
"branch_slugify_default": "feat/a_really+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
"branch_slugify_smarttruncate_disabled": "feat/areallylongpullrequestnametotestargoslugificationandbranchnameshorteningfeature",
"branch_slugify_smarttruncate_enabled": "feat/testwithsmarttruncateenabledramdomlonglistofcharacters",
"labels": []string{"label1"},
},
},
template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "AppSet-{{.branch}}-{{.number}}",
Labels: map[string]string{
"app1": "{{index .labels 0}}",
"branch-test1": "AppSet-{{.branch_slugify_default | slugify }}",
"branch-test2": "AppSet-{{.branch_slugify_smarttruncate_disabled | slugify 49 false }}",
"branch-test3": "AppSet-{{.branch_slugify_smarttruncate_enabled | slugify 50 true }}",
},
},
Spec: v1alpha1.ApplicationSpec{
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://testurl/testRepo",
TargetRevision: "{{.head_short_sha}}",
},
Destination: v1alpha1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "AppSet-{{.branch_slug}}-{{.head_sha}}",
},
},
},
expectedApp: []v1alpha1.Application{
{
ObjectMeta: metav1.ObjectMeta{
Name: "AppSet-branch1-1",
Labels: map[string]string{
"app1": "label1",
"branch-test1": "AppSet-feat-a-really-long-pull-request-name-to-test-argo",
"branch-test2": "AppSet-feat-areallylongpullrequestnametotestargoslugific",
"branch-test3": "AppSet-feat",
},
},
Spec: v1alpha1.ApplicationSpec{
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://testurl/testRepo",
TargetRevision: "089d92cb",
},
Destination: v1alpha1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "AppSet-branchSlug1-089d92cbf9ff857a39e6feccd32798ca700fb958",
},
},
},
},
},
} {
t.Run(cases.name, func(t *testing.T) {
generatorMock := &genmock.Generator{}
generator := v1alpha1.ApplicationSetGenerator{
PullRequest: &v1alpha1.PullRequestGenerator{},
}
generatorMock.EXPECT().GenerateParams(&generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).
Return(cases.params, nil)
generatorMock.EXPECT().GetTemplate(&generator).
Return(&cases.template)
generators := map[string]generators.Generator{
"PullRequest": generatorMock,
}
renderer := &utils.Render{}
gotApp, _, _ := GenerateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{
Spec: v1alpha1.ApplicationSetSpec{
GoTemplate: true,
Generators: []v1alpha1.ApplicationSetGenerator{{
PullRequest: &v1alpha1.PullRequestGenerator{},
}},
Template: cases.template,
},
},
generators,
renderer,
nil,
)
assert.Equal(t, cases.expectedApp[0].Name, gotApp[0].Name)
assert.Equal(t, cases.expectedApp[0].Spec.Source.TargetRevision, gotApp[0].Spec.Source.TargetRevision)
assert.Equal(t, cases.expectedApp[0].Spec.Destination.Namespace, gotApp[0].Spec.Destination.Namespace)
assert.True(t, maps.Equal(cases.expectedApp[0].Labels, gotApp[0].Labels))
})
}
}

View File

@@ -14,7 +14,7 @@ spec:
app: guestbook-ui
spec:
containers:
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
name: guestbook-ui
ports:
- containerPort: 80

View File

@@ -14,7 +14,7 @@ spec:
app: guestbook-ui
spec:
containers:
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
name: guestbook-ui
ports:
- containerPort: 80

View File

@@ -5,7 +5,7 @@
replicaCount: 1
image:
repository: quay.io/argoprojlabs/argocd-e2e-container
repository: gcr.io/heptio-images/ks-guestbook-demo
tag: 0.1
pullPolicy: IfNotPresent

View File

@@ -14,7 +14,7 @@ spec:
app: guestbook-ui
spec:
containers:
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
name: guestbook-ui
ports:
- containerPort: 80

View File

@@ -1,26 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: guestbook
spec:
generators:
- git:
repoURL: https://github.com/argoproj/argo-cd.git
revision: HEAD
files:
- path: "applicationset/examples/git-generator-files-discovery/cluster-config/**/config.json"
- path: "applicationset/examples/git-generator-files-discovery/cluster-config/*/dev/config.json"
exclude: true
template:
metadata:
name: '{{cluster.name}}-guestbook'
spec:
project: default
source:
repoURL: https://github.com/argoproj/argo-cd.git
targetRevision: HEAD
path: "applicationset/examples/git-generator-files-discovery/apps/guestbook"
destination:
server: https://kubernetes.default.svc
#server: '{{cluster.address}}'
namespace: guestbook

View File

@@ -1,27 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: guestbook
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- git:
repoURL: https://github.com/argoproj/argo-cd.git
revision: HEAD
files:
- path: "applicationset/examples/git-generator-files-discovery/cluster-config/**/config.json"
- path: "applicationset/examples/git-generator-files-discovery/cluster-config/*/dev/config.json"
exclude: true
template:
metadata:
name: '{{.cluster.name}}-guestbook'
spec:
project: default
source:
repoURL: https://github.com/argoproj/argo-cd.git
targetRevision: HEAD
path: "applicationset/examples/git-generator-files-discovery/apps/guestbook"
destination:
server: https://kubernetes.default.svc
namespace: guestbook

View File

@@ -14,7 +14,7 @@ spec:
app: guestbook-ui
spec:
containers:
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
name: guestbook-ui
ports:
- containerPort: 80

View File

@@ -14,7 +14,7 @@ spec:
app: guestbook-ui
spec:
containers:
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
name: guestbook-ui
ports:
- containerPort: 80

View File

@@ -1,26 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: guestbook
spec:
generators:
- scmProvider:
gitlab:
api: https://gitlab.com
group: test-argocd-proton
includeSubgroups: true
cloneProtocol: https
filters:
- repositoryMatch: test-app
template:
metadata:
name: '{{ repository }}-guestbook'
spec:
project: "default"
source:
repoURL: '{{ url }}'
targetRevision: '{{ branch }}'
path: guestbook
destination:
server: https://kubernetes.default.svc
namespace: guestbook

View File

@@ -14,7 +14,7 @@ spec:
app: guestbook-ui
spec:
containers:
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
name: guestbook-ui
ports:
- containerPort: 80

View File

@@ -14,7 +14,7 @@ spec:
app: guestbook-ui
spec:
containers:
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
name: guestbook-ui
ports:
- containerPort: 80

View File

@@ -7,14 +7,20 @@ import (
log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v2/util/settings"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v3/applicationset/utils"
"github.com/argoproj/argo-cd/v3/common"
argoappsetv1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
argoappsetv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
const (
ArgoCDSecretTypeLabel = "argocd.argoproj.io/secret-type"
ArgoCDSecretTypeCluster = "cluster"
)
var _ Generator = (*ClusterGenerator)(nil)
@@ -25,24 +31,29 @@ type ClusterGenerator struct {
ctx context.Context
clientset kubernetes.Interface
// namespace is the Argo CD namespace
namespace string
namespace string
settingsManager *settings.SettingsManager
}
var render = &utils.Render{}
func NewClusterGenerator(ctx context.Context, c client.Client, clientset kubernetes.Interface, namespace string) Generator {
func NewClusterGenerator(c client.Client, ctx context.Context, clientset kubernetes.Interface, namespace string) Generator {
settingsManager := settings.NewSettingsManager(ctx, clientset, namespace)
g := &ClusterGenerator{
Client: c,
ctx: ctx,
clientset: clientset,
namespace: namespace,
Client: c,
ctx: ctx,
clientset: clientset,
namespace: namespace,
settingsManager: settingsManager,
}
return g
}
// GetRequeueAfter never requeue the cluster generator because the `clusterSecretEventHandler` will requeue the appsets
// when the cluster secrets change
func (g *ClusterGenerator) GetRequeueAfter(_ *argoappsetv1alpha1.ApplicationSetGenerator) time.Duration {
func (g *ClusterGenerator) GetRequeueAfter(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator) time.Duration {
return NoRequeueAfter
}
@@ -50,146 +61,120 @@ func (g *ClusterGenerator) GetTemplate(appSetGenerator *argoappsetv1alpha1.Appli
return &appSetGenerator.Clusters.Template
}
func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator, appSet *argoappsetv1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) {
logCtx := log.WithField("applicationset", appSet.GetName()).WithField("namespace", appSet.GetNamespace())
func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator, appSet *argoappsetv1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil {
return nil, ErrEmptyAppSetGenerator
return nil, EmptyAppSetGeneratorError
}
if appSetGenerator.Clusters == nil {
return nil, ErrEmptyAppSetGenerator
return nil, EmptyAppSetGeneratorError
}
// Do not include the local cluster in the cluster parameters IF there is a non-empty selector
// - Since local clusters do not have secrets, they do not have labels to match against
ignoreLocalClusters := len(appSetGenerator.Clusters.Selector.MatchExpressions) > 0 || len(appSetGenerator.Clusters.Selector.MatchLabels) > 0
// ListCluster will include the local cluster in the list of clusters
// ListCluster from Argo CD's util/db package will include the local cluster in the list of clusters
clustersFromArgoCD, err := utils.ListClusters(g.ctx, g.clientset, g.namespace)
if err != nil {
return nil, fmt.Errorf("error listing clusters: %w", err)
return nil, err
}
if clustersFromArgoCD == nil {
return nil, nil
}
clusterSecrets, err := g.getSecretsByClusterName(logCtx, appSetGenerator)
clusterSecrets, err := g.getSecretsByClusterName(appSetGenerator)
if err != nil {
return nil, fmt.Errorf("error getting cluster secrets: %w", err)
return nil, err
}
paramHolder := &paramHolder{isFlatMode: appSetGenerator.Clusters.FlatList}
logCtx.Debugf("Using flat mode = %t for cluster generator", paramHolder.isFlatMode)
res := []map[string]interface{}{}
secretsFound := []corev1.Secret{}
for _, cluster := range clustersFromArgoCD {
for _, cluster := range clustersFromArgoCD.Items {
// If there is a secret for this cluster, then it's a non-local cluster, so it will be
// handled by the next step.
if secretForCluster, exists := clusterSecrets[cluster.Name]; exists {
secretsFound = append(secretsFound, secretForCluster)
} else if !ignoreLocalClusters {
// If there is no secret for the cluster, it's the local cluster, so handle it here.
params := map[string]any{}
params := map[string]interface{}{}
params["name"] = cluster.Name
params["nameNormalized"] = cluster.Name
params["server"] = cluster.Server
params["project"] = ""
err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
if err != nil {
return nil, fmt.Errorf("error appending templated values for local cluster: %w", err)
return nil, err
}
paramHolder.append(params)
logCtx.WithField("cluster", "local cluster").Info("matched local cluster")
res = append(res, params)
log.WithField("cluster", "local cluster").Info("matched local cluster")
}
}
// For each matching cluster secret (non-local clusters only)
for _, cluster := range secretsFound {
params := g.getClusterParameters(cluster, appSet)
params := map[string]interface{}{}
params["name"] = string(cluster.Data["name"])
params["nameNormalized"] = utils.SanitizeName(string(cluster.Data["name"]))
params["server"] = string(cluster.Data["server"])
if appSet.Spec.GoTemplate {
meta := map[string]interface{}{}
if len(cluster.ObjectMeta.Annotations) > 0 {
meta["annotations"] = cluster.ObjectMeta.Annotations
}
if len(cluster.ObjectMeta.Labels) > 0 {
meta["labels"] = cluster.ObjectMeta.Labels
}
params["metadata"] = meta
} else {
for key, value := range cluster.ObjectMeta.Annotations {
params[fmt.Sprintf("metadata.annotations.%s", key)] = value
}
for key, value := range cluster.ObjectMeta.Labels {
params[fmt.Sprintf("metadata.labels.%s", key)] = value
}
}
err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
if err != nil {
return nil, fmt.Errorf("error appending templated values for cluster: %w", err)
return nil, err
}
paramHolder.append(params)
logCtx.WithField("cluster", cluster.Name).Debug("matched cluster secret")
res = append(res, params)
log.WithField("cluster", cluster.Name).Info("matched cluster secret")
}
return paramHolder.consolidate(), nil
return res, nil
}
type paramHolder struct {
isFlatMode bool
params []map[string]any
}
func (p *paramHolder) append(params map[string]any) {
p.params = append(p.params, params)
}
func (p *paramHolder) consolidate() []map[string]any {
if p.isFlatMode {
p.params = []map[string]any{
{"clusters": p.params},
}
}
return p.params
}
func (g *ClusterGenerator) getClusterParameters(cluster corev1.Secret, appSet *argoappsetv1alpha1.ApplicationSet) map[string]any {
params := map[string]any{}
params["name"] = string(cluster.Data["name"])
params["nameNormalized"] = utils.SanitizeName(string(cluster.Data["name"]))
params["server"] = string(cluster.Data["server"])
project, ok := cluster.Data["project"]
if ok {
params["project"] = string(project)
} else {
params["project"] = ""
}
if appSet.Spec.GoTemplate {
meta := map[string]any{}
if len(cluster.Annotations) > 0 {
meta["annotations"] = cluster.Annotations
}
if len(cluster.Labels) > 0 {
meta["labels"] = cluster.Labels
}
params["metadata"] = meta
} else {
for key, value := range cluster.Annotations {
params["metadata.annotations."+key] = value
}
for key, value := range cluster.Labels {
params["metadata.labels."+key] = value
}
}
return params
}
func (g *ClusterGenerator) getSecretsByClusterName(log *log.Entry, appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator) (map[string]corev1.Secret, error) {
func (g *ClusterGenerator) getSecretsByClusterName(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator) (map[string]corev1.Secret, error) {
// List all Clusters:
clusterSecretList := &corev1.SecretList{}
selector := metav1.AddLabelToSelector(&appSetGenerator.Clusters.Selector, common.LabelKeySecretType, common.LabelValueSecretTypeCluster)
selector := metav1.AddLabelToSelector(&appSetGenerator.Clusters.Selector, ArgoCDSecretTypeLabel, ArgoCDSecretTypeCluster)
secretSelector, err := metav1.LabelSelectorAsSelector(selector)
if err != nil {
return nil, fmt.Errorf("error converting label selector: %w", err)
}
if err := g.List(context.Background(), clusterSecretList, client.MatchingLabelsSelector{Selector: secretSelector}); err != nil {
return nil, err
}
log.Debugf("clusters matching labels: %d", len(clusterSecretList.Items))
if err := g.Client.List(context.Background(), clusterSecretList, client.MatchingLabelsSelector{Selector: secretSelector}); err != nil {
return nil, err
}
log.Debug("clusters matching labels", "count", len(clusterSecretList.Items))
res := map[string]corev1.Secret{}
@@ -200,4 +185,5 @@ func (g *ClusterGenerator) getSecretsByClusterName(log *log.Entry, appSetGenerat
}
return res, nil
}

View File

@@ -2,7 +2,7 @@ package generators
import (
"context"
"errors"
"fmt"
"testing"
corev1 "k8s.io/api/core/v1"
@@ -13,11 +13,10 @@ import (
kubefake "k8s.io/client-go/kubernetes/fake"
"github.com/argoproj/argo-cd/v3/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type possiblyErroringFakeCtrlRuntimeClient struct {
@@ -27,7 +26,7 @@ type possiblyErroringFakeCtrlRuntimeClient struct {
func (p *possiblyErroringFakeCtrlRuntimeClient) List(ctx context.Context, secretList client.ObjectList, opts ...client.ListOption) error {
if p.shouldError {
return errors.New("could not list Secrets")
return fmt.Errorf("could not list Secrets")
}
return p.Client.List(ctx, secretList, opts...)
}
@@ -76,20 +75,18 @@ func TestGenerateParams(t *testing.T) {
},
},
Data: map[string][]byte{
"config": []byte("{}"),
"name": []byte("production_01/west"),
"server": []byte("https://production-01.example.com"),
"project": []byte("prod-project"),
"config": []byte("{}"),
"name": []byte("production_01/west"),
"server": []byte("https://production-01.example.com"),
},
Type: corev1.SecretType("Opaque"),
},
}
testCases := []struct {
name string
selector metav1.LabelSelector
isFlatMode bool
values map[string]string
expected []map[string]any
name string
selector metav1.LabelSelector
values map[string]string
expected []map[string]interface{}
// clientError is true if a k8s client error should be simulated
clientError bool
expectedError error
@@ -106,17 +103,14 @@ func TestGenerateParams(t *testing.T) {
"bat": "{{ metadata.labels.environment }}",
"aaa": "{{ server }}",
"no-op": "{{ this-does-not-exist }}",
}, expected: []map[string]any{
{"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "{{ metadata.annotations.foo.argoproj.io }}", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "{{ metadata.labels.environment }}", "values.aaa": "https://kubernetes.default.svc", "nameNormalized": "in-cluster", "name": "in-cluster", "server": "https://kubernetes.default.svc", "project": ""},
{
"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "production", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "production", "values.aaa": "https://production-01.example.com", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
},
}, expected: []map[string]interface{}{
{"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "production", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "production", "values.aaa": "https://production-01.example.com", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production"},
{
"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "staging", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "staging", "values.aaa": "https://staging-01.example.com", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
},
{"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "staging", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "staging", "values.aaa": "https://staging-01.example.com", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging"},
{"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "{{ metadata.annotations.foo.argoproj.io }}", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "{{ metadata.labels.environment }}", "values.aaa": "https://kubernetes.default.svc", "nameNormalized": "in-cluster", "name": "in-cluster", "server": "https://kubernetes.default.svc"},
},
clientError: false,
expectedError: nil,
@@ -129,16 +123,12 @@ func TestGenerateParams(t *testing.T) {
},
},
values: nil,
expected: []map[string]any{
{
"name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
},
expected: []map[string]interface{}{
{"name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production"},
{
"name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
},
{"name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging"},
},
clientError: false,
expectedError: nil,
@@ -153,11 +143,9 @@ func TestGenerateParams(t *testing.T) {
values: map[string]string{
"foo": "bar",
},
expected: []map[string]any{
{
"values.foo": "bar", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
},
expected: []map[string]interface{}{
{"values.foo": "bar", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production"},
},
clientError: false,
expectedError: nil,
@@ -179,15 +167,11 @@ func TestGenerateParams(t *testing.T) {
values: map[string]string{
"foo": "bar",
},
expected: []map[string]any{
{
"values.foo": "bar", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
},
{
"values.foo": "bar", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
},
expected: []map[string]interface{}{
{"values.foo": "bar", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging"},
{"values.foo": "bar", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production"},
},
clientError: false,
expectedError: nil,
@@ -212,11 +196,9 @@ func TestGenerateParams(t *testing.T) {
values: map[string]string{
"name": "baz",
},
expected: []map[string]any{
{
"values.name": "baz", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
},
expected: []map[string]interface{}{
{"values.name": "baz", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging"},
},
clientError: false,
expectedError: nil,
@@ -227,75 +209,7 @@ func TestGenerateParams(t *testing.T) {
values: nil,
expected: nil,
clientError: true,
expectedError: errors.New("error getting cluster secrets: could not list Secrets"),
},
{
name: "flat mode without selectors",
selector: metav1.LabelSelector{},
values: map[string]string{
"lol1": "lol",
"lol2": "{{values.lol1}}{{values.lol1}}",
"lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}",
"foo": "bar",
"bar": "{{ metadata.annotations.foo.argoproj.io }}",
"bat": "{{ metadata.labels.environment }}",
"aaa": "{{ server }}",
"no-op": "{{ this-does-not-exist }}",
},
expected: []map[string]any{
{
"clusters": []map[string]any{
{"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "{{ metadata.annotations.foo.argoproj.io }}", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "{{ metadata.labels.environment }}", "values.aaa": "https://kubernetes.default.svc", "nameNormalized": "in-cluster", "name": "in-cluster", "server": "https://kubernetes.default.svc", "project": ""},
{
"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "production", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "production", "values.aaa": "https://production-01.example.com", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
},
{
"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "staging", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "staging", "values.aaa": "https://staging-01.example.com", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
},
},
},
},
isFlatMode: true,
clientError: false,
expectedError: nil,
},
{
name: "production or staging with flat mode",
selector: metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "environment",
Operator: "In",
Values: []string{
"production",
"staging",
},
},
},
},
isFlatMode: true,
values: map[string]string{
"foo": "bar",
},
expected: []map[string]any{
{
"clusters": []map[string]any{
{
"values.foo": "bar", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
},
{
"values.foo": "bar", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
},
},
},
},
clientError: false,
expectedError: nil,
expectedError: fmt.Errorf("could not list Secrets"),
},
}
@@ -306,7 +220,9 @@ func TestGenerateParams(t *testing.T) {
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
@@ -315,7 +231,7 @@ func TestGenerateParams(t *testing.T) {
testCase.clientError,
}
clusterGenerator := NewClusterGenerator(t.Context(), cl, appClientset, "namespace")
var clusterGenerator = NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -328,16 +244,16 @@ func TestGenerateParams(t *testing.T) {
Clusters: &argoprojiov1alpha1.ClusterGenerator{
Selector: testCase.selector,
Values: testCase.values,
FlatList: testCase.isFlatMode,
},
}, &applicationSetInfo, nil)
}, &applicationSetInfo)
if testCase.expectedError != nil {
require.EqualError(t, err, testCase.expectedError.Error())
assert.EqualError(t, err, testCase.expectedError.Error())
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, got)
}
})
}
}
@@ -394,11 +310,10 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
},
}
testCases := []struct {
name string
selector metav1.LabelSelector
values map[string]string
isFlatMode bool
expected []map[string]any
name string
selector metav1.LabelSelector
values map[string]string
expected []map[string]interface{}
// clientError is true if a k8s client error should be simulated
clientError bool
expectedError error
@@ -415,13 +330,12 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"bat": "{{ if not (empty .metadata) }}{{.metadata.labels.environment}}{{ end }}",
"aaa": "{{ .server }}",
"no-op": "{{ .thisDoesNotExist }}",
}, expected: []map[string]any{
}, expected: []map[string]interface{}{
{
"name": "production_01/west",
"nameNormalized": "production-01-west",
"server": "https://production-01.example.com",
"project": "",
"metadata": map[string]any{
"metadata": map[string]interface{}{
"labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
"environment": "production",
@@ -446,8 +360,7 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"name": "staging-01",
"nameNormalized": "staging-01",
"server": "https://staging-01.example.com",
"project": "",
"metadata": map[string]any{
"metadata": map[string]interface{}{
"labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
"environment": "staging",
@@ -472,7 +385,6 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"nameNormalized": "in-cluster",
"name": "in-cluster",
"server": "https://kubernetes.default.svc",
"project": "",
"values": map[string]string{
"lol1": "lol",
"lol2": "<no value><no value>",
@@ -496,13 +408,12 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
},
},
values: nil,
expected: []map[string]any{
expected: []map[string]interface{}{
{
"name": "production_01/west",
"nameNormalized": "production-01-west",
"server": "https://production-01.example.com",
"project": "",
"metadata": map[string]any{
"metadata": map[string]interface{}{
"labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
"environment": "production",
@@ -517,8 +428,7 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"name": "staging-01",
"nameNormalized": "staging-01",
"server": "https://staging-01.example.com",
"project": "",
"metadata": map[string]any{
"metadata": map[string]interface{}{
"labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
"environment": "staging",
@@ -543,13 +453,12 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
values: map[string]string{
"foo": "bar",
},
expected: []map[string]any{
expected: []map[string]interface{}{
{
"name": "production_01/west",
"nameNormalized": "production-01-west",
"server": "https://production-01.example.com",
"project": "",
"metadata": map[string]any{
"metadata": map[string]interface{}{
"labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
"environment": "production",
@@ -584,13 +493,12 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
values: map[string]string{
"foo": "bar",
},
expected: []map[string]any{
expected: []map[string]interface{}{
{
"name": "production_01/west",
"nameNormalized": "production-01-west",
"server": "https://production-01.example.com",
"project": "",
"metadata": map[string]any{
"metadata": map[string]interface{}{
"labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
"environment": "production",
@@ -608,8 +516,7 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"name": "staging-01",
"nameNormalized": "staging-01",
"server": "https://staging-01.example.com",
"project": "",
"metadata": map[string]any{
"metadata": map[string]interface{}{
"labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
"environment": "staging",
@@ -647,13 +554,12 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
values: map[string]string{
"name": "baz",
},
expected: []map[string]any{
expected: []map[string]interface{}{
{
"name": "staging-01",
"nameNormalized": "staging-01",
"server": "https://staging-01.example.com",
"project": "",
"metadata": map[string]any{
"metadata": map[string]interface{}{
"labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
"environment": "staging",
@@ -677,163 +583,7 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
values: nil,
expected: nil,
clientError: true,
expectedError: errors.New("error getting cluster secrets: could not list Secrets"),
},
{
name: "Clusters with flat list mode and no selector",
selector: metav1.LabelSelector{},
isFlatMode: true,
values: map[string]string{
"lol1": "lol",
"lol2": "{{ .values.lol1 }}{{ .values.lol1 }}",
"lol3": "{{ .values.lol2 }}{{ .values.lol2 }}{{ .values.lol2 }}",
"foo": "bar",
"bar": "{{ if not (empty .metadata) }}{{index .metadata.annotations \"foo.argoproj.io\" }}{{ end }}",
"bat": "{{ if not (empty .metadata) }}{{.metadata.labels.environment}}{{ end }}",
"aaa": "{{ .server }}",
"no-op": "{{ .thisDoesNotExist }}",
},
expected: []map[string]any{
{
"clusters": []map[string]any{
{
"nameNormalized": "in-cluster",
"name": "in-cluster",
"server": "https://kubernetes.default.svc",
"project": "",
"values": map[string]string{
"lol1": "lol",
"lol2": "<no value><no value>",
"lol3": "<no value><no value><no value>",
"foo": "bar",
"bar": "",
"bat": "",
"aaa": "https://kubernetes.default.svc",
"no-op": "<no value>",
},
},
{
"name": "production_01/west",
"nameNormalized": "production-01-west",
"server": "https://production-01.example.com",
"project": "",
"metadata": map[string]any{
"labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
"environment": "production",
"org": "bar",
},
"annotations": map[string]string{
"foo.argoproj.io": "production",
},
},
"values": map[string]string{
"lol1": "lol",
"lol2": "<no value><no value>",
"lol3": "<no value><no value><no value>",
"foo": "bar",
"bar": "production",
"bat": "production",
"aaa": "https://production-01.example.com",
"no-op": "<no value>",
},
},
{
"name": "staging-01",
"nameNormalized": "staging-01",
"server": "https://staging-01.example.com",
"project": "",
"metadata": map[string]any{
"labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
"environment": "staging",
"org": "foo",
},
"annotations": map[string]string{
"foo.argoproj.io": "staging",
},
},
"values": map[string]string{
"lol1": "lol",
"lol2": "<no value><no value>",
"lol3": "<no value><no value><no value>",
"foo": "bar",
"bar": "staging",
"bat": "staging",
"aaa": "https://staging-01.example.com",
"no-op": "<no value>",
},
},
},
},
},
clientError: false,
expectedError: nil,
},
{
name: "production or staging with flat mode",
selector: metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "environment",
Operator: "In",
Values: []string{
"production",
"staging",
},
},
},
},
isFlatMode: true,
values: map[string]string{
"foo": "bar",
},
expected: []map[string]any{
{
"clusters": []map[string]any{
{
"name": "production_01/west",
"nameNormalized": "production-01-west",
"server": "https://production-01.example.com",
"project": "",
"metadata": map[string]any{
"labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
"environment": "production",
"org": "bar",
},
"annotations": map[string]string{
"foo.argoproj.io": "production",
},
},
"values": map[string]string{
"foo": "bar",
},
},
{
"name": "staging-01",
"nameNormalized": "staging-01",
"server": "https://staging-01.example.com",
"project": "",
"metadata": map[string]any{
"labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
"environment": "staging",
"org": "foo",
},
"annotations": map[string]string{
"foo.argoproj.io": "staging",
},
},
"values": map[string]string{
"foo": "bar",
},
},
},
},
},
clientError: false,
expectedError: nil,
expectedError: fmt.Errorf("could not list Secrets"),
},
}
@@ -844,7 +594,9 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
@@ -853,7 +605,7 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
testCase.clientError,
}
clusterGenerator := NewClusterGenerator(t.Context(), cl, appClientset, "namespace")
var clusterGenerator = NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -868,16 +620,16 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
Clusters: &argoprojiov1alpha1.ClusterGenerator{
Selector: testCase.selector,
Values: testCase.values,
FlatList: testCase.isFlatMode,
},
}, &applicationSetInfo, nil)
}, &applicationSetInfo)
if testCase.expectedError != nil {
require.EqualError(t, err, testCase.expectedError.Error())
assert.EqualError(t, err, testCase.expectedError.Error())
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, got)
}
})
}
}

View File

@@ -2,73 +2,79 @@ package generators
import (
"context"
"errors"
"fmt"
"strings"
"time"
log "github.com/sirupsen/logrus"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v2/util/settings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"github.com/argoproj/argo-cd/v3/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
var _ Generator = (*DuckTypeGenerator)(nil)
// DuckTypeGenerator generates Applications for some or all clusters registered with ArgoCD.
type DuckTypeGenerator struct {
ctx context.Context
dynClient dynamic.Interface
clientset kubernetes.Interface
namespace string // namespace is the Argo CD namespace
ctx context.Context
dynClient dynamic.Interface
clientset kubernetes.Interface
namespace string // namespace is the Argo CD namespace
settingsManager *settings.SettingsManager
}
func NewDuckTypeGenerator(ctx context.Context, dynClient dynamic.Interface, clientset kubernetes.Interface, namespace string) Generator {
settingsManager := settings.NewSettingsManager(ctx, clientset, namespace)
g := &DuckTypeGenerator{
ctx: ctx,
dynClient: dynClient,
clientset: clientset,
namespace: namespace,
ctx: ctx,
dynClient: dynClient,
clientset: clientset,
namespace: namespace,
settingsManager: settingsManager,
}
return g
}
func (g *DuckTypeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
// Return a requeue default of 3 minutes, if no override is specified.
if appSetGenerator.ClusterDecisionResource.RequeueAfterSeconds != nil {
return time.Duration(*appSetGenerator.ClusterDecisionResource.RequeueAfterSeconds) * time.Second
}
return getDefaultRequeueAfter()
return DefaultRequeueAfterSeconds
}
func (g *DuckTypeGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
return &appSetGenerator.ClusterDecisionResource.Template
}
func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) {
func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil {
return nil, ErrEmptyAppSetGenerator
return nil, EmptyAppSetGeneratorError
}
// Not likely to happen
if appSetGenerator.ClusterDecisionResource == nil {
return nil, ErrEmptyAppSetGenerator
return nil, EmptyAppSetGeneratorError
}
// ListCluster from Argo CD's util/db package will include the local cluster in the list of clusters
clustersFromArgoCD, err := utils.ListClusters(g.ctx, g.clientset, g.namespace)
if err != nil {
return nil, fmt.Errorf("error listing clusters: %w", err)
return nil, err
}
if clustersFromArgoCD == nil {
@@ -77,8 +83,9 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
// Read the configMapRef
cm, err := g.clientset.CoreV1().ConfigMaps(g.namespace).Get(g.ctx, appSetGenerator.ClusterDecisionResource.ConfigMapRef, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("error reading configMapRef: %w", err)
return nil, err
}
// Extract GVK data for the dynamic client to use
@@ -92,13 +99,14 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
// Validate the fields
if kind == "" || versionIdx < 1 {
log.Warningf("kind=%v, resourceName=%v, versionIdx=%v", kind, resourceName, versionIdx)
return nil, errors.New("there is a problem with the apiVersion, kind or resourceName provided")
return nil, fmt.Errorf("There is a problem with the apiVersion, kind or resourceName provided")
}
if (resourceName == "" && labelSelector.MatchLabels == nil && labelSelector.MatchExpressions == nil) ||
(resourceName != "" && (labelSelector.MatchExpressions != nil || labelSelector.MatchLabels != nil)) {
log.Warningf("You must choose either resourceName=%v, labelSelector.matchLabels=%v or labelSelect.matchExpressions=%v", resourceName, labelSelector.MatchLabels, labelSelector.MatchExpressions)
return nil, errors.New("there is a problem with the definition of the ClusterDecisionResource generator")
return nil, fmt.Errorf("There is a problem with the definition of the ClusterDecisionResource generator")
}
// Split up the apiVersion
@@ -114,116 +122,115 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
log.WithField("listOptions.LabelSelector", listOptions.LabelSelector).Info("selection type")
} else {
listOptions.FieldSelector = fields.OneTermEqualSelector("metadata.name", resourceName).String()
// metav1.Convert_fields_Selector_To_string(fields.).Sprintf("metadata.name=%s", resourceName)
//metav1.Convert_fields_Selector_To_string(fields.).Sprintf("metadata.name=%s", resourceName)
log.WithField("listOptions.FieldSelector", listOptions.FieldSelector).Info("selection type")
}
duckResources, err := g.dynClient.Resource(duckGVR).Namespace(g.namespace).List(g.ctx, listOptions)
if err != nil {
log.WithField("GVK", duckGVR).Warning("resources were not found")
return nil, fmt.Errorf("failed to get dynamic resources: %w", err)
return nil, err
}
if len(duckResources.Items) == 0 {
log.Warning("no resource found, make sure you clusterDecisionResource is defined correctly")
return nil, errors.New("no clusterDecisionResources found")
return nil, fmt.Errorf("no clusterDecisionResources found")
}
// Override the duck type in the status of the resource
statusListKey := "clusters"
matchKey := cm.Data["matchKey"]
if cm.Data["statusListKey"] != "" {
statusListKey = cm.Data["statusListKey"]
}
matchKey := cm.Data["matchKey"]
if matchKey == "" {
log.WithField("matchKey", matchKey).Warning("matchKey not found in " + cm.Name)
return nil, nil
}
clusterDecisions := buildClusterDecisions(duckResources, statusListKey)
if len(clusterDecisions) == 0 {
log.Warningf("clusterDecisionResource status.%s missing", statusListKey)
return nil, nil
}
res := []map[string]any{}
for _, clusterDecision := range clusterDecisions {
cluster := findCluster(clustersFromArgoCD, clusterDecision, matchKey, statusListKey)
// if no cluster is found, move to the next cluster
if cluster == nil {
continue
}
// generated instance of cluster params
params := map[string]any{
"name": cluster.Name,
"server": cluster.Server,
}
for key, value := range clusterDecision.(map[string]any) {
params[key] = value.(string)
}
for key, value := range appSetGenerator.ClusterDecisionResource.Values {
collectParams(appSet, params, key, value)
}
res = append(res, params)
}
return res, nil
}
func buildClusterDecisions(duckResources *unstructured.UnstructuredList, statusListKey string) []any {
clusterDecisions := []any{}
res := []map[string]interface{}{}
clusterDecisions := []interface{}{}
// Build the decision slice
for _, duckResource := range duckResources.Items {
log.WithField("duckResourceName", duckResource.GetName()).Debug("found resource")
if duckResource.Object["status"] == nil || len(duckResource.Object["status"].(map[string]any)) == 0 {
if duckResource.Object["status"] == nil || len(duckResource.Object["status"].(map[string]interface{})) == 0 {
log.Warningf("clusterDecisionResource: %s, has no status", duckResource.GetName())
continue
}
log.WithField("duckResourceStatus", duckResource.Object["status"]).Debug("found resource")
clusterDecisions = append(clusterDecisions, duckResource.Object["status"].(map[string]any)[statusListKey].([]any)...)
clusterDecisions = append(clusterDecisions, duckResource.Object["status"].(map[string]interface{})[statusListKey].([]interface{})...)
}
log.Infof("Number of decisions found: %v", len(clusterDecisions))
return clusterDecisions
}
func findCluster(clustersFromArgoCD []utils.ClusterSpecifier, cluster any, matchKey string, statusListKey string) *utils.ClusterSpecifier {
log.Infof("cluster: %v", cluster)
matchValue := cluster.(map[string]any)[matchKey]
if matchValue == nil || matchValue.(string) == "" {
log.Warningf("matchKey=%v not found in \"%v\" list: %v\n", matchKey, statusListKey, cluster.(map[string]any))
return nil // no match
}
// Read this outside the loop to improve performance
argoClusters := clustersFromArgoCD.Items
strMatchValue := matchValue.(string)
log.WithField(matchKey, strMatchValue).Debug("validate against ArgoCD")
if len(clusterDecisions) > 0 {
for _, cluster := range clusterDecisions {
for _, argoCluster := range clustersFromArgoCD {
if argoCluster.Name == strMatchValue {
log.WithField(matchKey, argoCluster.Name).Info("matched cluster in ArgoCD")
return &argoCluster
// generated instance of cluster params
params := map[string]interface{}{}
log.Infof("cluster: %v", cluster)
matchValue := cluster.(map[string]interface{})[matchKey]
if matchValue == nil || matchValue.(string) == "" {
log.Warningf("matchKey=%v not found in \"%v\" list: %v\n", matchKey, statusListKey, cluster.(map[string]interface{}))
continue
}
strMatchValue := matchValue.(string)
log.WithField(matchKey, strMatchValue).Debug("validate against ArgoCD")
found := false
for _, argoCluster := range argoClusters {
if argoCluster.Name == strMatchValue {
log.WithField(matchKey, argoCluster.Name).Info("matched cluster in ArgoCD")
params["name"] = argoCluster.Name
params["server"] = argoCluster.Server
found = true
break // Stop looking
}
}
if !found {
log.WithField(matchKey, strMatchValue).Warning("unmatched cluster in ArgoCD")
continue
}
for key, value := range cluster.(map[string]interface{}) {
params[key] = value.(string)
}
for key, value := range appSetGenerator.ClusterDecisionResource.Values {
if appSet.Spec.GoTemplate {
if params["values"] == nil {
params["values"] = map[string]string{}
}
params["values"].(map[string]string)[key] = value
} else {
params[fmt.Sprintf("values.%s", key)] = value
}
}
res = append(res, params)
}
}
log.WithField(matchKey, strMatchValue).Warning("unmatched cluster in ArgoCD")
return nil
}
func collectParams(appSet *argoprojiov1alpha1.ApplicationSet, params map[string]any, key string, value string) {
if appSet.Spec.GoTemplate {
if params["values"] == nil {
params["values"] = map[string]string{}
}
params["values"].(map[string]string)[key] = value
} else {
params["values."+key] = value
log.Warningf("clusterDecisionResource status." + statusListKey + " missing")
return nil, nil
}
return res, nil
}

View File

@@ -1,11 +1,11 @@
package generators
import (
"errors"
"context"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -15,14 +15,12 @@ import (
kubefake "k8s.io/client-go/kubernetes/fake"
"sigs.k8s.io/controller-runtime/pkg/client"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
const (
resourceAPIVersion = "mallard.io/v1"
resourceKind = "ducks"
resourceName = "quak"
)
const resourceApiVersion = "mallard.io/v1"
const resourceKind = "ducks"
const resourceName = "quak"
func TestGenerateParamsForDuckType(t *testing.T) {
clusters := []client.Object{
@@ -77,20 +75,20 @@ func TestGenerateParamsForDuckType(t *testing.T) {
}
duckType := &unstructured.Unstructured{
Object: map[string]any{
"apiVersion": resourceAPIVersion,
Object: map[string]interface{}{
"apiVersion": resourceApiVersion,
"kind": "Duck",
"metadata": map[string]any{
"metadata": map[string]interface{}{
"name": resourceName,
"namespace": "namespace",
"labels": map[string]any{"duck": "all-species"},
"labels": map[string]interface{}{"duck": "all-species"},
},
"status": map[string]any{
"decisions": []any{
map[string]any{
"status": map[string]interface{}{
"decisions": []interface{}{
map[string]interface{}{
"clusterName": "staging-01",
},
map[string]any{
map[string]interface{}{
"clusterName": "production-01",
},
},
@@ -99,17 +97,17 @@ func TestGenerateParamsForDuckType(t *testing.T) {
}
duckTypeProdOnly := &unstructured.Unstructured{
Object: map[string]any{
"apiVersion": resourceAPIVersion,
Object: map[string]interface{}{
"apiVersion": resourceApiVersion,
"kind": "Duck",
"metadata": map[string]any{
"metadata": map[string]interface{}{
"name": resourceName,
"namespace": "namespace",
"labels": map[string]any{"duck": "spotted"},
"labels": map[string]interface{}{"duck": "spotted"},
},
"status": map[string]any{
"decisions": []any{
map[string]any{
"status": map[string]interface{}{
"decisions": []interface{}{
map[string]interface{}{
"clusterName": "production-01",
},
},
@@ -118,15 +116,15 @@ func TestGenerateParamsForDuckType(t *testing.T) {
}
duckTypeEmpty := &unstructured.Unstructured{
Object: map[string]any{
"apiVersion": resourceAPIVersion,
Object: map[string]interface{}{
"apiVersion": resourceApiVersion,
"kind": "Duck",
"metadata": map[string]any{
"metadata": map[string]interface{}{
"name": resourceName,
"namespace": "namespace",
"labels": map[string]any{"duck": "canvasback"},
"labels": map[string]interface{}{"duck": "canvasback"},
},
"status": map[string]any{},
"status": map[string]interface{}{},
},
}
@@ -136,7 +134,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
Namespace: "namespace",
},
Data: map[string]string{
"apiVersion": resourceAPIVersion,
"apiVersion": resourceApiVersion,
"kind": resourceKind,
"statusListKey": "decisions",
"matchKey": "clusterName",
@@ -150,7 +148,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
labelSelector metav1.LabelSelector
resource *unstructured.Unstructured
values map[string]string
expected []map[string]any
expected []map[string]interface{}
expectedError error
}{
{
@@ -158,8 +156,8 @@ func TestGenerateParamsForDuckType(t *testing.T) {
resourceName: "",
resource: duckType,
values: nil,
expected: []map[string]any{},
expectedError: errors.New("there is a problem with the definition of the ClusterDecisionResource generator"),
expected: []map[string]interface{}{},
expectedError: fmt.Errorf("There is a problem with the definition of the ClusterDecisionResource generator"),
},
/*** This does not work with the FAKE runtime client, fieldSelectors are broken.
{
@@ -176,7 +174,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
resourceName: resourceName,
resource: duckType,
values: nil,
expected: []map[string]any{
expected: []map[string]interface{}{
{"clusterName": "production-01", "name": "production-01", "server": "https://production-01.example.com"},
{"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"},
@@ -190,7 +188,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
values: map[string]string{
"foo": "bar",
},
expected: []map[string]any{
expected: []map[string]interface{}{
{"clusterName": "production-01", "values.foo": "bar", "name": "production-01", "server": "https://production-01.example.com"},
},
expectedError: nil,
@@ -218,7 +216,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
labelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"duck": "all-species"}},
resource: duckType,
values: nil,
expected: []map[string]any{
expected: []map[string]interface{}{
{"clusterName": "production-01", "name": "production-01", "server": "https://production-01.example.com"},
{"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"},
@@ -233,7 +231,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
values: map[string]string{
"foo": "bar",
},
expected: []map[string]any{
expected: []map[string]interface{}{
{"clusterName": "production-01", "values.foo": "bar", "name": "production-01", "server": "https://production-01.example.com"},
},
expectedError: nil,
@@ -250,7 +248,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
}},
resource: duckType,
values: nil,
expected: []map[string]any{
expected: []map[string]interface{}{
{"clusterName": "production-01", "name": "production-01", "server": "https://production-01.example.com"},
{"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"},
@@ -270,7 +268,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
resource: duckType,
values: nil,
expected: nil,
expectedError: errors.New("there is a problem with the definition of the ClusterDecisionResource generator"),
expectedError: fmt.Errorf("There is a problem with the definition of the ClusterDecisionResource generator"),
},
}
@@ -281,7 +279,9 @@ func TestGenerateParamsForDuckType(t *testing.T) {
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
appClientset := kubefake.NewSimpleClientset(append(runtimeClusters, configMap)...)
gvrToListKind := map[schema.GroupVersionResource]string{{
@@ -292,7 +292,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, testCase.resource)
duckTypeGenerator := NewDuckTypeGenerator(t.Context(), fakeDynClient, appClientset, "namespace")
var duckTypeGenerator = NewDuckTypeGenerator(context.Background(), fakeDynClient, appClientset, "namespace")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -308,12 +308,12 @@ func TestGenerateParamsForDuckType(t *testing.T) {
LabelSelector: testCase.labelSelector,
Values: testCase.values,
},
}, &applicationSetInfo, nil)
}, &applicationSetInfo)
if testCase.expectedError != nil {
require.EqualError(t, err, testCase.expectedError.Error())
assert.EqualError(t, err, testCase.expectedError.Error())
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, got)
}
})
@@ -373,20 +373,20 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
}
duckType := &unstructured.Unstructured{
Object: map[string]any{
"apiVersion": resourceAPIVersion,
Object: map[string]interface{}{
"apiVersion": resourceApiVersion,
"kind": "Duck",
"metadata": map[string]any{
"metadata": map[string]interface{}{
"name": resourceName,
"namespace": "namespace",
"labels": map[string]any{"duck": "all-species"},
"labels": map[string]interface{}{"duck": "all-species"},
},
"status": map[string]any{
"decisions": []any{
map[string]any{
"status": map[string]interface{}{
"decisions": []interface{}{
map[string]interface{}{
"clusterName": "staging-01",
},
map[string]any{
map[string]interface{}{
"clusterName": "production-01",
},
},
@@ -395,17 +395,17 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
}
duckTypeProdOnly := &unstructured.Unstructured{
Object: map[string]any{
"apiVersion": resourceAPIVersion,
Object: map[string]interface{}{
"apiVersion": resourceApiVersion,
"kind": "Duck",
"metadata": map[string]any{
"metadata": map[string]interface{}{
"name": resourceName,
"namespace": "namespace",
"labels": map[string]any{"duck": "spotted"},
"labels": map[string]interface{}{"duck": "spotted"},
},
"status": map[string]any{
"decisions": []any{
map[string]any{
"status": map[string]interface{}{
"decisions": []interface{}{
map[string]interface{}{
"clusterName": "production-01",
},
},
@@ -414,15 +414,15 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
}
duckTypeEmpty := &unstructured.Unstructured{
Object: map[string]any{
"apiVersion": resourceAPIVersion,
Object: map[string]interface{}{
"apiVersion": resourceApiVersion,
"kind": "Duck",
"metadata": map[string]any{
"metadata": map[string]interface{}{
"name": resourceName,
"namespace": "namespace",
"labels": map[string]any{"duck": "canvasback"},
"labels": map[string]interface{}{"duck": "canvasback"},
},
"status": map[string]any{},
"status": map[string]interface{}{},
},
}
@@ -432,7 +432,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
Namespace: "namespace",
},
Data: map[string]string{
"apiVersion": resourceAPIVersion,
"apiVersion": resourceApiVersion,
"kind": resourceKind,
"statusListKey": "decisions",
"matchKey": "clusterName",
@@ -446,7 +446,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
labelSelector metav1.LabelSelector
resource *unstructured.Unstructured
values map[string]string
expected []map[string]any
expected []map[string]interface{}
expectedError error
}{
{
@@ -454,8 +454,8 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
resourceName: "",
resource: duckType,
values: nil,
expected: []map[string]any{},
expectedError: errors.New("there is a problem with the definition of the ClusterDecisionResource generator"),
expected: []map[string]interface{}{},
expectedError: fmt.Errorf("There is a problem with the definition of the ClusterDecisionResource generator"),
},
/*** This does not work with the FAKE runtime client, fieldSelectors are broken.
{
@@ -472,7 +472,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
resourceName: resourceName,
resource: duckType,
values: nil,
expected: []map[string]any{
expected: []map[string]interface{}{
{"clusterName": "production-01", "name": "production-01", "server": "https://production-01.example.com"},
{"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"},
@@ -486,7 +486,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
values: map[string]string{
"foo": "bar",
},
expected: []map[string]any{
expected: []map[string]interface{}{
{"clusterName": "production-01", "values": map[string]string{"foo": "bar"}, "name": "production-01", "server": "https://production-01.example.com"},
},
expectedError: nil,
@@ -514,7 +514,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
labelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"duck": "all-species"}},
resource: duckType,
values: nil,
expected: []map[string]any{
expected: []map[string]interface{}{
{"clusterName": "production-01", "name": "production-01", "server": "https://production-01.example.com"},
{"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"},
@@ -529,7 +529,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
values: map[string]string{
"foo": "bar",
},
expected: []map[string]any{
expected: []map[string]interface{}{
{"clusterName": "production-01", "values": map[string]string{"foo": "bar"}, "name": "production-01", "server": "https://production-01.example.com"},
},
expectedError: nil,
@@ -546,7 +546,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
}},
resource: duckType,
values: nil,
expected: []map[string]any{
expected: []map[string]interface{}{
{"clusterName": "production-01", "name": "production-01", "server": "https://production-01.example.com"},
{"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"},
@@ -566,7 +566,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
resource: duckType,
values: nil,
expected: nil,
expectedError: errors.New("there is a problem with the definition of the ClusterDecisionResource generator"),
expectedError: fmt.Errorf("There is a problem with the definition of the ClusterDecisionResource generator"),
},
}
@@ -577,7 +577,9 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
appClientset := kubefake.NewSimpleClientset(append(runtimeClusters, configMap)...)
gvrToListKind := map[schema.GroupVersionResource]string{{
@@ -588,7 +590,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, testCase.resource)
duckTypeGenerator := NewDuckTypeGenerator(t.Context(), fakeDynClient, appClientset, "namespace")
var duckTypeGenerator = NewDuckTypeGenerator(context.Background(), fakeDynClient, appClientset, "namespace")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -606,12 +608,12 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
LabelSelector: testCase.labelSelector,
Values: testCase.values,
},
}, &applicationSetInfo, nil)
}, &applicationSetInfo)
if testCase.expectedError != nil {
require.EqualError(t, err, testCase.expectedError.Error())
assert.EqualError(t, err, testCase.expectedError.Error())
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, got)
}
})

View File

@@ -5,15 +5,14 @@ import (
"reflect"
"github.com/jeremywohl/flatten"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v3/applicationset/utils"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
"k8s.io/apimachinery/pkg/labels"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"dario.cat/mergo"
"github.com/imdario/mergo"
log "github.com/sirupsen/logrus"
)
@@ -22,12 +21,12 @@ const (
)
type TransformResult struct {
Params []map[string]any
Params []map[string]interface{}
Template argoprojiov1alpha1.ApplicationSetTemplate
}
// 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]any, client client.Client) ([]TransformResult, error) {
func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, allGenerators map[string]Generator, baseTemplate argoprojiov1alpha1.ApplicationSetTemplate, appSet *argoprojiov1alpha1.ApplicationSet, genParams map[string]interface{}) ([]TransformResult, error) {
// 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.
@@ -52,7 +51,7 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
}
continue
}
var params []map[string]any
var params []map[string]interface{}
if len(genParams) != 0 {
tempInterpolatedGenerator, err := InterpolateGenerator(&requestedGenerator, genParams, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
interpolatedGenerator = &tempInterpolatedGenerator
@@ -65,7 +64,7 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
continue
}
}
params, err = g.GenerateParams(interpolatedGenerator, appSet, client)
params, err = g.GenerateParams(interpolatedGenerator, appSet)
if err != nil {
log.WithError(err).WithField("generator", g).
Error("error generating params")
@@ -74,7 +73,7 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
}
continue
}
var filterParams []map[string]any
var filterParams []map[string]interface{}
for _, param := range params {
flatParam, err := flattenParameters(param)
if err != nil {
@@ -123,10 +122,10 @@ func GetRelevantGenerators(requestedGenerator *argoprojiov1alpha1.ApplicationSet
return res
}
func flattenParameters(in map[string]any) (map[string]string, error) {
func flattenParameters(in map[string]interface{}) (map[string]string, error) {
flat, err := flatten.Flatten(in, "", flatten.DotStyle)
if err != nil {
return nil, fmt.Errorf("error flatenning parameters: %w", err)
return nil, err
}
out := make(map[string]string, len(flat))
@@ -149,7 +148,7 @@ func mergeGeneratorTemplate(g Generator, requestedGenerator *argoprojiov1alpha1.
// InterpolateGenerator allows interpolating the matrix's 2nd child generator with values from the 1st child generator
// "params" parameter is an array, where each index corresponds to a generator. Each index contains a map w/ that generator's parameters.
func InterpolateGenerator(requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, params map[string]any, useGoTemplate bool, goTemplateOptions []string) (argoprojiov1alpha1.ApplicationSetGenerator, error) {
func InterpolateGenerator(requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, params map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (argoprojiov1alpha1.ApplicationSetGenerator, error) {
render := utils.Render{}
interpolatedGenerator, err := render.RenderGeneratorParams(requestedGenerator, params, useGoTemplate, goTemplateOptions)
if err != nil {
@@ -159,3 +158,16 @@ func InterpolateGenerator(requestedGenerator *argoprojiov1alpha1.ApplicationSetG
return *interpolatedGenerator, nil
}
// Fixes https://github.com/argoproj/argo-cd/issues/11982 while ensuring backwards compatibility.
// This is only a short-term solution and should be removed in a future major version.
func dropDisabledNestedSelectors(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator) bool {
var foundSelector bool
for i := range generators {
if generators[i].Selector != nil {
foundSelector = true
generators[i].Selector = nil
}
}
return foundSelector
}

View File

@@ -10,9 +10,9 @@ import (
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/argoproj/argo-cd/v3/applicationset/services/mocks"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
argov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/stretchr/testify/mock"
corev1 "k8s.io/api/core/v1"
@@ -27,19 +27,19 @@ func TestMatchValues(t *testing.T) {
name string
elements []apiextensionsv1.JSON
selector *metav1.LabelSelector
expected []map[string]any
expected []map[string]interface{}
}{
{
name: "no filter",
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
selector: &metav1.LabelSelector{},
expected: []map[string]any{{"cluster": "cluster", "url": "url"}},
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url"}},
},
{
name: "nil",
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
selector: nil,
expected: []map[string]any{{"cluster": "cluster", "url": "url"}},
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url"}},
},
{
name: "values.foo should be foo but is ignore element",
@@ -49,7 +49,7 @@ func TestMatchValues(t *testing.T) {
"values.foo": "foo",
},
},
expected: []map[string]any{},
expected: []map[string]interface{}{},
},
{
name: "values.foo should be bar",
@@ -59,14 +59,14 @@ func TestMatchValues(t *testing.T) {
"values.foo": "bar",
},
},
expected: []map[string]any{{"cluster": "cluster", "url": "url", "values.foo": "bar"}},
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url", "values.foo": "bar"}},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
listGenerator := NewListGenerator()
data := map[string]Generator{
var listGenerator = NewListGenerator()
var data = map[string]Generator{
"List": listGenerator,
}
@@ -84,13 +84,12 @@ func TestMatchValues(t *testing.T) {
List: &argov1alpha1.ListGenerator{
Elements: testCase.elements,
Template: emptyTemplate(),
},
},
}},
data,
emptyTemplate(),
&applicationSetInfo, nil, nil)
&applicationSetInfo, nil)
require.NoError(t, err)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, results[0].Params)
})
}
@@ -101,19 +100,19 @@ func TestMatchValuesGoTemplate(t *testing.T) {
name string
elements []apiextensionsv1.JSON
selector *metav1.LabelSelector
expected []map[string]any
expected []map[string]interface{}
}{
{
name: "no filter",
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
selector: &metav1.LabelSelector{},
expected: []map[string]any{{"cluster": "cluster", "url": "url"}},
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url"}},
},
{
name: "nil",
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
selector: nil,
expected: []map[string]any{{"cluster": "cluster", "url": "url"}},
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url"}},
},
{
name: "values.foo should be foo but is ignore element",
@@ -123,7 +122,7 @@ func TestMatchValuesGoTemplate(t *testing.T) {
"values.foo": "foo",
},
},
expected: []map[string]any{},
expected: []map[string]interface{}{},
},
{
name: "values.foo should be bar",
@@ -133,7 +132,7 @@ func TestMatchValuesGoTemplate(t *testing.T) {
"values.foo": "bar",
},
},
expected: []map[string]any{{"cluster": "cluster", "url": "url", "values": map[string]any{"foo": "bar"}}},
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url", "values": map[string]interface{}{"foo": "bar"}}},
},
{
name: "values.0 should be bar",
@@ -143,14 +142,14 @@ func TestMatchValuesGoTemplate(t *testing.T) {
"values.0": "bar",
},
},
expected: []map[string]any{{"cluster": "cluster", "url": "url", "values": []any{"bar"}}},
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url", "values": []interface{}{"bar"}}},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
listGenerator := NewListGenerator()
data := map[string]Generator{
var listGenerator = NewListGenerator()
var data = map[string]Generator{
"List": listGenerator,
}
@@ -168,13 +167,12 @@ func TestMatchValuesGoTemplate(t *testing.T) {
List: &argov1alpha1.ListGenerator{
Elements: testCase.elements,
Template: emptyTemplate(),
},
},
}},
data,
emptyTemplate(),
&applicationSetInfo, nil, nil)
&applicationSetInfo, nil)
require.NoError(t, err)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, results[0].Params)
})
}
@@ -184,14 +182,14 @@ func TestTransForm(t *testing.T) {
testCases := []struct {
name string
selector *metav1.LabelSelector
expected []map[string]any
expected []map[string]interface{}
}{
{
name: "server filter",
selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"server": "https://production-01.example.com"},
},
expected: []map[string]any{{
expected: []map[string]interface{}{{
"metadata.annotations.foo.argoproj.io": "production",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster",
"metadata.labels.environment": "production",
@@ -199,7 +197,6 @@ func TestTransForm(t *testing.T) {
"name": "production_01/west",
"nameNormalized": "production-01-west",
"server": "https://production-01.example.com",
"project": "",
}},
},
{
@@ -207,7 +204,7 @@ func TestTransForm(t *testing.T) {
selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"server": "https://some-really-long-url-that-will-exceed-63-characters.com"},
},
expected: []map[string]any{{
expected: []map[string]interface{}{{
"metadata.annotations.foo.argoproj.io": "production",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster",
"metadata.labels.environment": "production",
@@ -215,7 +212,6 @@ func TestTransForm(t *testing.T) {
"name": "some-really-long-server-url",
"nameNormalized": "some-really-long-server-url",
"server": "https://some-really-long-url-that-will-exceed-63-characters.com",
"project": "",
}},
},
}
@@ -223,7 +219,7 @@ func TestTransForm(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
testGenerators := map[string]Generator{
"Clusters": getMockClusterGenerator(t.Context()),
"Clusters": getMockClusterGenerator(),
}
applicationSetInfo := argov1alpha1.ApplicationSet{
@@ -240,13 +236,12 @@ func TestTransForm(t *testing.T) {
Selector: metav1.LabelSelector{},
Template: argov1alpha1.ApplicationSetTemplate{},
Values: nil,
},
},
}},
testGenerators,
emptyTemplate(),
&applicationSetInfo, nil, nil)
&applicationSetInfo, nil)
require.NoError(t, err)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, results[0].Params)
})
}
@@ -260,7 +255,7 @@ func emptyTemplate() argov1alpha1.ApplicationSetTemplate {
}
}
func getMockClusterGenerator(ctx context.Context) Generator {
func getMockClusterGenerator() Generator {
clusters := []crtclient.Object{
&corev1.Secret{
TypeMeta: metav1.TypeMeta{
@@ -342,19 +337,20 @@ func getMockClusterGenerator(ctx context.Context) Generator {
appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
return NewClusterGenerator(ctx, fakeClient, appClientset, "namespace")
return NewClusterGenerator(fakeClient, context.Background(), appClientset, "namespace")
}
func getMockGitGenerator() Generator {
argoCDServiceMock := &mocks.Repos{}
argoCDServiceMock.EXPECT().GetDirectories(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]string{"app1", "app2", "app_3", "p1/app4"}, nil)
gitGenerator := NewGitGenerator(argoCDServiceMock, "namespace")
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)
return gitGenerator
}
func TestGetRelevantGenerators(t *testing.T) {
testGenerators := map[string]Generator{
"Clusters": getMockClusterGenerator(t.Context()),
"Clusters": getMockClusterGenerator(),
"Git": getMockGitGenerator(),
}
@@ -365,8 +361,7 @@ func TestGetRelevantGenerators(t *testing.T) {
requestedGenerator := &argov1alpha1.ApplicationSetGenerator{
List: &argov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}},
},
}
}}
relevantGenerators := GetRelevantGenerators(requestedGenerator, testGenerators)
assert.Len(t, relevantGenerators, 1)
@@ -409,11 +404,10 @@ func TestInterpolateGenerator(t *testing.T) {
"path-basename": "{{path.basename}}",
"path-zero": "{{path[0]}}",
"path-full": "{{path}}",
},
},
}},
},
}
gitGeneratorParams := map[string]any{
gitGeneratorParams := map[string]interface{}{
"path": "p1/p2/app3",
"path.basename": "app3",
"path[0]": "p1",
@@ -442,7 +436,7 @@ func TestInterpolateGenerator(t *testing.T) {
Template: argov1alpha1.ApplicationSetTemplate{},
},
}
clusterGeneratorParams := map[string]any{
clusterGeneratorParams := map[string]interface{}{
"name": "production_01/west", "server": "https://production-01.example.com",
}
interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, false, nil)
@@ -464,12 +458,11 @@ func TestInterpolateGenerator_go(t *testing.T) {
"path-zero": "{{index .path.segments 0}}",
"path-full": "{{.path.path}}",
"kubernetes.io/environment": `{{default "foo" .my_label}}`,
},
},
}},
},
}
gitGeneratorParams := map[string]any{
"path": map[string]any{
gitGeneratorParams := map[string]interface{}{
"path": map[string]interface{}{
"path": "p1/p2/app3",
"segments": []string{"p1", "p2", "app3"},
},
@@ -497,7 +490,7 @@ func TestInterpolateGenerator_go(t *testing.T) {
Template: argov1alpha1.ApplicationSetTemplate{},
},
}
clusterGeneratorParams := map[string]any{
clusterGeneratorParams := map[string]interface{}{
"name": "production_01/west", "server": "https://production-01.example.com",
}
interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, true, nil)
@@ -512,7 +505,7 @@ func TestInterpolateGenerator_go(t *testing.T) {
func TestInterpolateGeneratorError(t *testing.T) {
type args struct {
requestedGenerator *argov1alpha1.ApplicationSetGenerator
params map[string]any
params map[string]interface{}
useGoTemplate bool
goTemplateOptions []string
}
@@ -530,7 +523,7 @@ func TestInterpolateGeneratorError(t *testing.T) {
}, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: "generator is empty"},
{name: "No Params", args: args{
requestedGenerator: &argov1alpha1.ApplicationSetGenerator{},
params: map[string]any{},
params: map[string]interface{}{},
useGoTemplate: false,
goTemplateOptions: nil,
}, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: ""},
@@ -545,7 +538,7 @@ func TestInterpolateGeneratorError(t *testing.T) {
"resolved": "{{ index .rmap (default .override .test) }}",
},
}},
params: map[string]any{
params: map[string]interface{}{
"name": "in-cluster",
"override": "foo",
},
@@ -557,7 +550,7 @@ func TestInterpolateGeneratorError(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
got, err := InterpolateGenerator(tt.args.requestedGenerator, tt.args.params, tt.args.useGoTemplate, tt.args.goTemplateOptions)
if tt.expectedErrStr != "" {
require.EqualError(t, err, tt.expectedErrStr)
assert.EqualError(t, err, tt.expectedErrStr)
} else {
require.NoError(t, err)
}

View File

@@ -11,112 +11,73 @@ import (
"github.com/jeremywohl/flatten"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
"github.com/argoproj/argo-cd/v3/applicationset/services"
"github.com/argoproj/argo-cd/v3/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v3/util/gpg"
"github.com/argoproj/argo-cd/v2/applicationset/services"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
var _ Generator = (*GitGenerator)(nil)
type GitGenerator struct {
repos services.Repos
namespace string
repos services.Repos
}
// NewGitGenerator creates a new instance of Git Generator
func NewGitGenerator(repos services.Repos, controllerNamespace string) Generator {
func NewGitGenerator(repos services.Repos) Generator {
g := &GitGenerator{
repos: repos,
namespace: controllerNamespace,
repos: repos,
}
return g
}
// GetTemplate returns the ApplicationSetTemplate associated with the Git generator
// from the provided ApplicationSetGenerator. This template defines how each
// generated Argo CD Application should be rendered.
func (g *GitGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
return &appSetGenerator.Git.Template
}
// GetRequeueAfter returns the duration after which the Git generator should be
// requeued for reconciliation. If RequeueAfterSeconds is set in the generator spec,
// it uses that value. Otherwise, it falls back to a default requeue interval (3 minutes).
func (g *GitGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
// Return a requeue default of 3 minutes, if no default is specified.
if appSetGenerator.Git.RequeueAfterSeconds != nil {
return time.Duration(*appSetGenerator.Git.RequeueAfterSeconds) * time.Second
}
return getDefaultRequeueAfter()
return DefaultRequeueAfterSeconds
}
// GenerateParams generates a list of parameter maps for the ApplicationSet by evaluating the Git generator's configuration.
// It supports both directory-based and file-based Git generators.
func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]any, error) {
func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil {
return nil, ErrEmptyAppSetGenerator
return nil, EmptyAppSetGeneratorError
}
if appSetGenerator.Git == nil {
return nil, ErrEmptyAppSetGenerator
return nil, EmptyAppSetGeneratorError
}
noRevisionCache := appSet.RefreshRequired()
verifyCommit := false
// When the project field is templated, the contents of the git repo are required to run the git generator and get the templated value,
// but git generator cannot be called without verifying the commit signature.
// In this case, we skip the signature verification.
// If the project is templated, we skip the commit verification
if !strings.Contains(appSet.Spec.Template.Spec.Project, "{{") {
project := appSet.Spec.Template.Spec.Project
appProject := &argoprojiov1alpha1.AppProject{}
controllerNamespace := g.namespace
if controllerNamespace == "" {
controllerNamespace = appSet.Namespace
}
if err := client.Get(context.TODO(), types.NamespacedName{Name: project, Namespace: controllerNamespace}, appProject); err != nil {
return nil, fmt.Errorf("error getting project %s: %w", project, err)
}
// we need to verify the signature on the Git revision if GPG is enabled
verifyCommit = len(appProject.Spec.SignatureKeys) > 0 && gpg.IsGPGEnabled()
}
// If the project field is templated, we cannot resolve the project name, so we pass an empty string to the repo-server.
// This means only "globally-scoped" repo credentials can be used for such appsets.
project := resolveProjectName(appSet.Spec.Template.Spec.Project)
var err error
var res []map[string]any
switch {
case len(appSetGenerator.Git.Directories) != 0:
res, err = g.generateParamsForGitDirectories(appSetGenerator, noRevisionCache, verifyCommit, appSet.Spec.GoTemplate, project, appSet.Spec.GoTemplateOptions)
case len(appSetGenerator.Git.Files) != 0:
res, err = g.generateParamsForGitFiles(appSetGenerator, noRevisionCache, verifyCommit, appSet.Spec.GoTemplate, project, appSet.Spec.GoTemplateOptions)
default:
return nil, ErrEmptyAppSetGenerator
var res []map[string]interface{}
if len(appSetGenerator.Git.Directories) != 0 {
res, err = g.generateParamsForGitDirectories(appSetGenerator, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
} else if len(appSetGenerator.Git.Files) != 0 {
res, err = g.generateParamsForGitFiles(appSetGenerator, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
} else {
return nil, EmptyAppSetGeneratorError
}
if err != nil {
return nil, fmt.Errorf("error generating params from git: %w", err)
return nil, err
}
return res, nil
}
// generateParamsForGitDirectories generates parameters for an ApplicationSet using a directory-based Git generator.
// It fetches all directories from the given Git repository and revision, optionally using a revision cache and verifying commits.
// It then filters the directories based on the generator's configuration and renders parameters for the resulting applications
func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit, useGoTemplate bool, project string, goTemplateOptions []string) ([]map[string]any, error) {
allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, project, noRevisionCache, verifyCommit)
func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
// Directories, not files
allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision)
if err != nil {
return nil, fmt.Errorf("error getting directories from repo: %w", err)
return nil, err
}
log.WithFields(log.Fields{
@@ -131,122 +92,76 @@ func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoproj
res, err := g.generateParamsFromApps(requestedApps, appSetGenerator, useGoTemplate, goTemplateOptions)
if err != nil {
return nil, fmt.Errorf("error generating params from apps: %w", err)
return nil, fmt.Errorf("failed to generate params from apps: %w", err)
}
return res, nil
}
// generateParamsForGitFiles generates parameters for an ApplicationSet using a file-based Git generator.
// It retrieves and processes specified files from the Git repository, supporting both YAML and JSON formats,
// and returns a list of parameter maps extracted from the content.
func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit, useGoTemplate bool, project string, goTemplateOptions []string) ([]map[string]any, error) {
// fileContentMap maps absolute file paths to their byte content
fileContentMap := make(map[string][]byte)
var includePatterns []string
var excludePatterns []string
func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
for _, req := range appSetGenerator.Git.Files {
if req.Exclude {
excludePatterns = append(excludePatterns, req.Path)
} else {
includePatterns = append(includePatterns, req.Path)
}
}
// Fetch all files from include patterns
for _, includePattern := range includePatterns {
retrievedFiles, err := g.repos.GetFiles(
context.TODO(),
appSetGenerator.Git.RepoURL,
appSetGenerator.Git.Revision,
project,
includePattern,
noRevisionCache,
verifyCommit,
)
// Get all files that match the requested path string, removing duplicates
allFiles := make(map[string][]byte)
for _, requestedPath := range appSetGenerator.Git.Files {
files, err := g.repos.GetFiles(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, requestedPath.Path)
if err != nil {
return nil, err
}
for absPath, content := range retrievedFiles {
fileContentMap[absPath] = content
for filePath, content := range files {
allFiles[filePath] = content
}
}
// Now remove files matching any exclude pattern
for _, excludePattern := range excludePatterns {
matchingFiles, err := g.repos.GetFiles(
context.TODO(),
appSetGenerator.Git.RepoURL,
appSetGenerator.Git.Revision,
project,
excludePattern,
noRevisionCache,
verifyCommit,
)
if err != nil {
return nil, err
}
for absPath := range matchingFiles {
// if the file doesn't exist already and you try to delete it from the map
// the operation is a no-op. Its safe and doesn't return an error or panic.
// Hence, we can simply try to delete the file from the path without checking
// if that file already exists in the map.
delete(fileContentMap, absPath)
}
// Extract the unduplicated map into a list, and sort by path to ensure a deterministic
// processing order in the subsequent step
allPaths := []string{}
for path := range allFiles {
allPaths = append(allPaths, path)
}
sort.Strings(allPaths)
// Get a sorted list of file paths to ensure deterministic processing order
var filePaths []string
for path := range fileContentMap {
filePaths = append(filePaths, path)
}
sort.Strings(filePaths)
// Generate params from each path, and return
res := []map[string]interface{}{}
for _, path := range allPaths {
var allParams []map[string]any
for _, filePath := range filePaths {
// A JSON / YAML file path can contain multiple sets of parameters (ie it is an array)
paramsFromFileArray, err := g.generateParamsFromGitFile(filePath, fileContentMap[filePath], appSetGenerator.Git.Values, useGoTemplate, goTemplateOptions, appSetGenerator.Git.PathParamPrefix)
paramsArray, err := g.generateParamsFromGitFile(path, allFiles[path], appSetGenerator.Git.Values, useGoTemplate, goTemplateOptions, appSetGenerator.Git.PathParamPrefix)
if err != nil {
return nil, fmt.Errorf("unable to process file '%s': %w", filePath, err)
return nil, fmt.Errorf("unable to process file '%s': %v", path, err)
}
allParams = append(allParams, paramsFromFileArray...)
}
return allParams, nil
res = append(res, paramsArray...)
}
return res, nil
}
// generateParamsFromGitFile parses the content of a Git-tracked file and generates a slice of parameter maps.
// The file can contain a single YAML/JSON object or an array of such objects. Depending on the useGoTemplate flag,
// it either preserves structure for Go templating or flattens the objects for use as plain key-value parameters.
func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []byte, values map[string]string, useGoTemplate bool, goTemplateOptions []string, pathParamPrefix string) ([]map[string]any, error) {
objectsFound := []map[string]any{}
func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []byte, values map[string]string, useGoTemplate bool, goTemplateOptions []string, pathParamPrefix string) ([]map[string]interface{}, error) {
objectsFound := []map[string]interface{}{}
// First, we attempt to parse as a single object.
// This will also succeed for empty files.
singleObj := map[string]any{}
err := yaml.Unmarshal(fileContent, &singleObj)
if err == nil {
objectsFound = append(objectsFound, singleObj)
} else {
// If unable to parse as an object, try to parse as an array
err = yaml.Unmarshal(fileContent, &objectsFound)
// First, we attempt to parse as an array
err := yaml.Unmarshal(fileContent, &objectsFound)
if err != nil {
// If unable to parse as an array, attempt to parse as a single object
singleObj := make(map[string]interface{})
err = yaml.Unmarshal(fileContent, &singleObj)
if err != nil {
return nil, fmt.Errorf("unable to parse file: %w", err)
return nil, fmt.Errorf("unable to parse file: %v", err)
}
objectsFound = append(objectsFound, singleObj)
}
res := []map[string]any{}
res := []map[string]interface{}{}
for _, objectFound := range objectsFound {
params := map[string]any{}
params := map[string]interface{}{}
if useGoTemplate {
for k, v := range objectFound {
params[k] = v
}
paramPath := map[string]any{}
paramPath := map[string]interface{}{}
paramPath["path"] = path.Dir(filePath)
paramPath["basename"] = path.Base(paramPath["path"].(string))
@@ -255,14 +170,14 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
paramPath["filenameNormalized"] = utils.SanitizeName(path.Base(paramPath["filename"].(string)))
paramPath["segments"] = strings.Split(paramPath["path"].(string), "/")
if pathParamPrefix != "" {
params[pathParamPrefix] = map[string]any{"path": paramPath}
params[pathParamPrefix] = map[string]interface{}{"path": paramPath}
} else {
params["path"] = paramPath
}
} else {
flat, err := flatten.Flatten(objectFound, "", flatten.DotStyle)
if err != nil {
return nil, fmt.Errorf("error flattening object: %w", err)
return nil, err
}
for k, v := range flat {
params[k] = fmt.Sprintf("%v", v)
@@ -277,7 +192,7 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
params[pathParamName+".basenameNormalized"] = utils.SanitizeName(path.Base(params[pathParamName].(string)))
params[pathParamName+".filenameNormalized"] = utils.SanitizeName(path.Base(params[pathParamName+".filename"].(string)))
for k, v := range strings.Split(params[pathParamName].(string), "/") {
if v != "" {
if len(v) > 0 {
params[pathParamName+"["+strconv.Itoa(k)+"]"] = v
}
}
@@ -294,15 +209,13 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
return res, nil
}
// filterApps filters the list of all application paths based on inclusion and exclusion rules
// defined in GitDirectoryGeneratorItems. Each item can either include or exclude matching paths.
func (g *GitGenerator) filterApps(directories []argoprojiov1alpha1.GitDirectoryGeneratorItem, allPaths []string) []string {
var res []string
func (g *GitGenerator) filterApps(Directories []argoprojiov1alpha1.GitDirectoryGeneratorItem, allPaths []string) []string {
res := []string{}
for _, appPath := range allPaths {
appInclude := false
appExclude := false
// Iterating over each appPath and check whether directories object has requestedPath that matches the appPath
for _, requestedPath := range directories {
for _, requestedPath := range Directories {
match, err := path.Match(requestedPath.Path, appPath)
if err != nil {
log.WithError(err).WithField("requestedPath", requestedPath).
@@ -324,22 +237,20 @@ func (g *GitGenerator) filterApps(directories []argoprojiov1alpha1.GitDirectoryG
return res
}
// generateParamsFromApps generates a list of parameter maps based on the given app paths.
// Each app path is converted into a parameter object with path metadata (basename, segments, etc.).
// It supports both Go templates and flat key-value parameters.
func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool, goTemplateOptions []string) ([]map[string]any, error) {
res := make([]map[string]any, len(requestedApps))
func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
res := make([]map[string]interface{}, len(requestedApps))
for i, a := range requestedApps {
params := make(map[string]any, 5)
params := make(map[string]interface{}, 5)
if useGoTemplate {
paramPath := map[string]any{}
paramPath := map[string]interface{}{}
paramPath["path"] = a
paramPath["basename"] = path.Base(a)
paramPath["basenameNormalized"] = utils.SanitizeName(path.Base(a))
paramPath["segments"] = strings.Split(paramPath["path"].(string), "/")
if appSetGenerator.Git.PathParamPrefix != "" {
params[appSetGenerator.Git.PathParamPrefix] = map[string]any{"path": paramPath}
params[appSetGenerator.Git.PathParamPrefix] = map[string]interface{}{"path": paramPath}
} else {
params["path"] = paramPath
}
@@ -352,7 +263,7 @@ func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGene
params[pathParamName+".basename"] = path.Base(a)
params[pathParamName+".basenameNormalized"] = utils.SanitizeName(path.Base(a))
for k, v := range strings.Split(params[pathParamName].(string), "/") {
if v != "" {
if len(v) > 0 {
params[pathParamName+"["+strconv.Itoa(k)+"]"] = v
}
}
@@ -368,12 +279,3 @@ func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGene
return res, nil
}
// resolveProjectName resolves a project name whether templated or not
func resolveProjectName(project string) string {
if strings.Contains(project, "{{") {
return ""
}
return project
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,10 @@
package generators
import (
"errors"
"fmt"
"time"
"sigs.k8s.io/controller-runtime/pkg/client"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v3/util/env"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
// Generator defines the interface implemented by all ApplicationSet generators.
@@ -15,7 +12,7 @@ type Generator interface {
// GenerateParams interprets the ApplicationSet and generates all relevant parameters for the application template.
// The expected / desired list of parameters is returned, it then will be render and reconciled
// against the current state of the Applications in the cluster.
GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]any, error)
GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error)
// GetRequeueAfter is the generator can controller the next reconciled loop
// In case there is more then one generator the time will be the minimum of the times.
@@ -26,16 +23,10 @@ type Generator interface {
GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate
}
var (
ErrEmptyAppSetGenerator = errors.New("ApplicationSet is empty")
NoRequeueAfter time.Duration
)
var EmptyAppSetGeneratorError = fmt.Errorf("ApplicationSet is empty")
var NoRequeueAfter time.Duration
// DefaultRequeueAfterSeconds is used when GetRequeueAfter is not specified, it is the default time to wait before the next reconcile loop
const (
DefaultRequeueAfter = 3 * time.Minute
DefaultRequeueAfterSeconds = 3 * time.Minute
)
func getDefaultRequeueAfter() time.Duration {
// Default is 3 minutes, min is 1 second, max is 1 year
return env.ParseDurationFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_REQUEUE_AFTER", DefaultRequeueAfter, 1*time.Second, 8760*time.Hour)
}

View File

@@ -1,29 +0,0 @@
package generators
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func Test_getDefaultRequeueAfter(t *testing.T) {
tests := []struct {
name string
requeueAfterEnv string
want time.Duration
}{
{name: "Default", requeueAfterEnv: "", want: DefaultRequeueAfter},
{name: "Min", requeueAfterEnv: "1s", want: 1 * time.Second},
{name: "Max", requeueAfterEnv: "8760h", want: 8760 * time.Hour},
{name: "Override", requeueAfterEnv: "10m", want: 10 * time.Minute},
{name: "LessThanMin", requeueAfterEnv: "1ms", want: DefaultRequeueAfter},
{name: "MoreThanMax", requeueAfterEnv: "8761h", want: DefaultRequeueAfter},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv("ARGOCD_APPLICATIONSET_CONTROLLER_REQUEUE_AFTER", tt.requeueAfterEnv)
assert.Equalf(t, tt.want, getDefaultRequeueAfter(), "getDefaultRequeueAfter()")
})
}
}

View File

@@ -2,26 +2,25 @@ package generators
import (
"encoding/json"
"errors"
"fmt"
"time"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
var _ Generator = (*ListGenerator)(nil)
type ListGenerator struct{}
type ListGenerator struct {
}
func NewListGenerator() Generator {
g := &ListGenerator{}
return g
}
func (g *ListGenerator) GetRequeueAfter(_ *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
func (g *ListGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
return NoRequeueAfter
}
@@ -29,23 +28,23 @@ func (g *ListGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Applicat
return &appSetGenerator.List.Template
}
func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) {
func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil {
return nil, ErrEmptyAppSetGenerator
return nil, EmptyAppSetGeneratorError
}
if appSetGenerator.List == nil {
return nil, ErrEmptyAppSetGenerator
return nil, EmptyAppSetGeneratorError
}
res := make([]map[string]any, len(appSetGenerator.List.Elements))
res := make([]map[string]interface{}, len(appSetGenerator.List.Elements))
for i, tmpItem := range appSetGenerator.List.Elements {
params := map[string]any{}
var element map[string]any
params := map[string]interface{}{}
var element map[string]interface{}
err := json.Unmarshal(tmpItem.Raw, &element)
if err != nil {
return nil, fmt.Errorf("error unmarshling list element %w", err)
return nil, fmt.Errorf("error unmarshling list element %v", err)
}
if appSet.Spec.GoTemplate {
@@ -53,21 +52,21 @@ func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appli
} else {
for key, value := range element {
if key == "values" {
values, ok := (value).(map[string]any)
values, ok := (value).(map[string]interface{})
if !ok {
return nil, errors.New("error parsing values map")
return nil, fmt.Errorf("error parsing values map")
}
for k, v := range values {
value, ok := v.(string)
if !ok {
return nil, fmt.Errorf("error parsing value as string %w", err)
return nil, fmt.Errorf("error parsing value as string %v", err)
}
params["values."+k] = value
params[fmt.Sprintf("values.%s", k)] = value
}
} else {
v, ok := value.(string)
if !ok {
return nil, fmt.Errorf("error parsing value as string %w", err)
return nil, fmt.Errorf("error parsing value as string %v", err)
}
params[key] = v
}
@@ -77,13 +76,14 @@ func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appli
}
// Append elements from ElementsYaml to the response
if appSetGenerator.List.ElementsYaml != "" {
var yamlElements []map[string]any
if len(appSetGenerator.List.ElementsYaml) > 0 {
var yamlElements []map[string]interface{}
err := yaml.Unmarshal([]byte(appSetGenerator.List.ElementsYaml), &yamlElements)
if err != nil {
return nil, fmt.Errorf("error unmarshling decoded ElementsYaml %w", err)
return nil, fmt.Errorf("error unmarshling decoded ElementsYaml %v", err)
}
res = append(res, yamlElements...)
res = append(res, yamlElements...)
}
return res, nil

View File

@@ -4,29 +4,29 @@ import (
"testing"
"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"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func TestGenerateListParams(t *testing.T) {
testCases := []struct {
elements []apiextensionsv1.JSON
expected []map[string]any
expected []map[string]interface{}
}{
{
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
expected: []map[string]any{{"cluster": "cluster", "url": "url"}},
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url"}},
}, {
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}},
expected: []map[string]any{{"cluster": "cluster", "url": "url", "values.foo": "bar"}},
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url", "values.foo": "bar"}},
},
}
for _, testCase := range testCases {
listGenerator := NewListGenerator()
var listGenerator = NewListGenerator()
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -38,30 +38,31 @@ func TestGenerateListParams(t *testing.T) {
got, err := listGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
List: &argoprojiov1alpha1.ListGenerator{
Elements: testCase.elements,
},
}, &applicationSetInfo, nil)
}}, &applicationSetInfo)
require.NoError(t, err)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, got)
}
}
func TestGenerateListParamsGoTemplate(t *testing.T) {
testCases := []struct {
elements []apiextensionsv1.JSON
expected []map[string]any
expected []map[string]interface{}
}{
{
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
expected: []map[string]any{{"cluster": "cluster", "url": "url"}},
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url"}},
}, {
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}},
expected: []map[string]any{{"cluster": "cluster", "url": "url", "values": map[string]any{"foo": "bar"}}},
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url", "values": map[string]interface{}{"foo": "bar"}}},
},
}
for _, testCase := range testCases {
listGenerator := NewListGenerator()
var listGenerator = NewListGenerator()
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -75,10 +76,9 @@ func TestGenerateListParamsGoTemplate(t *testing.T) {
got, err := listGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
List: &argoprojiov1alpha1.ListGenerator{
Elements: testCase.elements,
},
}, &applicationSetInfo, nil)
}}, &applicationSetInfo)
require.NoError(t, err)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, got)
}
}

View File

@@ -1,23 +1,23 @@
package generators
import (
"errors"
"fmt"
"time"
"dario.cat/mergo"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/imdario/mergo"
"github.com/argoproj/argo-cd/v3/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
log "github.com/sirupsen/logrus"
)
var _ Generator = (*MatrixGenerator)(nil)
var (
ErrMoreThanTwoGenerators = errors.New("found more than two generators, Matrix support only two")
ErrLessThanTwoGenerators = errors.New("found less than two generators, Matrix support only two")
ErrMoreThenOneInnerGenerators = errors.New("found more than one generator in matrix.Generators")
ErrMoreThanTwoGenerators = fmt.Errorf("found more than two generators, Matrix support only two")
ErrLessThanTwoGenerators = fmt.Errorf("found less than two generators, Matrix support only two")
ErrMoreThenOneInnerGenerators = fmt.Errorf("found more than one generator in matrix.Generators")
)
type MatrixGenerator struct {
@@ -32,9 +32,10 @@ func NewMatrixGenerator(supportedGenerators map[string]Generator) Generator {
return m
}
func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]any, error) {
func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator.Matrix == nil {
return nil, ErrEmptyAppSetGenerator
return nil, EmptyAppSetGeneratorError
}
if len(appSetGenerator.Matrix.Generators) < 2 {
@@ -45,25 +46,26 @@ func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.App
return nil, ErrMoreThanTwoGenerators
}
res := []map[string]any{}
res := []map[string]interface{}{}
g0, err := m.getParams(appSetGenerator.Matrix.Generators[0], appSet, nil, client)
g0, err := m.getParams(appSetGenerator.Matrix.Generators[0], appSet, nil)
if err != nil {
return nil, fmt.Errorf("error failed to get params for first generator in matrix generator: %w", err)
return nil, err
}
for _, a := range g0 {
g1, err := m.getParams(appSetGenerator.Matrix.Generators[1], appSet, a, client)
g1, err := m.getParams(appSetGenerator.Matrix.Generators[1], appSet, a)
if err != nil {
return nil, fmt.Errorf("failed to get params for second generator in the matrix generator: %w", err)
}
for _, b := range g1 {
if appSet.Spec.GoTemplate {
tmp := map[string]any{}
if err := mergo.Merge(&tmp, b, mergo.WithOverride); err != nil {
return nil, fmt.Errorf("failed to merge params from the second generator in the matrix generator with temp map: %w", err)
tmp := map[string]interface{}{}
if err := mergo.Merge(&tmp, a); err != nil {
return nil, fmt.Errorf("failed to merge params from the first generator in the matrix generator with temp map: %w", err)
}
if err := mergo.Merge(&tmp, a, mergo.WithOverride); err != nil {
return nil, fmt.Errorf("failed to merge params from the second generator in the matrix generator with the first: %w", err)
if err := mergo.Merge(&tmp, b); err != nil {
return nil, fmt.Errorf("failed to merge params from the first generator in the matrix generator with the second: %w", err)
}
res = append(res, tmp)
} else {
@@ -71,7 +73,7 @@ func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.App
if err != nil {
return nil, fmt.Errorf("failed to combine string maps with merging params for the matrix generator: %w", err)
}
res = append(res, val)
res = append(res, utils.ConvertToMapStringInterface(val))
}
}
}
@@ -79,14 +81,26 @@ func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.App
return res, nil
}
func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, params map[string]any, client client.Client) ([]map[string]any, error) {
func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, params map[string]interface{}) ([]map[string]interface{}, error) {
matrixGen, err := getMatrixGenerator(appSetBaseGenerator)
if err != nil {
return nil, err
}
if matrixGen != nil && !appSet.Spec.ApplyNestedSelectors {
foundSelector := dropDisabledNestedSelectors(matrixGen.Generators)
if foundSelector {
log.Warnf("AppSet '%v' defines selector on nested matrix generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selectors", appSet.Name)
}
}
mergeGen, err := getMergeGenerator(appSetBaseGenerator)
if err != nil {
return nil, fmt.Errorf("error retrieving merge generator: %w", err)
return nil, err
}
if mergeGen != nil && !appSet.Spec.ApplyNestedSelectors {
foundSelector := dropDisabledNestedSelectors(mergeGen.Generators)
if foundSelector {
log.Warnf("AppSet '%v' defines selector on nested merge generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selectors", appSet.Name)
}
}
t, err := Transform(
@@ -105,14 +119,14 @@ func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Appli
m.supportedGenerators,
argoprojiov1alpha1.ApplicationSetTemplate{},
appSet,
params,
client)
params)
if err != nil {
return nil, fmt.Errorf("child generator returned an error on parameter generation: %w", err)
return nil, fmt.Errorf("child generator returned an error on parameter generation: %v", err)
}
if len(t) == 0 {
return nil, errors.New("child generator generated no parameters")
return nil, fmt.Errorf("child generator generated no parameters")
}
if len(t) > 1 {
@@ -132,15 +146,13 @@ func (m *MatrixGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Ap
matrixGen, _ := getMatrixGenerator(r)
mergeGen, _ := getMergeGenerator(r)
base := &argoprojiov1alpha1.ApplicationSetGenerator{
List: r.List,
Clusters: r.Clusters,
Git: r.Git,
PullRequest: r.PullRequest,
Plugin: r.Plugin,
SCMProvider: r.SCMProvider,
ClusterDecisionResource: r.ClusterDecisionResource,
Matrix: matrixGen,
Merge: mergeGen,
List: r.List,
Clusters: r.Clusters,
Git: r.Git,
PullRequest: r.PullRequest,
Plugin: r.Plugin,
Matrix: matrixGen,
Merge: mergeGen,
}
generators := GetRelevantGenerators(base, m.supportedGenerators)
@@ -155,8 +167,10 @@ func (m *MatrixGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Ap
if found {
return res
} else {
return NoRequeueAfter
}
return NoRequeueAfter
}
func getMatrixGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*argoprojiov1alpha1.MatrixGenerator, error) {

File diff suppressed because it is too large Load Diff

View File

@@ -2,23 +2,23 @@ package generators
import (
"encoding/json"
"errors"
"fmt"
"maps"
"time"
"dario.cat/mergo"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/imdario/mergo"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
log "github.com/sirupsen/logrus"
)
var _ Generator = (*MergeGenerator)(nil)
var (
ErrLessThanTwoGeneratorsInMerge = errors.New("found less than two generators, Merge requires two or more")
ErrNoMergeKeys = errors.New("no merge keys were specified, Merge requires at least one")
ErrNonUniqueParamSets = errors.New("the parameters from a generator were not unique by the given mergeKeys, Merge requires all param sets to be unique")
ErrLessThanTwoGeneratorsInMerge = fmt.Errorf("found less than two generators, Merge requires two or more")
ErrNoMergeKeys = fmt.Errorf("no merge keys were specified, Merge requires at least one")
ErrNonUniqueParamSets = fmt.Errorf("the parameters from a generator were not unique by the given mergeKeys, Merge requires all param sets to be unique")
)
type MergeGenerator struct {
@@ -36,12 +36,12 @@ func NewMergeGenerator(supportedGenerators map[string]Generator) Generator {
// getParamSetsForAllGenerators generates params for each child generator in a MergeGenerator. Param sets are returned
// in slices ordered according to the order of the given generators.
func (m *MergeGenerator) getParamSetsForAllGenerators(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([][]map[string]any, error) {
var paramSets [][]map[string]any
for i, generator := range generators {
generatorParamSets, err := m.getParams(generator, appSet, client)
func (m *MergeGenerator) getParamSetsForAllGenerators(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([][]map[string]interface{}, error) {
var paramSets [][]map[string]interface{}
for _, generator := range generators {
generatorParamSets, err := m.getParams(generator, appSet)
if err != nil {
return nil, fmt.Errorf("error getting params from generator %d of %d: %w", i+1, len(generators), err)
return nil, err
}
// concatenate param lists produced by each generator
paramSets = append(paramSets, generatorParamSets)
@@ -50,51 +50,55 @@ func (m *MergeGenerator) getParamSetsForAllGenerators(generators []argoprojiov1a
}
// GenerateParams gets the params produced by the MergeGenerator.
func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]any, error) {
func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator.Merge == nil {
return nil, ErrEmptyAppSetGenerator
return nil, EmptyAppSetGeneratorError
}
if len(appSetGenerator.Merge.Generators) < 2 {
return nil, ErrLessThanTwoGeneratorsInMerge
}
paramSetsFromGenerators, err := m.getParamSetsForAllGenerators(appSetGenerator.Merge.Generators, appSet, client)
paramSetsFromGenerators, err := m.getParamSetsForAllGenerators(appSetGenerator.Merge.Generators, appSet)
if err != nil {
return nil, fmt.Errorf("error getting param sets from generators: %w", err)
return nil, err
}
baseParamSetsByMergeKey, err := getParamSetsByMergeKey(appSetGenerator.Merge.MergeKeys, paramSetsFromGenerators[0])
if err != nil {
return nil, fmt.Errorf("error getting param sets by merge key: %w", err)
return nil, err
}
for _, paramSets := range paramSetsFromGenerators[1:] {
paramSetsByMergeKey, err := getParamSetsByMergeKey(appSetGenerator.Merge.MergeKeys, paramSets)
if err != nil {
return nil, fmt.Errorf("error getting param sets by merge key: %w", err)
return nil, err
}
for mergeKeyValue, baseParamSet := range baseParamSetsByMergeKey {
if overrideParamSet, exists := paramSetsByMergeKey[mergeKeyValue]; exists {
if appSet.Spec.GoTemplate {
if err := mergo.Merge(&baseParamSet, overrideParamSet, mergo.WithOverride); err != nil {
return nil, fmt.Errorf("error merging base param set with override param set: %w", err)
return nil, fmt.Errorf("failed to merge base param set with override param set: %w", err)
}
baseParamSetsByMergeKey[mergeKeyValue] = baseParamSet
} else {
maps.Copy(baseParamSet, overrideParamSet)
baseParamSetsByMergeKey[mergeKeyValue] = baseParamSet
overriddenParamSet, err := utils.CombineStringMapsAllowDuplicates(baseParamSet, overrideParamSet)
if err != nil {
return nil, err
}
baseParamSetsByMergeKey[mergeKeyValue] = utils.ConvertToMapStringInterface(overriddenParamSet)
}
}
}
}
mergedParamSets := make([]map[string]any, len(baseParamSetsByMergeKey))
i := 0
mergedParamSets := make([]map[string]interface{}, len(baseParamSetsByMergeKey))
var i = 0
for _, mergedParamSet := range baseParamSetsByMergeKey {
mergedParamSets[i] = mergedParamSet
i++
i += 1
}
return mergedParamSets, nil
@@ -103,7 +107,7 @@ func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appl
// getParamSetsByMergeKey converts the given list of parameter sets to a map of parameter sets where the key is the
// unique key of the parameter set as determined by the given mergeKeys. If any two parameter sets share the same merge
// key, getParamSetsByMergeKey will throw NonUniqueParamSets.
func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]any) (map[string]map[string]any, error) {
func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]interface{}) (map[string]map[string]interface{}, error) {
if len(mergeKeys) < 1 {
return nil, ErrNoMergeKeys
}
@@ -113,17 +117,17 @@ func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]any) (map
deDuplicatedMergeKeys[mergeKey] = false
}
paramSetsByMergeKey := make(map[string]map[string]any, len(paramSets))
paramSetsByMergeKey := make(map[string]map[string]interface{}, len(paramSets))
for _, paramSet := range paramSets {
paramSetKey := make(map[string]any)
paramSetKey := make(map[string]interface{})
for mergeKey := range deDuplicatedMergeKeys {
paramSetKey[mergeKey] = paramSet[mergeKey]
}
paramSetKeyJSON, err := json.Marshal(paramSetKey)
paramSetKeyJson, err := json.Marshal(paramSetKey)
if err != nil {
return nil, fmt.Errorf("error marshalling param set key json: %w", err)
return nil, err
}
paramSetKeyString := string(paramSetKeyJSON)
paramSetKeyString := string(paramSetKeyJson)
if _, exists := paramSetsByMergeKey[paramSetKeyString]; exists {
return nil, fmt.Errorf("%w. Duplicate key was %s", ErrNonUniqueParamSets, paramSetKeyString)
}
@@ -134,15 +138,27 @@ func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]any) (map
}
// getParams get the parameters generated by this generator.
func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]any, error) {
func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
matrixGen, err := getMatrixGenerator(appSetBaseGenerator)
if err != nil {
return nil, err
}
if matrixGen != nil && !appSet.Spec.ApplyNestedSelectors {
foundSelector := dropDisabledNestedSelectors(matrixGen.Generators)
if foundSelector {
log.Warnf("AppSet '%v' defines selector on nested matrix generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selector", appSet.Name)
}
}
mergeGen, err := getMergeGenerator(appSetBaseGenerator)
if err != nil {
return nil, err
}
if mergeGen != nil && !appSet.Spec.ApplyNestedSelectors {
foundSelector := dropDisabledNestedSelectors(mergeGen.Generators)
if foundSelector {
log.Warnf("AppSet '%v' defines selector on nested merge generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selector", appSet.Name)
}
}
t, err := Transform(
argoprojiov1alpha1.ApplicationSetGenerator{
@@ -160,13 +176,14 @@ func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Applic
m.supportedGenerators,
argoprojiov1alpha1.ApplicationSetTemplate{},
appSet,
map[string]any{}, client)
map[string]interface{}{})
if err != nil {
return nil, fmt.Errorf("child generator returned an error on parameter generation: %w", err)
return nil, fmt.Errorf("child generator returned an error on parameter generation: %v", err)
}
if len(t) == 0 {
return nil, errors.New("child generator generated no parameters")
return nil, fmt.Errorf("child generator generated no parameters")
}
if len(t) > 1 {
@@ -184,15 +201,13 @@ func (m *MergeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.App
matrixGen, _ := getMatrixGenerator(r)
mergeGen, _ := getMergeGenerator(r)
base := &argoprojiov1alpha1.ApplicationSetGenerator{
List: r.List,
Clusters: r.Clusters,
Git: r.Git,
PullRequest: r.PullRequest,
Plugin: r.Plugin,
SCMProvider: r.SCMProvider,
ClusterDecisionResource: r.ClusterDecisionResource,
Matrix: matrixGen,
Merge: mergeGen,
List: r.List,
Clusters: r.Clusters,
Git: r.Git,
PullRequest: r.PullRequest,
Plugin: r.Plugin,
Matrix: matrixGen,
Merge: mergeGen,
}
generators := GetRelevantGenerators(base, m.supportedGenerators)
@@ -207,8 +222,10 @@ func (m *MergeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.App
if found {
return res
} else {
return NoRequeueAfter
}
return NoRequeueAfter
}
func getMergeGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*argoprojiov1alpha1.MergeGenerator, error) {
@@ -217,7 +234,7 @@ func getMergeGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*arg
}
merge, err := argoprojiov1alpha1.ToNestedMergeGenerator(r.Merge)
if err != nil {
return nil, fmt.Errorf("error converting to nested merge generator: %w", err)
return nil, err
}
return merge.ToMergeGenerator(), nil
}

View File

@@ -6,10 +6,9 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func getNestedListGenerator(json string) *argoprojiov1alpha1.ApplicationSetNestedGenerator {
@@ -36,28 +35,27 @@ func getTerminalListGeneratorMultiple(jsons []string) argoprojiov1alpha1.Applica
return generator
}
func listOfMapsToSet(maps []map[string]any) (map[string]bool, error) {
func listOfMapsToSet(maps []map[string]interface{}) (map[string]bool, error) {
set := make(map[string]bool, len(maps))
for _, paramMap := range maps {
paramMapAsJSON, err := json.Marshal(paramMap)
paramMapAsJson, err := json.Marshal(paramMap)
if err != nil {
return nil, err
}
set[string(paramMapAsJSON)] = false
set[string(paramMapAsJson)] = false
}
return set, nil
}
func TestMergeGenerate(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
mergeKeys []string
expectedErr error
expected []map[string]any
expected []map[string]interface{}
}{
{
name: "no generators",
@@ -81,7 +79,7 @@ func TestMergeGenerate(t *testing.T) {
*getNestedListGenerator(`{"a": "3_1","b": "different","c": "3_3"}`), // gets ignored because its merge key value isn't in the base params set
},
mergeKeys: []string{"b"},
expected: []map[string]any{
expected: []map[string]interface{}{
{"a": "2_1", "b": "same", "c": "1_3"},
},
},
@@ -92,7 +90,7 @@ func TestMergeGenerate(t *testing.T) {
*getNestedListGenerator(`{"a": "a"}`),
},
mergeKeys: []string{"b"},
expected: []map[string]any{
expected: []map[string]interface{}{
{"a": "a"},
},
},
@@ -103,7 +101,7 @@ func TestMergeGenerate(t *testing.T) {
*getNestedListGenerator(`{"b": "b"}`),
},
mergeKeys: []string{"b"},
expected: []map[string]any{
expected: []map[string]interface{}{
{"a": "a"},
},
},
@@ -121,7 +119,7 @@ func TestMergeGenerate(t *testing.T) {
*getNestedListGenerator(`{"a": "1", "b": "1", "c": "added"}`),
},
mergeKeys: []string{"a", "b"},
expected: []map[string]any{
expected: []map[string]interface{}{
{"a": "1", "b": "1", "c": "added"},
{"a": "1", "b": "2"},
{"a": "2", "b": "1"},
@@ -143,7 +141,7 @@ func TestMergeGenerate(t *testing.T) {
*getNestedListGenerator(`{"a": "1", "b": "3", "d": "added"}`),
},
mergeKeys: []string{"a", "b"},
expected: []map[string]any{
expected: []map[string]interface{}{
{"a": "1", "b": "3", "c": "added", "d": "added"},
{"a": "2", "b": "2"},
},
@@ -158,7 +156,7 @@ func TestMergeGenerate(t *testing.T) {
appSet := &argoprojiov1alpha1.ApplicationSet{}
mergeGenerator := NewMergeGenerator(
var mergeGenerator = NewMergeGenerator(
map[string]Generator{
"List": &ListGenerator{},
"Matrix": &MatrixGenerator{
@@ -180,26 +178,26 @@ func TestMergeGenerate(t *testing.T) {
MergeKeys: testCaseCopy.mergeKeys,
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
},
}, appSet, nil)
}, appSet)
if testCaseCopy.expectedErr != nil {
require.EqualError(t, err, testCaseCopy.expectedErr.Error())
assert.EqualError(t, err, testCaseCopy.expectedErr.Error())
} else {
expectedSet, err := listOfMapsToSet(testCaseCopy.expected)
require.NoError(t, err)
assert.NoError(t, err)
actualSet, err := listOfMapsToSet(got)
require.NoError(t, err)
assert.NoError(t, err)
require.NoError(t, err)
assert.NoError(t, err)
assert.Equal(t, expectedSet, actualSet)
}
})
}
}
func toAPIExtensionsJSON(t *testing.T, g any) *apiextensionsv1.JSON {
t.Helper()
func toAPIExtensionsJSON(t *testing.T, g interface{}) *apiextensionsv1.JSON {
resVal, err := json.Marshal(g)
if err != nil {
t.Error("unable to unmarshal json", g)
@@ -212,14 +210,12 @@ func toAPIExtensionsJSON(t *testing.T, g any) *apiextensionsv1.JSON {
}
func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
mergeKeys []string
paramSets []map[string]any
paramSets []map[string]interface{}
expectedErr error
expected map[string]map[string]any
expected map[string]map[string]interface{}
}{
{
name: "no merge keys",
@@ -229,13 +225,13 @@ func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
{
name: "no paramSets",
mergeKeys: []string{"key"},
expected: make(map[string]map[string]any),
expected: make(map[string]map[string]interface{}),
},
{
name: "simple key, unique paramSets",
mergeKeys: []string{"key"},
paramSets: []map[string]any{{"key": "a"}, {"key": "b"}},
expected: map[string]map[string]any{
paramSets: []map[string]interface{}{{"key": "a"}, {"key": "b"}},
expected: map[string]map[string]interface{}{
`{"key":"a"}`: {"key": "a"},
`{"key":"b"}`: {"key": "b"},
},
@@ -243,23 +239,23 @@ func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
{
name: "simple key object, unique paramSets",
mergeKeys: []string{"key"},
paramSets: []map[string]any{{"key": map[string]any{"hello": "world"}}, {"key": "b"}},
expected: map[string]map[string]any{
`{"key":{"hello":"world"}}`: {"key": map[string]any{"hello": "world"}},
paramSets: []map[string]interface{}{{"key": map[string]interface{}{"hello": "world"}}, {"key": "b"}},
expected: map[string]map[string]interface{}{
`{"key":{"hello":"world"}}`: {"key": map[string]interface{}{"hello": "world"}},
`{"key":"b"}`: {"key": "b"},
},
},
{
name: "simple key, non-unique paramSets",
mergeKeys: []string{"key"},
paramSets: []map[string]any{{"key": "a"}, {"key": "b"}, {"key": "b"}},
paramSets: []map[string]interface{}{{"key": "a"}, {"key": "b"}, {"key": "b"}},
expectedErr: fmt.Errorf("%w. Duplicate key was %s", ErrNonUniqueParamSets, `{"key":"b"}`),
},
{
name: "simple key, duplicated key name, unique paramSets",
mergeKeys: []string{"key", "key"},
paramSets: []map[string]any{{"key": "a"}, {"key": "b"}},
expected: map[string]map[string]any{
paramSets: []map[string]interface{}{{"key": "a"}, {"key": "b"}},
expected: map[string]map[string]interface{}{
`{"key":"a"}`: {"key": "a"},
`{"key":"b"}`: {"key": "b"},
},
@@ -267,18 +263,18 @@ func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
{
name: "simple key, duplicated key name, non-unique paramSets",
mergeKeys: []string{"key", "key"},
paramSets: []map[string]any{{"key": "a"}, {"key": "b"}, {"key": "b"}},
paramSets: []map[string]interface{}{{"key": "a"}, {"key": "b"}, {"key": "b"}},
expectedErr: fmt.Errorf("%w. Duplicate key was %s", ErrNonUniqueParamSets, `{"key":"b"}`),
},
{
name: "compound key, unique paramSets",
mergeKeys: []string{"key1", "key2"},
paramSets: []map[string]any{
paramSets: []map[string]interface{}{
{"key1": "a", "key2": "a"},
{"key1": "a", "key2": "b"},
{"key1": "b", "key2": "a"},
},
expected: map[string]map[string]any{
expected: map[string]map[string]interface{}{
`{"key1":"a","key2":"a"}`: {"key1": "a", "key2": "a"},
`{"key1":"a","key2":"b"}`: {"key1": "a", "key2": "b"},
`{"key1":"b","key2":"a"}`: {"key1": "b", "key2": "a"},
@@ -287,13 +283,13 @@ func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
{
name: "compound key object, unique paramSets",
mergeKeys: []string{"key1", "key2"},
paramSets: []map[string]any{
{"key1": "a", "key2": map[string]any{"hello": "world"}},
paramSets: []map[string]interface{}{
{"key1": "a", "key2": map[string]interface{}{"hello": "world"}},
{"key1": "a", "key2": "b"},
{"key1": "b", "key2": "a"},
},
expected: map[string]map[string]any{
`{"key1":"a","key2":{"hello":"world"}}`: {"key1": "a", "key2": map[string]any{"hello": "world"}},
expected: map[string]map[string]interface{}{
`{"key1":"a","key2":{"hello":"world"}}`: {"key1": "a", "key2": map[string]interface{}{"hello": "world"}},
`{"key1":"a","key2":"b"}`: {"key1": "a", "key2": "b"},
`{"key1":"b","key2":"a"}`: {"key1": "b", "key2": "a"},
},
@@ -301,12 +297,12 @@ func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
{
name: "compound key, duplicate key names, unique paramSets",
mergeKeys: []string{"key1", "key1", "key2"},
paramSets: []map[string]any{
paramSets: []map[string]interface{}{
{"key1": "a", "key2": "a"},
{"key1": "a", "key2": "b"},
{"key1": "b", "key2": "a"},
},
expected: map[string]map[string]any{
expected: map[string]map[string]interface{}{
`{"key1":"a","key2":"a"}`: {"key1": "a", "key2": "a"},
`{"key1":"a","key2":"b"}`: {"key1": "a", "key2": "b"},
`{"key1":"b","key2":"a"}`: {"key1": "b", "key2": "a"},
@@ -315,7 +311,7 @@ func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
{
name: "compound key, non-unique paramSets",
mergeKeys: []string{"key1", "key2"},
paramSets: []map[string]any{
paramSets: []map[string]interface{}{
{"key1": "a", "key2": "a"},
{"key1": "a", "key2": "a"},
{"key1": "b", "key2": "a"},
@@ -325,7 +321,7 @@ func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
{
name: "compound key, duplicate key names, non-unique paramSets",
mergeKeys: []string{"key1", "key1", "key2"},
paramSets: []map[string]any{
paramSets: []map[string]interface{}{
{"key1": "a", "key2": "a"},
{"key1": "a", "key2": "a"},
{"key1": "b", "key2": "a"},
@@ -343,11 +339,13 @@ func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
got, err := getParamSetsByMergeKey(testCaseCopy.mergeKeys, testCaseCopy.paramSets)
if testCaseCopy.expectedErr != nil {
require.EqualError(t, err, testCaseCopy.expectedErr.Error())
assert.EqualError(t, err, testCaseCopy.expectedErr.Error())
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got)
}
})
}
}

View File

@@ -1,218 +0,0 @@
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
package mocks
import (
"time"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
mock "github.com/stretchr/testify/mock"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// NewGenerator creates a new instance of Generator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewGenerator(t interface {
mock.TestingT
Cleanup(func())
}) *Generator {
mock := &Generator{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
// Generator is an autogenerated mock type for the Generator type
type Generator struct {
mock.Mock
}
type Generator_Expecter struct {
mock *mock.Mock
}
func (_m *Generator) EXPECT() *Generator_Expecter {
return &Generator_Expecter{mock: &_m.Mock}
}
// GenerateParams provides a mock function for the type Generator
func (_mock *Generator) GenerateParams(appSetGenerator *v1alpha1.ApplicationSetGenerator, applicationSetInfo *v1alpha1.ApplicationSet, client1 client.Client) ([]map[string]any, error) {
ret := _mock.Called(appSetGenerator, applicationSetInfo, client1)
if len(ret) == 0 {
panic("no return value specified for GenerateParams")
}
var r0 []map[string]any
var r1 error
if returnFunc, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) ([]map[string]any, error)); ok {
return returnFunc(appSetGenerator, applicationSetInfo, client1)
}
if returnFunc, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) []map[string]any); ok {
r0 = returnFunc(appSetGenerator, applicationSetInfo, client1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]map[string]any)
}
}
if returnFunc, ok := ret.Get(1).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) error); ok {
r1 = returnFunc(appSetGenerator, applicationSetInfo, client1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Generator_GenerateParams_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GenerateParams'
type Generator_GenerateParams_Call struct {
*mock.Call
}
// GenerateParams is a helper method to define mock.On call
// - appSetGenerator *v1alpha1.ApplicationSetGenerator
// - applicationSetInfo *v1alpha1.ApplicationSet
// - client1 client.Client
func (_e *Generator_Expecter) GenerateParams(appSetGenerator interface{}, applicationSetInfo interface{}, client1 interface{}) *Generator_GenerateParams_Call {
return &Generator_GenerateParams_Call{Call: _e.mock.On("GenerateParams", appSetGenerator, applicationSetInfo, client1)}
}
func (_c *Generator_GenerateParams_Call) Run(run func(appSetGenerator *v1alpha1.ApplicationSetGenerator, applicationSetInfo *v1alpha1.ApplicationSet, client1 client.Client)) *Generator_GenerateParams_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 *v1alpha1.ApplicationSetGenerator
if args[0] != nil {
arg0 = args[0].(*v1alpha1.ApplicationSetGenerator)
}
var arg1 *v1alpha1.ApplicationSet
if args[1] != nil {
arg1 = args[1].(*v1alpha1.ApplicationSet)
}
var arg2 client.Client
if args[2] != nil {
arg2 = args[2].(client.Client)
}
run(
arg0,
arg1,
arg2,
)
})
return _c
}
func (_c *Generator_GenerateParams_Call) Return(stringToVs []map[string]any, err error) *Generator_GenerateParams_Call {
_c.Call.Return(stringToVs, err)
return _c
}
func (_c *Generator_GenerateParams_Call) RunAndReturn(run func(appSetGenerator *v1alpha1.ApplicationSetGenerator, applicationSetInfo *v1alpha1.ApplicationSet, client1 client.Client) ([]map[string]any, error)) *Generator_GenerateParams_Call {
_c.Call.Return(run)
return _c
}
// GetRequeueAfter provides a mock function for the type Generator
func (_mock *Generator) GetRequeueAfter(appSetGenerator *v1alpha1.ApplicationSetGenerator) time.Duration {
ret := _mock.Called(appSetGenerator)
if len(ret) == 0 {
panic("no return value specified for GetRequeueAfter")
}
var r0 time.Duration
if returnFunc, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator) time.Duration); ok {
r0 = returnFunc(appSetGenerator)
} else {
r0 = ret.Get(0).(time.Duration)
}
return r0
}
// Generator_GetRequeueAfter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRequeueAfter'
type Generator_GetRequeueAfter_Call struct {
*mock.Call
}
// GetRequeueAfter is a helper method to define mock.On call
// - appSetGenerator *v1alpha1.ApplicationSetGenerator
func (_e *Generator_Expecter) GetRequeueAfter(appSetGenerator interface{}) *Generator_GetRequeueAfter_Call {
return &Generator_GetRequeueAfter_Call{Call: _e.mock.On("GetRequeueAfter", appSetGenerator)}
}
func (_c *Generator_GetRequeueAfter_Call) Run(run func(appSetGenerator *v1alpha1.ApplicationSetGenerator)) *Generator_GetRequeueAfter_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 *v1alpha1.ApplicationSetGenerator
if args[0] != nil {
arg0 = args[0].(*v1alpha1.ApplicationSetGenerator)
}
run(
arg0,
)
})
return _c
}
func (_c *Generator_GetRequeueAfter_Call) Return(duration time.Duration) *Generator_GetRequeueAfter_Call {
_c.Call.Return(duration)
return _c
}
func (_c *Generator_GetRequeueAfter_Call) RunAndReturn(run func(appSetGenerator *v1alpha1.ApplicationSetGenerator) time.Duration) *Generator_GetRequeueAfter_Call {
_c.Call.Return(run)
return _c
}
// GetTemplate provides a mock function for the type Generator
func (_mock *Generator) GetTemplate(appSetGenerator *v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate {
ret := _mock.Called(appSetGenerator)
if len(ret) == 0 {
panic("no return value specified for GetTemplate")
}
var r0 *v1alpha1.ApplicationSetTemplate
if returnFunc, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate); ok {
r0 = returnFunc(appSetGenerator)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.ApplicationSetTemplate)
}
}
return r0
}
// Generator_GetTemplate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTemplate'
type Generator_GetTemplate_Call struct {
*mock.Call
}
// GetTemplate is a helper method to define mock.On call
// - appSetGenerator *v1alpha1.ApplicationSetGenerator
func (_e *Generator_Expecter) GetTemplate(appSetGenerator interface{}) *Generator_GetTemplate_Call {
return &Generator_GetTemplate_Call{Call: _e.mock.On("GetTemplate", appSetGenerator)}
}
func (_c *Generator_GetTemplate_Call) Run(run func(appSetGenerator *v1alpha1.ApplicationSetGenerator)) *Generator_GetTemplate_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 *v1alpha1.ApplicationSetGenerator
if args[0] != nil {
arg0 = args[0].(*v1alpha1.ApplicationSetGenerator)
}
run(
arg0,
)
})
return _c
}
func (_c *Generator_GetTemplate_Call) Return(applicationSetTemplate *v1alpha1.ApplicationSetTemplate) *Generator_GetTemplate_Call {
_c.Call.Return(applicationSetTemplate)
return _c
}
func (_c *Generator_GetTemplate_Call) RunAndReturn(run func(appSetGenerator *v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate) *Generator_GetTemplate_Call {
_c.Call.Return(run)
return _c
}

View File

@@ -2,7 +2,6 @@ package generators
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
@@ -10,28 +9,33 @@ import (
"github.com/jeremywohl/flatten"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v3/util/settings"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/settings"
"github.com/argoproj/argo-cd/v3/applicationset/services/plugin"
"github.com/argoproj/argo-cd/v2/applicationset/services/plugin"
)
const (
DefaultPluginRequeueAfter = 30 * time.Minute
DefaultPluginRequeueAfterSeconds = 30 * time.Minute
)
var _ Generator = (*PluginGenerator)(nil)
type PluginGenerator struct {
client client.Client
ctx context.Context
clientset kubernetes.Interface
namespace string
}
func NewPluginGenerator(client client.Client, namespace string) Generator {
func NewPluginGenerator(client client.Client, ctx context.Context, clientset kubernetes.Interface, namespace string) Generator {
g := &PluginGenerator{
client: client,
ctx: ctx,
clientset: clientset,
namespace: namespace,
}
return g
@@ -44,20 +48,21 @@ func (g *PluginGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Ap
return time.Duration(*appSetGenerator.Plugin.RequeueAfterSeconds) * time.Second
}
return DefaultPluginRequeueAfter
return DefaultPluginRequeueAfterSeconds
}
func (g *PluginGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
return &appSetGenerator.Plugin.Template
}
func (g *PluginGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) {
func (g *PluginGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil {
return nil, ErrEmptyAppSetGenerator
return nil, EmptyAppSetGeneratorError
}
if appSetGenerator.Plugin == nil {
return nil, ErrEmptyAppSetGenerator
return nil, EmptyAppSetGeneratorError
}
ctx := context.Background()
@@ -66,7 +71,7 @@ func (g *PluginGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.App
pluginClient, err := g.getPluginFromGenerator(ctx, applicationSetInfo.Name, providerConfig)
if err != nil {
return nil, fmt.Errorf("error getting plugin from generator: %w", err)
return nil, err
}
list, err := pluginClient.List(ctx, providerConfig.Input.Parameters)
@@ -76,7 +81,7 @@ func (g *PluginGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.App
res, err := g.generateParams(appSetGenerator, applicationSetInfo, list.Output.Parameters, appSetGenerator.Plugin.Input.Parameters, applicationSetInfo.Spec.GoTemplate)
if err != nil {
return nil, fmt.Errorf("error generating params: %w", err)
return nil, err
}
return res, nil
@@ -89,7 +94,7 @@ func (g *PluginGenerator) getPluginFromGenerator(ctx context.Context, appSetName
}
token, err := g.getToken(ctx, cm["token"])
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err)
return nil, fmt.Errorf("error fetching Secret token: %v", err)
}
var requestTimeout int
@@ -101,18 +106,19 @@ func (g *PluginGenerator) getPluginFromGenerator(ctx context.Context, appSetName
}
}
pluginClient, err := plugin.NewPluginService(appSetName, cm["baseUrl"], token, requestTimeout)
pluginClient, err := plugin.NewPluginService(ctx, appSetName, cm["baseUrl"], token, requestTimeout)
if err != nil {
return nil, fmt.Errorf("error initializing plugin client: %w", err)
return nil, err
}
return pluginClient, nil
}
func (g *PluginGenerator) generateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, objectsFound []map[string]any, pluginParams argoprojiov1alpha1.PluginParameters, useGoTemplate bool) ([]map[string]any, error) {
res := []map[string]any{}
func (g *PluginGenerator) generateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, objectsFound []map[string]interface{}, pluginParams argoprojiov1alpha1.PluginParameters, useGoTemplate bool) ([]map[string]interface{}, error) {
res := []map[string]interface{}{}
for _, objectFound := range objectsFound {
params := map[string]any{}
params := map[string]interface{}{}
if useGoTemplate {
for k, v := range objectFound {
@@ -128,7 +134,7 @@ func (g *PluginGenerator) generateParams(appSetGenerator *argoprojiov1alpha1.App
}
}
params["generator"] = map[string]any{
params["generator"] = map[string]interface{}{
"input": map[string]argoprojiov1alpha1.PluginParameters{
"parameters": pluginParams,
},
@@ -146,6 +152,7 @@ func (g *PluginGenerator) generateParams(appSetGenerator *argoprojiov1alpha1.App
}
func (g *PluginGenerator) getToken(ctx context.Context, tokenRef string) (string, error) {
if tokenRef == "" || !strings.HasPrefix(tokenRef, "$") {
return "", fmt.Errorf("token is empty, or does not reference a secret key starting with '$': %v", tokenRef)
}
@@ -160,8 +167,9 @@ func (g *PluginGenerator) getToken(ctx context.Context, tokenRef string) (string
Namespace: g.namespace,
},
secret)
if err != nil {
return "", fmt.Errorf("error fetching secret %s/%s: %w", g.namespace, secretName, err)
return "", fmt.Errorf("error fetching secret %s/%s: %v", g.namespace, secretName, err)
}
secretValues := make(map[string]string, len(secret.Data))
@@ -184,18 +192,19 @@ func (g *PluginGenerator) getConfigMap(ctx context.Context, configMapRef string)
Namespace: g.namespace,
},
cm)
if err != nil {
return nil, err
}
baseURL, ok := cm.Data["baseUrl"]
if !ok || baseURL == "" {
return nil, errors.New("baseUrl not found in ConfigMap")
baseUrl, ok := cm.Data["baseUrl"]
if !ok || baseUrl == "" {
return nil, fmt.Errorf("baseUrl not found in ConfigMap")
}
token, ok := cm.Data["token"]
if !ok || token == "" {
return nil, errors.New("token not found in ConfigMap")
return nil, fmt.Errorf("token not found in ConfigMap")
}
return cm.Data, nil

View File

@@ -1,8 +1,8 @@
package generators
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httptest"
@@ -11,31 +11,33 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
kubefake "k8s.io/client-go/kubernetes/fake"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/argoproj/argo-cd/v3/applicationset/services/plugin"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/applicationset/services/plugin"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func TestPluginGenerateParams(t *testing.T) {
testCases := []struct {
name string
configmap *corev1.ConfigMap
secret *corev1.Secret
configmap *v1.ConfigMap
secret *v1.Secret
inputParameters map[string]apiextensionsv1.JSON
values map[string]string
gotemplate bool
expected []map[string]any
expected []map[string]interface{}
content []byte
expectedError error
}{
{
name: "simple case",
configmap: &corev1.ConfigMap{
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
@@ -45,7 +47,7 @@ func TestPluginGenerateParams(t *testing.T) {
"token": "$plugin.token",
},
},
secret: &corev1.Secret{
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
@@ -71,13 +73,13 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": 123
}]
}}`),
expected: []map[string]any{
expected: []map[string]interface{}{
{
"key1": "val1",
"key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123",
"generator": map[string]any{
"generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
@@ -91,7 +93,7 @@ func TestPluginGenerateParams(t *testing.T) {
},
{
name: "simple case with values",
configmap: &corev1.ConfigMap{
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
@@ -101,7 +103,7 @@ func TestPluginGenerateParams(t *testing.T) {
"token": "$plugin.token",
},
},
secret: &corev1.Secret{
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
@@ -131,7 +133,7 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": 123
}]
}}`),
expected: []map[string]any{
expected: []map[string]interface{}{
{
"key1": "val1",
"key2.key2_1": "val2_1",
@@ -139,7 +141,7 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": "123",
"values.valuekey1": "valuevalue1",
"values.valuekey2": "templated-val1",
"generator": map[string]any{
"generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
@@ -153,7 +155,7 @@ func TestPluginGenerateParams(t *testing.T) {
},
{
name: "simple case with gotemplate",
configmap: &corev1.ConfigMap{
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
@@ -163,7 +165,7 @@ func TestPluginGenerateParams(t *testing.T) {
"token": "$plugin.token",
},
},
secret: &corev1.Secret{
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
@@ -189,17 +191,17 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": 123
}]
}}`),
expected: []map[string]any{
expected: []map[string]interface{}{
{
"key1": "val1",
"key2": map[string]any{
"key2": map[string]interface{}{
"key2_1": "val2_1",
"key2_2": map[string]any{
"key2_2": map[string]interface{}{
"key2_2_1": "val2_2_1",
},
},
"key3": float64(123),
"generator": map[string]any{
"generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
@@ -213,7 +215,7 @@ func TestPluginGenerateParams(t *testing.T) {
},
{
name: "simple case with appended params",
configmap: &corev1.ConfigMap{
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
@@ -223,7 +225,7 @@ func TestPluginGenerateParams(t *testing.T) {
"token": "$plugin.token",
},
},
secret: &corev1.Secret{
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
@@ -248,14 +250,14 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": 123,
"pkey2": "valplugin"
}]}}`),
expected: []map[string]any{
expected: []map[string]interface{}{
{
"key1": "val1",
"key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123",
"pkey2": "valplugin",
"generator": map[string]any{
"generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
@@ -269,7 +271,7 @@ func TestPluginGenerateParams(t *testing.T) {
},
{
name: "no params",
configmap: &corev1.ConfigMap{
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
@@ -279,7 +281,7 @@ func TestPluginGenerateParams(t *testing.T) {
"token": "$plugin.token",
},
},
secret: &corev1.Secret{
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
@@ -302,14 +304,14 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": 123
}]
}}`),
expected: []map[string]any{
expected: []map[string]interface{}{
{
"key1": "val1",
"key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123",
"generator": map[string]any{
"input": map[string]map[string]any{
"generator": map[string]interface{}{
"input": map[string]map[string]interface{}{
"parameters": {},
},
},
@@ -319,7 +321,7 @@ func TestPluginGenerateParams(t *testing.T) {
},
{
name: "empty return",
configmap: &corev1.ConfigMap{
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
@@ -329,7 +331,7 @@ func TestPluginGenerateParams(t *testing.T) {
"token": "$plugin.token",
},
},
secret: &corev1.Secret{
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
@@ -341,12 +343,12 @@ func TestPluginGenerateParams(t *testing.T) {
inputParameters: map[string]apiextensionsv1.JSON{},
gotemplate: false,
content: []byte(`{"input": {"parameters": []}}`),
expected: []map[string]any{},
expected: []map[string]interface{}{},
expectedError: nil,
},
{
name: "wrong return",
configmap: &corev1.ConfigMap{
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
@@ -356,7 +358,7 @@ func TestPluginGenerateParams(t *testing.T) {
"token": "$plugin.token",
},
},
secret: &corev1.Secret{
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
@@ -368,12 +370,12 @@ func TestPluginGenerateParams(t *testing.T) {
inputParameters: map[string]apiextensionsv1.JSON{},
gotemplate: false,
content: []byte(`wrong body ...`),
expected: []map[string]any{},
expectedError: errors.New("error listing params: error get api 'set': invalid character 'w' looking for beginning of value: wrong body ..."),
expected: []map[string]interface{}{},
expectedError: fmt.Errorf("error listing params: error get api 'set': invalid character 'w' looking for beginning of value: wrong body ..."),
},
{
name: "external secret",
configmap: &corev1.ConfigMap{
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
@@ -383,7 +385,7 @@ func TestPluginGenerateParams(t *testing.T) {
"token": "$plugin-secret:plugin.token",
},
},
secret: &corev1.Secret{
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "plugin-secret",
Namespace: "default",
@@ -408,14 +410,14 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": 123,
"pkey2": "valplugin"
}]}}`),
expected: []map[string]any{
expected: []map[string]interface{}{
{
"key1": "val1",
"key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123",
"pkey2": "valplugin",
"generator": map[string]any{
"generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
@@ -429,7 +431,7 @@ func TestPluginGenerateParams(t *testing.T) {
},
{
name: "no secret",
configmap: &corev1.ConfigMap{
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
@@ -439,7 +441,7 @@ func TestPluginGenerateParams(t *testing.T) {
"token": "$plugin.token",
},
},
secret: &corev1.Secret{},
secret: &v1.Secret{},
inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
@@ -457,13 +459,13 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": 123
}]
}}`),
expected: []map[string]any{
expected: []map[string]interface{}{
{
"key1": "val1",
"key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123",
"generator": map[string]any{
"generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
@@ -473,12 +475,12 @@ func TestPluginGenerateParams(t *testing.T) {
},
},
},
expectedError: errors.New("error getting plugin from generator: error fetching Secret token: error fetching secret default/argocd-secret: secrets \"argocd-secret\" not found"),
expectedError: fmt.Errorf("error fetching Secret token: error fetching secret default/argocd-secret: secrets \"argocd-secret\" not found"),
},
{
name: "no configmap",
configmap: &corev1.ConfigMap{},
secret: &corev1.Secret{
configmap: &v1.ConfigMap{},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
@@ -504,13 +506,13 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": 123
}]
}}`),
expected: []map[string]any{
expected: []map[string]interface{}{
{
"key1": "val1",
"key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123",
"generator": map[string]any{
"generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
@@ -520,11 +522,11 @@ func TestPluginGenerateParams(t *testing.T) {
},
},
},
expectedError: errors.New("error getting plugin from generator: error fetching ConfigMap: configmaps \"\" not found"),
expectedError: fmt.Errorf("error fetching ConfigMap: configmaps \"\" not found"),
},
{
name: "no baseUrl",
configmap: &corev1.ConfigMap{
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
@@ -533,7 +535,7 @@ func TestPluginGenerateParams(t *testing.T) {
"token": "$plugin.token",
},
},
secret: &corev1.Secret{
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
@@ -559,13 +561,13 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": 123
}]
}}`),
expected: []map[string]any{
expected: []map[string]interface{}{
{
"key1": "val1",
"key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123",
"generator": map[string]any{
"generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
@@ -575,11 +577,11 @@ func TestPluginGenerateParams(t *testing.T) {
},
},
},
expectedError: errors.New("error getting plugin from generator: error fetching ConfigMap: baseUrl not found in ConfigMap"),
expectedError: fmt.Errorf("error fetching ConfigMap: baseUrl not found in ConfigMap"),
},
{
name: "no token",
configmap: &corev1.ConfigMap{
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
@@ -588,7 +590,7 @@ func TestPluginGenerateParams(t *testing.T) {
"baseUrl": "http://127.0.0.1",
},
},
secret: &corev1.Secret{},
secret: &v1.Secret{},
inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
@@ -606,13 +608,13 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": 123
}]
}}`),
expected: []map[string]any{
expected: []map[string]interface{}{
{
"key1": "val1",
"key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123",
"generator": map[string]any{
"generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
@@ -622,12 +624,16 @@ func TestPluginGenerateParams(t *testing.T) {
},
},
},
expectedError: errors.New("error getting plugin from generator: error fetching ConfigMap: token not found in ConfigMap"),
expectedError: fmt.Errorf("error fetching ConfigMap: token not found in ConfigMap"),
},
}
ctx := context.Background()
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
generatorConfig := argoprojiov1alpha1.ApplicationSetGenerator{
Plugin: &argoprojiov1alpha1.PluginGenerator{
ConfigMapRef: argoprojiov1alpha1.PluginConfigMapRef{Name: testCase.configmap.Name},
@@ -639,9 +645,10 @@ func TestPluginGenerateParams(t *testing.T) {
}
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
_, tokenKey := plugin.ParseSecretKey(testCase.configmap.Data["token"])
expectedToken := testCase.secret.Data[strings.ReplaceAll(tokenKey, "$", "")]
expectedToken := testCase.secret.Data[strings.Replace(tokenKey, "$", "", -1)]
if authHeader != "Bearer "+string(expectedToken) {
w.WriteHeader(http.StatusUnauthorized)
return
@@ -650,7 +657,7 @@ func TestPluginGenerateParams(t *testing.T) {
w.Header().Set("Content-Type", "application/json")
_, err := w.Write(testCase.content)
if err != nil {
require.NoError(t, fmt.Errorf("Error Write %w", err))
assert.NoError(t, fmt.Errorf("Error Write %v", err))
}
})
@@ -662,9 +669,11 @@ func TestPluginGenerateParams(t *testing.T) {
testCase.configmap.Data["baseUrl"] = fakeServer.URL
}
fakeClient := kubefake.NewSimpleClientset(append([]runtime.Object{}, testCase.configmap, testCase.secret)...)
fakeClientWithCache := fake.NewClientBuilder().WithObjects([]client.Object{testCase.configmap, testCase.secret}...).Build()
pluginGenerator := NewPluginGenerator(fakeClientWithCache, "default")
var pluginGenerator = NewPluginGenerator(fakeClientWithCache, ctx, fakeClient, "default")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -675,20 +684,21 @@ func TestPluginGenerateParams(t *testing.T) {
},
}
got, err := pluginGenerator.GenerateParams(&generatorConfig, &applicationSetInfo, nil)
got, err := pluginGenerator.GenerateParams(&generatorConfig, &applicationSetInfo)
if err != nil {
fmt.Println(err)
}
if testCase.expectedError != nil {
require.EqualError(t, err, testCase.expectedError.Error())
assert.EqualError(t, err, testCase.expectedError.Error())
} else {
assert.NoError(t, err)
expectedJson, err := json.Marshal(testCase.expected)
require.NoError(t, err)
expectedJSON, err := json.Marshal(testCase.expected)
gotJson, err := json.Marshal(got)
require.NoError(t, err)
gotJSON, err := json.Marshal(got)
require.NoError(t, err)
assert.JSONEq(t, string(expectedJSON), string(gotJSON))
assert.Equal(t, string(expectedJson), string(gotJson))
}
})
}

View File

@@ -2,37 +2,39 @@ package generators
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"time"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/gosimple/slug"
log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v3/applicationset/services"
pullrequest "github.com/argoproj/argo-cd/v3/applicationset/services/pull_request"
"github.com/argoproj/argo-cd/v3/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
pullrequest "github.com/argoproj/argo-cd/v2/applicationset/services/pull_request"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
var _ Generator = (*PullRequestGenerator)(nil)
const (
DefaultPullRequestRequeueAfter = 30 * time.Minute
DefaultPullRequestRequeueAfterSeconds = 30 * time.Minute
)
type PullRequestGenerator struct {
client client.Client
selectServiceProviderFunc func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error)
SCMConfig
auth SCMAuthProviders
scmRootCAPath string
allowedSCMProviders []string
}
func NewPullRequestGenerator(client client.Client, scmConfig SCMConfig) Generator {
func NewPullRequestGenerator(client client.Client, auth SCMAuthProviders, scmRootCAPath string, allowedScmProviders []string) Generator {
g := &PullRequestGenerator{
client: client,
SCMConfig: scmConfig,
client: client,
auth: auth,
scmRootCAPath: scmRootCAPath,
allowedSCMProviders: allowedScmProviders,
}
g.selectServiceProviderFunc = g.selectServiceProvider
return g
@@ -45,42 +47,33 @@ func (g *PullRequestGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alph
return time.Duration(*appSetGenerator.PullRequest.RequeueAfterSeconds) * time.Second
}
return DefaultPullRequestRequeueAfter
}
func (g *PullRequestGenerator) GetContinueOnRepoNotFoundError(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) bool {
return appSetGenerator.PullRequest.ContinueOnRepoNotFoundError
return DefaultPullRequestRequeueAfterSeconds
}
func (g *PullRequestGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
return &appSetGenerator.PullRequest.Template
}
func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) {
func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil {
return nil, ErrEmptyAppSetGenerator
return nil, EmptyAppSetGeneratorError
}
if appSetGenerator.PullRequest == nil {
return nil, ErrEmptyAppSetGenerator
return nil, EmptyAppSetGeneratorError
}
ctx := context.Background()
svc, err := g.selectServiceProviderFunc(ctx, appSetGenerator.PullRequest, applicationSetInfo)
if err != nil {
return nil, fmt.Errorf("failed to select pull request service provider: %w", err)
return nil, fmt.Errorf("failed to select pull request service provider: %v", err)
}
pulls, err := pullrequest.ListPullRequests(ctx, svc, appSetGenerator.PullRequest.Filters)
params := make([]map[string]any, 0, len(pulls))
if err != nil {
if pullrequest.IsRepositoryNotFoundError(err) && g.GetContinueOnRepoNotFoundError(appSetGenerator) {
log.WithError(err).WithField("generator", g).
Warn("Skipping params generation for this repository since it was not found.")
return params, nil
}
return nil, fmt.Errorf("error listing repos: %w", err)
return nil, fmt.Errorf("error listing repos: %v", err)
}
params := make([]map[string]interface{}, 0, len(pulls))
// In order to follow the DNS label standard as defined in RFC 1123,
// we need to limit the 'branch' to 50 to give room to append/suffix-ing it
@@ -106,9 +99,8 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
shortSHALength7 = len(pull.HeadSHA)
}
paramMap := map[string]any{
paramMap := map[string]interface{}{
"number": strconv.Itoa(pull.Number),
"title": pull.Title,
"branch": pull.Branch,
"branch_slug": slug.Make(pull.Branch),
"target_branch": pull.TargetBranch,
@@ -116,18 +108,12 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
"head_sha": pull.HeadSHA,
"head_short_sha": pull.HeadSHA[:shortSHALength],
"head_short_sha_7": pull.HeadSHA[:shortSHALength7],
"author": pull.Author,
}
// PR lables will only be supported for Go Template appsets, since fasttemplate will be deprecated.
if applicationSetInfo != nil && applicationSetInfo.Spec.GoTemplate {
paramMap["labels"] = pull.Labels
}
err := appendTemplatedValues(appSetGenerator.PullRequest.Values, paramMap, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions)
if err != nil {
return nil, fmt.Errorf("failed to append templated values: %w", err)
}
params = append(params, paramMap)
}
return params, nil
@@ -135,127 +121,116 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
// selectServiceProvider selects the provider to get pull requests from the configuration
func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, generatorConfig *argoprojiov1alpha1.PullRequestGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
if !g.enableSCMProviders {
return nil, ErrSCMProvidersDisabled
}
if err := ScmProviderAllowed(applicationSetInfo, generatorConfig, g.allowedSCMProviders); err != nil {
return nil, fmt.Errorf("scm provider not allowed: %w", err)
}
if generatorConfig.Github != nil {
if !ScmProviderAllowed(applicationSetInfo, generatorConfig.Github.API, g.allowedSCMProviders) {
return nil, fmt.Errorf("scm provider not allowed: %s", generatorConfig.Github.API)
}
return g.github(ctx, generatorConfig.Github, applicationSetInfo)
}
if generatorConfig.GitLab != nil {
providerConfig := generatorConfig.GitLab
var caCerts []byte
var prErr error
if providerConfig.CARef != nil {
caCerts, prErr = utils.GetConfigMapData(ctx, g.client, providerConfig.CARef, applicationSetInfo.Namespace)
if prErr != nil {
return nil, fmt.Errorf("error fetching CA certificates from ConfigMap: %w", prErr)
}
if !ScmProviderAllowed(applicationSetInfo, providerConfig.API, g.allowedSCMProviders) {
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.API)
}
token, err := utils.GetSecretRef(ctx, g.client, providerConfig.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
token, err := g.getSecretRef(ctx, providerConfig.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err)
return nil, fmt.Errorf("error fetching Secret token: %v", err)
}
return pullrequest.NewGitLabService(token, providerConfig.API, providerConfig.Project, providerConfig.Labels, providerConfig.PullRequestState, g.scmRootCAPath, providerConfig.Insecure, caCerts)
return pullrequest.NewGitLabService(ctx, token, providerConfig.API, providerConfig.Project, providerConfig.Labels, providerConfig.PullRequestState, g.scmRootCAPath, providerConfig.Insecure)
}
if generatorConfig.Gitea != nil {
providerConfig := generatorConfig.Gitea
token, err := utils.GetSecretRef(ctx, g.client, providerConfig.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err)
if !ScmProviderAllowed(applicationSetInfo, providerConfig.API, g.allowedSCMProviders) {
return nil, fmt.Errorf("scm provider not allowed: %s", generatorConfig.Gitea.API)
}
return pullrequest.NewGiteaService(token, providerConfig.API, providerConfig.Owner, providerConfig.Repo, providerConfig.Labels, providerConfig.Insecure)
token, err := g.getSecretRef(ctx, providerConfig.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %v", err)
}
return pullrequest.NewGiteaService(ctx, token, providerConfig.API, providerConfig.Owner, providerConfig.Repo, providerConfig.Insecure)
}
if generatorConfig.BitbucketServer != nil {
providerConfig := generatorConfig.BitbucketServer
var caCerts []byte
var prErr error
if providerConfig.CARef != nil {
caCerts, prErr = utils.GetConfigMapData(ctx, g.client, providerConfig.CARef, applicationSetInfo.Namespace)
if prErr != nil {
return nil, fmt.Errorf("error fetching CA certificates from ConfigMap: %w", prErr)
}
if !ScmProviderAllowed(applicationSetInfo, providerConfig.API, g.allowedSCMProviders) {
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.API)
}
if providerConfig.BearerToken != nil {
appToken, err := utils.GetSecretRef(ctx, g.client, providerConfig.BearerToken.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
if providerConfig.BasicAuth != nil {
password, err := g.getSecretRef(ctx, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Secret Bearer token: %w", err)
return nil, fmt.Errorf("error fetching Secret token: %v", err)
}
return pullrequest.NewBitbucketServiceBearerToken(ctx, appToken, providerConfig.API, providerConfig.Project, providerConfig.Repo, g.scmRootCAPath, providerConfig.Insecure, caCerts)
} else if providerConfig.BasicAuth != nil {
password, err := utils.GetSecretRef(ctx, g.client, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err)
}
return pullrequest.NewBitbucketServiceBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.Repo, g.scmRootCAPath, providerConfig.Insecure, caCerts)
return pullrequest.NewBitbucketServiceBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.Repo)
} else {
return pullrequest.NewBitbucketServiceNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.Repo)
}
return pullrequest.NewBitbucketServiceNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.Repo, g.scmRootCAPath, providerConfig.Insecure, caCerts)
}
if generatorConfig.Bitbucket != nil {
providerConfig := generatorConfig.Bitbucket
if providerConfig.BearerToken != nil {
appToken, err := utils.GetSecretRef(ctx, g.client, providerConfig.BearerToken.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
appToken, err := g.getSecretRef(ctx, providerConfig.BearerToken.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Secret Bearer token: %w", err)
return nil, fmt.Errorf("error fetching Secret Bearer token: %v", err)
}
return pullrequest.NewBitbucketCloudServiceBearerToken(providerConfig.API, appToken, providerConfig.Owner, providerConfig.Repo)
} else if providerConfig.BasicAuth != nil {
password, err := utils.GetSecretRef(ctx, g.client, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
password, err := g.getSecretRef(ctx, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err)
return nil, fmt.Errorf("error fetching Secret token: %v", err)
}
return pullrequest.NewBitbucketCloudServiceBasicAuth(providerConfig.API, providerConfig.BasicAuth.Username, password, providerConfig.Owner, providerConfig.Repo)
} else {
return pullrequest.NewBitbucketCloudServiceNoAuth(providerConfig.API, providerConfig.Owner, providerConfig.Repo)
}
return pullrequest.NewBitbucketCloudServiceNoAuth(providerConfig.API, providerConfig.Owner, providerConfig.Repo)
}
if generatorConfig.AzureDevOps != nil {
providerConfig := generatorConfig.AzureDevOps
token, err := utils.GetSecretRef(ctx, g.client, providerConfig.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
token, err := g.getSecretRef(ctx, providerConfig.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err)
return nil, fmt.Errorf("error fetching Secret token: %v", err)
}
return pullrequest.NewAzureDevOpsService(token, providerConfig.API, providerConfig.Organization, providerConfig.Project, providerConfig.Repo, providerConfig.Labels)
return pullrequest.NewAzureDevOpsService(ctx, token, providerConfig.API, providerConfig.Organization, providerConfig.Project, providerConfig.Repo, providerConfig.Labels)
}
return nil, errors.New("no Pull Request provider implementation configured")
return nil, fmt.Errorf("no Pull Request provider implementation configured")
}
func (g *PullRequestGenerator) github(ctx context.Context, cfg *argoprojiov1alpha1.PullRequestGeneratorGithub, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
var metricsCtx *services.MetricsContext
var httpClient *http.Client
if g.enableGitHubAPIMetrics {
metricsCtx = &services.MetricsContext{
AppSetNamespace: applicationSetInfo.Namespace,
AppSetName: applicationSetInfo.Name,
}
httpClient = services.NewGitHubMetricsClient(metricsCtx)
}
// use an app if it was configured
if cfg.AppSecretName != "" {
auth, err := g.GitHubApps.GetAuthSecret(ctx, cfg.AppSecretName)
auth, err := g.auth.GitHubApps.GetAuthSecret(ctx, cfg.AppSecretName)
if err != nil {
return nil, fmt.Errorf("error getting GitHub App secret: %w", err)
}
if g.enableGitHubAPIMetrics {
return pullrequest.NewGithubAppService(*auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels, httpClient)
return nil, fmt.Errorf("error getting GitHub App secret: %v", err)
}
return pullrequest.NewGithubAppService(*auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
}
// always default to token, even if not set (public access)
token, err := utils.GetSecretRef(ctx, g.client, cfg.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
token, err := g.getSecretRef(ctx, cfg.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err)
return nil, fmt.Errorf("error fetching Secret token: %v", err)
}
return pullrequest.NewGithubService(ctx, token, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
}
// getSecretRef gets the value of the key for the specified Secret resource.
func (g *PullRequestGenerator) getSecretRef(ctx context.Context, ref *argoprojiov1alpha1.SecretRef, namespace string) (string, error) {
if ref == nil {
return "", nil
}
if g.enableGitHubAPIMetrics {
return pullrequest.NewGithubService(token, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels, httpClient)
secret := &corev1.Secret{}
err := g.client.Get(
ctx,
client.ObjectKey{
Name: ref.SecretName,
Namespace: namespace,
},
secret)
if err != nil {
return "", fmt.Errorf("error fetching secret %s/%s: %v", namespace, ref.SecretName, err)
}
return pullrequest.NewGithubService(token, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
tokenBytes, ok := secret.Data[ref.Key]
if !ok {
return "", fmt.Errorf("key %q in secret %s/%s not found", ref.Key, namespace, ref.SecretName)
}
return string(tokenBytes), nil
}

View File

@@ -2,48 +2,44 @@ package generators
import (
"context"
"errors"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
pullrequest "github.com/argoproj/argo-cd/v3/applicationset/services/pull_request"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
pullrequest "github.com/argoproj/argo-cd/v2/applicationset/services/pull_request"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func TestPullRequestGithubGenerateParams(t *testing.T) {
ctx := t.Context()
ctx := context.Background()
cases := []struct {
selectFunc func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error)
values map[string]string
expected []map[string]any
expectedErr error
applicationSet argoprojiov1alpha1.ApplicationSet
continueOnRepoNotFoundError bool
selectFunc func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error)
expected []map[string]interface{}
expectedErr error
applicationSet argoprojiov1alpha1.ApplicationSet
}{
{
selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
return pullrequest.NewFakeService(
ctx,
[]*pullrequest.PullRequest{
{
&pullrequest.PullRequest{
Number: 1,
Title: "title1",
Branch: "branch1",
TargetBranch: "master",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "testName",
},
},
nil,
)
},
expected: []map[string]any{
expected: []map[string]interface{}{
{
"number": "1",
"title": "title1",
"branch": "branch1",
"branch_slug": "branch1",
"target_branch": "master",
@@ -51,7 +47,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb",
"head_short_sha_7": "089d92c",
"author": "testName",
},
},
expectedErr: nil,
@@ -61,22 +56,19 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
return pullrequest.NewFakeService(
ctx,
[]*pullrequest.PullRequest{
{
&pullrequest.PullRequest{
Number: 2,
Title: "title2",
Branch: "feat/areally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
TargetBranch: "feat/anotherreally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
HeadSHA: "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
Author: "testName",
},
},
nil,
)
},
expected: []map[string]any{
expected: []map[string]interface{}{
{
"number": "2",
"title": "title2",
"branch": "feat/areally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
"branch_slug": "feat-areally-long-pull-request-name-to-test-argo",
"target_branch": "feat/anotherreally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
@@ -84,7 +76,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
"head_sha": "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
"head_short_sha": "9b34ff5b",
"head_short_sha_7": "9b34ff5",
"author": "testName",
},
},
expectedErr: nil,
@@ -94,22 +85,19 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
return pullrequest.NewFakeService(
ctx,
[]*pullrequest.PullRequest{
{
&pullrequest.PullRequest{
Number: 1,
Title: "title1",
Branch: "a-very-short-sha",
TargetBranch: "master",
HeadSHA: "abcd",
Author: "testName",
},
},
nil,
)
},
expected: []map[string]any{
expected: []map[string]interface{}{
{
"number": "1",
"title": "title1",
"branch": "a-very-short-sha",
"branch_slug": "a-very-short-sha",
"target_branch": "master",
@@ -117,46 +105,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
"head_sha": "abcd",
"head_short_sha": "abcd",
"head_short_sha_7": "abcd",
"author": "testName",
},
},
expectedErr: nil,
},
{
selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
return pullrequest.NewFakeService(
ctx,
[]*pullrequest.PullRequest{
{
Number: 1,
Title: "title1",
Branch: "my_branch",
TargetBranch: "master",
HeadSHA: "abcd",
Author: "testName",
},
},
nil,
)
},
values: map[string]string{
"foo": "bar",
"pr_branch": "{{ branch }}",
},
expected: []map[string]any{
{
"number": "1",
"title": "title1",
"branch": "my_branch",
"branch_slug": "my-branch",
"target_branch": "master",
"target_branch_slug": "master",
"head_sha": "abcd",
"head_short_sha": "abcd",
"head_short_sha_7": "abcd",
"author": "testName",
"values.foo": "bar",
"values.pr_branch": "my_branch",
},
},
expectedErr: nil,
@@ -166,58 +114,31 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
return pullrequest.NewFakeService(
ctx,
nil,
errors.New("fake error"),
fmt.Errorf("fake error"),
)
},
expected: nil,
expectedErr: errors.New("error listing repos: fake error"),
},
{
selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
return pullrequest.NewFakeService(
ctx,
nil,
pullrequest.NewRepositoryNotFoundError(errors.New("repository not found")),
)
},
expected: []map[string]any{},
expectedErr: nil,
continueOnRepoNotFoundError: true,
},
{
selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
return pullrequest.NewFakeService(
ctx,
nil,
pullrequest.NewRepositoryNotFoundError(errors.New("repository not found")),
)
},
expected: nil,
expectedErr: errors.New("error listing repos: repository not found"),
continueOnRepoNotFoundError: false,
expectedErr: fmt.Errorf("error listing repos: fake error"),
},
{
selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
return pullrequest.NewFakeService(
ctx,
[]*pullrequest.PullRequest{
{
&pullrequest.PullRequest{
Number: 1,
Title: "title1",
Branch: "branch1",
TargetBranch: "master",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
Labels: []string{"preview"},
Author: "testName",
},
},
nil,
)
},
expected: []map[string]any{
expected: []map[string]interface{}{
{
"number": "1",
"title": "title1",
"branch": "branch1",
"branch_slug": "branch1",
"target_branch": "master",
@@ -226,7 +147,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
"head_short_sha": "089d92cb",
"head_short_sha_7": "089d92c",
"labels": []string{"preview"},
"author": "testName",
},
},
expectedErr: nil,
@@ -242,23 +162,20 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
return pullrequest.NewFakeService(
ctx,
[]*pullrequest.PullRequest{
{
&pullrequest.PullRequest{
Number: 1,
Title: "title1",
Branch: "branch1",
TargetBranch: "master",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
Labels: []string{"preview"},
Author: "testName",
},
},
nil,
)
},
expected: []map[string]any{
expected: []map[string]interface{}{
{
"number": "1",
"title": "title1",
"branch": "branch1",
"branch_slug": "branch1",
"target_branch": "master",
@@ -266,7 +183,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb",
"head_short_sha_7": "089d92c",
"author": "testName",
},
},
expectedErr: nil,
@@ -277,51 +193,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
},
},
},
{
selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
return pullrequest.NewFakeService(
ctx,
[]*pullrequest.PullRequest{
{
Number: 1,
Title: "title1",
Branch: "my_branch",
TargetBranch: "master",
HeadSHA: "abcd",
Author: "testName",
Labels: []string{"preview", "preview:team1"},
},
},
nil,
)
},
values: map[string]string{
"preview_env": "{{ regexFind \"(team1|team2)\" (.labels | join \",\") }}",
},
expected: []map[string]any{
{
"number": "1",
"title": "title1",
"branch": "my_branch",
"branch_slug": "my-branch",
"target_branch": "master",
"target_branch_slug": "master",
"head_sha": "abcd",
"head_short_sha": "abcd",
"head_short_sha_7": "abcd",
"author": "testName",
"labels": []string{"preview", "preview:team1"},
"values": map[string]string{"preview_env": "team1"},
},
},
expectedErr: nil,
applicationSet: argoprojiov1alpha1.ApplicationSet{
Spec: argoprojiov1alpha1.ApplicationSetSpec{
// Application set is using fasttemplate.
GoTemplate: true,
},
},
},
}
for _, c := range cases {
@@ -329,28 +200,85 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
selectServiceProviderFunc: c.selectFunc,
}
generatorConfig := argoprojiov1alpha1.ApplicationSetGenerator{
PullRequest: &argoprojiov1alpha1.PullRequestGenerator{
Values: c.values,
ContinueOnRepoNotFoundError: c.continueOnRepoNotFoundError,
},
PullRequest: &argoprojiov1alpha1.PullRequestGenerator{},
}
got, gotErr := gen.GenerateParams(&generatorConfig, &c.applicationSet, nil)
if c.expectedErr != nil {
require.EqualError(t, gotErr, c.expectedErr.Error())
} else {
require.NoError(t, gotErr)
}
got, gotErr := gen.GenerateParams(&generatorConfig, &c.applicationSet)
assert.Equal(t, c.expectedErr, gotErr)
assert.ElementsMatch(t, c.expected, got)
}
}
func TestAllowedSCMProviderPullRequest(t *testing.T) {
t.Parallel()
func TestPullRequestGetSecretRef(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test-secret", Namespace: "test"},
Data: map[string][]byte{
"my-token": []byte("secret"),
},
}
gen := &PullRequestGenerator{client: fake.NewClientBuilder().WithObjects(secret).Build()}
ctx := context.Background()
cases := []struct {
name, namespace, token string
ref *argoprojiov1alpha1.SecretRef
hasError bool
}{
{
name: "valid ref",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "my-token"},
namespace: "test",
token: "secret",
hasError: false,
},
{
name: "nil ref",
ref: nil,
namespace: "test",
token: "",
hasError: false,
},
{
name: "wrong name",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "other", Key: "my-token"},
namespace: "test",
token: "",
hasError: true,
},
{
name: "wrong key",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "other-token"},
namespace: "test",
token: "",
hasError: true,
},
{
name: "wrong namespace",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "my-token"},
namespace: "other",
token: "",
hasError: true,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
token, err := gen.getSecretRef(ctx, c.ref, c.namespace)
if c.hasError {
assert.NotNil(t, err)
} else {
assert.Nil(t, err)
}
assert.Equal(t, c.token, token)
})
}
}
func TestAllowedSCMProviderPullRequest(t *testing.T) {
cases := []struct {
name string
providerConfig *argoprojiov1alpha1.PullRequestGenerator
expectedError string
}{
{
name: "Error Github",
@@ -359,6 +287,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: "failed to select pull request service provider: scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
},
{
name: "Error Gitlab",
@@ -367,6 +296,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: "failed to select pull request service provider: scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
},
{
name: "Error Gitea",
@@ -375,6 +305,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: "failed to select pull request service provider: scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
},
{
name: "Error Bitbucket",
@@ -383,6 +314,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: "failed to select pull request service provider: scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
},
}
@@ -392,13 +324,13 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel()
pullRequestGenerator := NewPullRequestGenerator(nil, NewSCMConfig("", []string{
pullRequestGenerator := NewPullRequestGenerator(nil, SCMAuthProviders{}, "", []string{
"github.myorg.com",
"gitlab.myorg.com",
"gitea.myorg.com",
"bitbucket.myorg.com",
"azuredevops.myorg.com",
}, true, true, nil, true))
})
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -411,33 +343,10 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
},
}
_, err := pullRequestGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
_, err := pullRequestGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
require.Error(t, err, "Must return an error")
var expectedError ErrDisallowedSCMProvider
assert.ErrorAs(t, err, &expectedError)
assert.Error(t, err, "Must return an error")
assert.Equal(t, testCaseCopy.expectedError, err.Error())
})
}
}
func TestSCMProviderDisabled_PRGenerator(t *testing.T) {
generator := NewPullRequestGenerator(nil, NewSCMConfig("", []string{}, false, true, nil, true))
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
PullRequest: &argoprojiov1alpha1.PullRequestGenerator{
Github: &argoprojiov1alpha1.PullRequestGeneratorGithub{
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
}},
},
}
_, err := generator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
assert.ErrorIs(t, err, ErrSCMProvidersDisabled)
}

View File

@@ -2,68 +2,53 @@ package generators
import (
"context"
"errors"
"fmt"
"net/http"
"strings"
"time"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v3/applicationset/services"
"github.com/argoproj/argo-cd/v3/applicationset/services/github_app_auth"
"github.com/argoproj/argo-cd/v3/applicationset/services/scm_provider"
"github.com/argoproj/argo-cd/v3/applicationset/utils"
"github.com/argoproj/argo-cd/v3/common"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/applicationset/services/github_app_auth"
"github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
"github.com/argoproj/argo-cd/v2/common"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
var _ Generator = (*SCMProviderGenerator)(nil)
const (
DefaultSCMProviderRequeueAfter = 30 * time.Minute
DefaultSCMProviderRequeueAfterSeconds = 30 * time.Minute
)
type SCMProviderGenerator struct {
client client.Client
// Testing hooks.
overrideProvider scm_provider.SCMProviderService
SCMConfig
}
type SCMConfig struct {
scmRootCAPath string
allowedSCMProviders []string
enableSCMProviders bool
enableGitHubAPIMetrics bool
GitHubApps github_app_auth.Credentials
tokenRefStrictMode bool
SCMAuthProviders
scmRootCAPath string
allowedSCMProviders []string
}
func NewSCMConfig(scmRootCAPath string, allowedSCMProviders []string, enableSCMProviders bool, enableGitHubAPIMetrics bool, gitHubApps github_app_auth.Credentials, tokenRefStrictMode bool) SCMConfig {
return SCMConfig{
scmRootCAPath: scmRootCAPath,
allowedSCMProviders: allowedSCMProviders,
enableSCMProviders: enableSCMProviders,
enableGitHubAPIMetrics: enableGitHubAPIMetrics,
GitHubApps: gitHubApps,
tokenRefStrictMode: tokenRefStrictMode,
}
type SCMAuthProviders struct {
GitHubApps github_app_auth.Credentials
}
func NewSCMProviderGenerator(client client.Client, scmConfig SCMConfig) Generator {
func NewSCMProviderGenerator(client client.Client, providers SCMAuthProviders, scmRootCAPath string, allowedSCMProviders []string) Generator {
return &SCMProviderGenerator{
client: client,
SCMConfig: scmConfig,
client: client,
SCMAuthProviders: providers,
scmRootCAPath: scmRootCAPath,
allowedSCMProviders: allowedSCMProviders,
}
}
// Testing generator
func NewTestSCMProviderGenerator(overrideProvider scm_provider.SCMProviderService) Generator {
return &SCMProviderGenerator{overrideProvider: overrideProvider, SCMConfig: SCMConfig{
enableSCMProviders: true,
}}
return &SCMProviderGenerator{overrideProvider: overrideProvider}
}
func (g *SCMProviderGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
@@ -73,41 +58,21 @@ func (g *SCMProviderGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alph
return time.Duration(*appSetGenerator.SCMProvider.RequeueAfterSeconds) * time.Second
}
return DefaultSCMProviderRequeueAfter
return DefaultSCMProviderRequeueAfterSeconds
}
func (g *SCMProviderGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
return &appSetGenerator.SCMProvider.Template
}
var ErrSCMProvidersDisabled = errors.New("scm providers are disabled")
type ErrDisallowedSCMProvider struct {
Provider string
Allowed []string
}
func NewErrDisallowedSCMProvider(provider string, allowed []string) ErrDisallowedSCMProvider {
return ErrDisallowedSCMProvider{
Provider: provider,
Allowed: allowed,
}
}
func (e ErrDisallowedSCMProvider) Error() string {
return fmt.Sprintf("scm provider %q not allowed, must use one of the following: %s", e.Provider, strings.Join(e.Allowed, ", "))
}
func ScmProviderAllowed(applicationSetInfo *argoprojiov1alpha1.ApplicationSet, generator SCMGeneratorWithCustomApiUrl, allowedScmProviders []string) error {
url := generator.CustomApiUrl()
func ScmProviderAllowed(applicationSetInfo *argoprojiov1alpha1.ApplicationSet, url string, allowedScmProviders []string) bool {
if url == "" || len(allowedScmProviders) == 0 {
return nil
return true
}
for _, allowedScmProvider := range allowedScmProviders {
if url == allowedScmProvider {
return nil
return true
}
}
@@ -115,132 +80,115 @@ func ScmProviderAllowed(applicationSetInfo *argoprojiov1alpha1.ApplicationSet, g
common.SecurityField: common.SecurityMedium,
"applicationset": applicationSetInfo.Name,
"appSetNamespace": applicationSetInfo.Namespace,
}).Debugf("attempted to use disallowed SCM %q, must use one of the following: %s", url, strings.Join(allowedScmProviders, ", "))
}).Debugf("attempted to use disallowed SCM %q", url)
return NewErrDisallowedSCMProvider(url, allowedScmProviders)
return false
}
func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) {
func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil {
return nil, ErrEmptyAppSetGenerator
return nil, EmptyAppSetGeneratorError
}
if appSetGenerator.SCMProvider == nil {
return nil, ErrEmptyAppSetGenerator
}
if !g.enableSCMProviders {
return nil, ErrSCMProvidersDisabled
}
// Create the SCM provider helper.
providerConfig := appSetGenerator.SCMProvider
if err := ScmProviderAllowed(applicationSetInfo, providerConfig, g.allowedSCMProviders); err != nil {
return nil, fmt.Errorf("scm provider not allowed: %w", err)
return nil, EmptyAppSetGeneratorError
}
ctx := context.Background()
// Create the SCM provider helper.
providerConfig := appSetGenerator.SCMProvider
var provider scm_provider.SCMProviderService
switch {
case g.overrideProvider != nil:
if g.overrideProvider != nil {
provider = g.overrideProvider
case providerConfig.Github != nil:
} else if providerConfig.Github != nil {
if !ScmProviderAllowed(applicationSetInfo, providerConfig.Github.API, g.allowedSCMProviders) {
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.Github.API)
}
var err error
provider, err = g.githubProvider(ctx, providerConfig.Github, applicationSetInfo)
if err != nil {
return nil, fmt.Errorf("scm provider: %w", err)
}
case providerConfig.Gitlab != nil:
providerConfig := providerConfig.Gitlab
var caCerts []byte
var scmError error
if providerConfig.CARef != nil {
caCerts, scmError = utils.GetConfigMapData(ctx, g.client, providerConfig.CARef, applicationSetInfo.Namespace)
if scmError != nil {
return nil, fmt.Errorf("error fetching CA certificates from ConfigMap: %w", scmError)
}
} else if providerConfig.Gitlab != nil {
if !ScmProviderAllowed(applicationSetInfo, providerConfig.Gitlab.API, g.allowedSCMProviders) {
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.Gitlab.API)
}
token, err := utils.GetSecretRef(ctx, g.client, providerConfig.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
token, err := g.getSecretRef(ctx, providerConfig.Gitlab.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Gitlab token: %w", err)
return nil, fmt.Errorf("error fetching Gitlab token: %v", err)
}
provider, err = scm_provider.NewGitlabProvider(providerConfig.Group, token, providerConfig.API, providerConfig.AllBranches, providerConfig.IncludeSubgroups, providerConfig.WillIncludeSharedProjects(), providerConfig.Insecure, g.scmRootCAPath, providerConfig.Topic, caCerts)
provider, err = scm_provider.NewGitlabProvider(ctx, providerConfig.Gitlab.Group, token, providerConfig.Gitlab.API, providerConfig.Gitlab.AllBranches, providerConfig.Gitlab.IncludeSubgroups, providerConfig.Gitlab.Insecure, g.scmRootCAPath)
if err != nil {
return nil, fmt.Errorf("error initializing Gitlab service: %w", err)
return nil, fmt.Errorf("error initializing Gitlab service: %v", err)
}
case providerConfig.Gitea != nil:
token, err := utils.GetSecretRef(ctx, g.client, providerConfig.Gitea.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
} else if providerConfig.Gitea != nil {
if !ScmProviderAllowed(applicationSetInfo, providerConfig.Gitea.API, g.allowedSCMProviders) {
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.Gitea.API)
}
token, err := g.getSecretRef(ctx, providerConfig.Gitea.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Gitea token: %w", err)
return nil, fmt.Errorf("error fetching Gitea token: %v", err)
}
provider, err = scm_provider.NewGiteaProvider(providerConfig.Gitea.Owner, token, providerConfig.Gitea.API, providerConfig.Gitea.AllBranches, providerConfig.Gitea.Insecure)
provider, err = scm_provider.NewGiteaProvider(ctx, providerConfig.Gitea.Owner, token, providerConfig.Gitea.API, providerConfig.Gitea.AllBranches, providerConfig.Gitea.Insecure)
if err != nil {
return nil, fmt.Errorf("error initializing Gitea service: %w", err)
return nil, fmt.Errorf("error initializing Gitea service: %v", err)
}
case providerConfig.BitbucketServer != nil:
} else if providerConfig.BitbucketServer != nil {
providerConfig := providerConfig.BitbucketServer
var caCerts []byte
var scmError error
if providerConfig.CARef != nil {
caCerts, scmError = utils.GetConfigMapData(ctx, g.client, providerConfig.CARef, applicationSetInfo.Namespace)
if scmError != nil {
return nil, fmt.Errorf("error fetching CA certificates from ConfigMap: %w", scmError)
}
if !ScmProviderAllowed(applicationSetInfo, providerConfig.API, g.allowedSCMProviders) {
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.API)
}
switch {
case providerConfig.BearerToken != nil:
appToken, err := utils.GetSecretRef(ctx, g.client, providerConfig.BearerToken.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
var scmError error
if providerConfig.BasicAuth != nil {
password, err := g.getSecretRef(ctx, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Secret Bearer token: %w", err)
return nil, fmt.Errorf("error fetching Secret token: %v", err)
}
provider, scmError = scm_provider.NewBitbucketServerProviderBearerToken(ctx, appToken, providerConfig.API, providerConfig.Project, providerConfig.AllBranches, g.scmRootCAPath, providerConfig.Insecure, caCerts)
case providerConfig.BasicAuth != nil:
password, err := utils.GetSecretRef(ctx, g.client, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err)
}
provider, scmError = scm_provider.NewBitbucketServerProviderBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.AllBranches, g.scmRootCAPath, providerConfig.Insecure, caCerts)
default:
provider, scmError = scm_provider.NewBitbucketServerProviderNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.AllBranches, g.scmRootCAPath, providerConfig.Insecure, caCerts)
provider, scmError = scm_provider.NewBitbucketServerProviderBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.AllBranches)
} else {
provider, scmError = scm_provider.NewBitbucketServerProviderNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.AllBranches)
}
if scmError != nil {
return nil, fmt.Errorf("error initializing Bitbucket Server service: %w", scmError)
return nil, fmt.Errorf("error initializing Bitbucket Server service: %v", scmError)
}
case providerConfig.AzureDevOps != nil:
token, err := utils.GetSecretRef(ctx, g.client, providerConfig.AzureDevOps.AccessTokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
} else if providerConfig.AzureDevOps != nil {
if !ScmProviderAllowed(applicationSetInfo, providerConfig.AzureDevOps.API, g.allowedSCMProviders) {
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.AzureDevOps.API)
}
token, err := g.getSecretRef(ctx, providerConfig.AzureDevOps.AccessTokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Azure Devops access token: %w", err)
return nil, fmt.Errorf("error fetching Azure Devops access token: %v", err)
}
provider, err = scm_provider.NewAzureDevOpsProvider(token, providerConfig.AzureDevOps.Organization, providerConfig.AzureDevOps.API, providerConfig.AzureDevOps.TeamProject, providerConfig.AzureDevOps.AllBranches)
provider, err = scm_provider.NewAzureDevOpsProvider(ctx, token, providerConfig.AzureDevOps.Organization, providerConfig.AzureDevOps.API, providerConfig.AzureDevOps.TeamProject, providerConfig.AzureDevOps.AllBranches)
if err != nil {
return nil, fmt.Errorf("error initializing Azure Devops service: %w", err)
return nil, fmt.Errorf("error initializing Azure Devops service: %v", err)
}
case providerConfig.Bitbucket != nil:
appPassword, err := utils.GetSecretRef(ctx, g.client, providerConfig.Bitbucket.AppPasswordRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
} else if providerConfig.Bitbucket != nil {
appPassword, err := g.getSecretRef(ctx, providerConfig.Bitbucket.AppPasswordRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Bitbucket cloud appPassword: %w", err)
return nil, fmt.Errorf("error fetching Bitbucket cloud appPassword: %v", err)
}
provider, err = scm_provider.NewBitBucketCloudProvider(providerConfig.Bitbucket.Owner, providerConfig.Bitbucket.User, appPassword, providerConfig.Bitbucket.AllBranches)
provider, err = scm_provider.NewBitBucketCloudProvider(ctx, providerConfig.Bitbucket.Owner, providerConfig.Bitbucket.User, appPassword, providerConfig.Bitbucket.AllBranches)
if err != nil {
return nil, fmt.Errorf("error initializing Bitbucket cloud service: %w", err)
return nil, fmt.Errorf("error initializing Bitbucket cloud service: %v", err)
}
case providerConfig.AWSCodeCommit != nil:
} else if providerConfig.AWSCodeCommit != nil {
var awsErr error
provider, awsErr = scm_provider.NewAWSCodeCommitProvider(ctx, providerConfig.AWSCodeCommit.TagFilters, providerConfig.AWSCodeCommit.Role, providerConfig.AWSCodeCommit.Region, providerConfig.AWSCodeCommit.AllBranches)
if awsErr != nil {
return nil, fmt.Errorf("error initializing AWS codecommit service: %w", awsErr)
return nil, fmt.Errorf("error initializing AWS codecommit service: %v", awsErr)
}
default:
return nil, errors.New("no SCM provider implementation configured")
} else {
return nil, fmt.Errorf("no SCM provider implementation configured")
}
// Find all the available repos.
repos, err := scm_provider.ListRepos(ctx, provider, providerConfig.Filters, providerConfig.CloneProtocol)
if err != nil {
return nil, fmt.Errorf("error listing repos: %w", err)
return nil, fmt.Errorf("error listing repos: %v", err)
}
paramsArray := make([]map[string]any, 0, len(repos))
paramsArray := make([]map[string]interface{}, 0, len(repos))
var shortSHALength int
var shortSHALength7 int
for _, repo := range repos {
@@ -254,10 +202,9 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
shortSHALength7 = len(repo.SHA)
}
params := map[string]any{
params := map[string]interface{}{
"organization": repo.Organization,
"repository": repo.Repository,
"repository_id": repo.RepositoryId,
"url": repo.URL,
"branch": repo.Branch,
"sha": repo.SHA,
@@ -277,37 +224,47 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
return paramsArray, nil
}
func (g *SCMProviderGenerator) githubProvider(ctx context.Context, github *argoprojiov1alpha1.SCMProviderGeneratorGithub, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (scm_provider.SCMProviderService, error) {
var metricsCtx *services.MetricsContext
var httpClient *http.Client
if g.enableGitHubAPIMetrics {
metricsCtx = &services.MetricsContext{
AppSetNamespace: applicationSetInfo.Namespace,
AppSetName: applicationSetInfo.Name,
}
httpClient = services.NewGitHubMetricsClient(metricsCtx)
func (g *SCMProviderGenerator) getSecretRef(ctx context.Context, ref *argoprojiov1alpha1.SecretRef, namespace string) (string, error) {
if ref == nil {
return "", nil
}
secret := &corev1.Secret{}
err := g.client.Get(
ctx,
client.ObjectKey{
Name: ref.SecretName,
Namespace: namespace,
},
secret)
if err != nil {
return "", fmt.Errorf("error fetching secret %s/%s: %v", namespace, ref.SecretName, err)
}
tokenBytes, ok := secret.Data[ref.Key]
if !ok {
return "", fmt.Errorf("key %q in secret %s/%s not found", ref.Key, namespace, ref.SecretName)
}
return string(tokenBytes), nil
}
func (g *SCMProviderGenerator) githubProvider(ctx context.Context, github *argoprojiov1alpha1.SCMProviderGeneratorGithub, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (scm_provider.SCMProviderService, error) {
if github.AppSecretName != "" {
auth, err := g.GitHubApps.GetAuthSecret(ctx, github.AppSecretName)
if err != nil {
return nil, fmt.Errorf("error fetching Github app secret: %w", err)
return nil, fmt.Errorf("error fetching Github app secret: %v", err)
}
if g.enableGitHubAPIMetrics {
return scm_provider.NewGithubAppProviderFor(*auth, github.Organization, github.API, github.AllBranches, httpClient)
}
return scm_provider.NewGithubAppProviderFor(*auth, github.Organization, github.API, github.AllBranches)
return scm_provider.NewGithubAppProviderFor(
*auth,
github.Organization,
github.API,
github.AllBranches,
)
}
token, err := utils.GetSecretRef(ctx, g.client, github.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
token, err := g.getSecretRef(ctx, github.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Github token: %w", err)
return nil, fmt.Errorf("error fetching Github token: %v", err)
}
if g.enableGitHubAPIMetrics {
return scm_provider.NewGithubProvider(github.Organization, token, github.API, github.AllBranches, httpClient)
}
return scm_provider.NewGithubProvider(github.Organization, token, github.API, github.AllBranches)
return scm_provider.NewGithubProvider(ctx, github.Organization, token, github.API, github.AllBranches)
}

View File

@@ -1,24 +1,90 @@
package generators
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/argoproj/argo-cd/v3/applicationset/services/scm_provider"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func TestSCMProviderGenerateParams(t *testing.T) {
t.Parallel()
func TestSCMProviderGetSecretRef(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test-secret", Namespace: "test"},
Data: map[string][]byte{
"my-token": []byte("secret"),
},
}
gen := &SCMProviderGenerator{client: fake.NewClientBuilder().WithObjects(secret).Build()}
ctx := context.Background()
cases := []struct {
name, namespace, token string
ref *argoprojiov1alpha1.SecretRef
hasError bool
}{
{
name: "valid ref",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "my-token"},
namespace: "test",
token: "secret",
hasError: false,
},
{
name: "nil ref",
ref: nil,
namespace: "test",
token: "",
hasError: false,
},
{
name: "wrong name",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "other", Key: "my-token"},
namespace: "test",
token: "",
hasError: true,
},
{
name: "wrong key",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "other-token"},
namespace: "test",
token: "",
hasError: true,
},
{
name: "wrong namespace",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "my-token"},
namespace: "other",
token: "",
hasError: true,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
token, err := gen.getSecretRef(ctx, c.ref, c.namespace)
if c.hasError {
assert.NotNil(t, err)
} else {
assert.Nil(t, err)
}
assert.Equal(t, c.token, token)
})
}
}
func TestSCMProviderGenerateParams(t *testing.T) {
cases := []struct {
name string
repos []*scm_provider.Repository
values map[string]string
expected []map[string]any
expected []map[string]interface{}
expectedError error
}{
{
@@ -27,7 +93,6 @@ func TestSCMProviderGenerateParams(t *testing.T) {
{
Organization: "myorg",
Repository: "repo1",
RepositoryId: 190320251,
URL: "git@github.com:myorg/repo1.git",
Branch: "main",
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
@@ -36,36 +101,33 @@ func TestSCMProviderGenerateParams(t *testing.T) {
{
Organization: "myorg",
Repository: "repo2",
RepositoryId: 190320252,
URL: "git@github.com:myorg/repo2.git",
Branch: "main",
SHA: "59d0",
},
},
expected: []map[string]any{
expected: []map[string]interface{}{
{
"organization": "myorg",
"repository": "repo1",
"repository_id": 190320251,
"url": "git@github.com:myorg/repo1.git",
"branch": "main",
"organization": "myorg",
"repository": "repo1",
"url": "git@github.com:myorg/repo1.git",
"branch": "main",
"branchNormalized": "main",
"sha": "0bc57212c3cbbec69d20b34c507284bd300def5b",
"short_sha": "0bc57212",
"short_sha_7": "0bc5721",
"labels": "prod,staging",
"sha": "0bc57212c3cbbec69d20b34c507284bd300def5b",
"short_sha": "0bc57212",
"short_sha_7": "0bc5721",
"labels": "prod,staging",
},
{
"organization": "myorg",
"repository": "repo2",
"repository_id": 190320252,
"url": "git@github.com:myorg/repo2.git",
"branch": "main",
"organization": "myorg",
"repository": "repo2",
"url": "git@github.com:myorg/repo2.git",
"branch": "main",
"branchNormalized": "main",
"sha": "59d0",
"short_sha": "59d0",
"short_sha_7": "59d0",
"labels": "",
"sha": "59d0",
"short_sha": "59d0",
"short_sha_7": "59d0",
"labels": "",
},
},
},
@@ -75,7 +137,6 @@ func TestSCMProviderGenerateParams(t *testing.T) {
{
Organization: "myorg",
Repository: "repo3",
RepositoryId: 190320253,
URL: "git@github.com:myorg/repo3.git",
Branch: "main",
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
@@ -86,11 +147,10 @@ func TestSCMProviderGenerateParams(t *testing.T) {
"foo": "bar",
"should_i_force_push_to": "{{ branch }}?",
},
expected: []map[string]any{
expected: []map[string]interface{}{
{
"organization": "myorg",
"repository": "repo3",
"repository_id": 190320253,
"url": "git@github.com:myorg/repo3.git",
"branch": "main",
"branchNormalized": "main",
@@ -103,52 +163,6 @@ func TestSCMProviderGenerateParams(t *testing.T) {
},
},
},
{
name: "Repos with and without id",
repos: []*scm_provider.Repository{
{
Organization: "myorg",
Repository: "repo4",
RepositoryId: "idaz09",
URL: "git@github.com:myorg/repo4.git",
Branch: "main",
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
},
{
Organization: "myorg",
Repository: "repo5",
URL: "git@github.com:myorg/repo5.git",
Branch: "main",
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
},
},
expected: []map[string]any{
{
"organization": "myorg",
"repository": "repo4",
"repository_id": "idaz09",
"url": "git@github.com:myorg/repo4.git",
"branch": "main",
"branchNormalized": "main",
"sha": "0bc57212c3cbbec69d20b34c507284bd300def5b",
"short_sha": "0bc57212",
"short_sha_7": "0bc5721",
"labels": "",
},
{
"organization": "myorg",
"repository": "repo5",
"repository_id": nil,
"url": "git@github.com:myorg/repo5.git",
"branch": "main",
"branchNormalized": "main",
"sha": "0bc57212c3cbbec69d20b34c507284bd300def5b",
"short_sha": "0bc57212",
"short_sha_7": "0bc5721",
"labels": "",
},
},
},
}
for _, testCase := range cases {
@@ -160,7 +174,7 @@ func TestSCMProviderGenerateParams(t *testing.T) {
mockProvider := &scm_provider.MockProvider{
Repos: testCaseCopy.repos,
}
scmGenerator := &SCMProviderGenerator{overrideProvider: mockProvider, SCMConfig: SCMConfig{enableSCMProviders: true}}
scmGenerator := &SCMProviderGenerator{overrideProvider: mockProvider}
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -174,24 +188,24 @@ func TestSCMProviderGenerateParams(t *testing.T) {
},
}
got, err := scmGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
got, err := scmGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
if testCaseCopy.expectedError != nil {
assert.EqualError(t, err, testCaseCopy.expectedError.Error())
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got)
}
})
}
}
func TestAllowedSCMProvider(t *testing.T) {
t.Parallel()
cases := []struct {
name string
providerConfig *argoprojiov1alpha1.SCMProviderGenerator
expectedError string
}{
{
name: "Error Github",
@@ -200,6 +214,7 @@ func TestAllowedSCMProvider(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: "scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
},
{
name: "Error Gitlab",
@@ -208,6 +223,7 @@ func TestAllowedSCMProvider(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: "scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
},
{
name: "Error Gitea",
@@ -216,6 +232,7 @@ func TestAllowedSCMProvider(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: "scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
},
{
name: "Error Bitbucket",
@@ -224,6 +241,7 @@ func TestAllowedSCMProvider(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: "scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
},
{
name: "Error AzureDevops",
@@ -232,6 +250,7 @@ func TestAllowedSCMProvider(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: "scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
},
}
@@ -241,18 +260,13 @@ func TestAllowedSCMProvider(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel()
scmGenerator := &SCMProviderGenerator{
SCMConfig: SCMConfig{
allowedSCMProviders: []string{
"github.myorg.com",
"gitlab.myorg.com",
"gitea.myorg.com",
"bitbucket.myorg.com",
"azuredevops.myorg.com",
},
enableSCMProviders: true,
},
}
scmGenerator := &SCMProviderGenerator{allowedSCMProviders: []string{
"github.myorg.com",
"gitlab.myorg.com",
"gitea.myorg.com",
"bitbucket.myorg.com",
"azuredevops.myorg.com",
}}
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -265,33 +279,10 @@ func TestAllowedSCMProvider(t *testing.T) {
},
}
_, err := scmGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
_, err := scmGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
require.Error(t, err, "Must return an error")
var expectedError ErrDisallowedSCMProvider
assert.ErrorAs(t, err, &expectedError)
assert.Error(t, err, "Must return an error")
assert.Equal(t, testCaseCopy.expectedError, err.Error())
})
}
}
func TestSCMProviderDisabled_SCMGenerator(t *testing.T) {
generator := &SCMProviderGenerator{SCMConfig: SCMConfig{enableSCMProviders: false}}
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
SCMProvider: &argoprojiov1alpha1.SCMProviderGenerator{
Github: &argoprojiov1alpha1.SCMProviderGeneratorGithub{
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
}},
},
}
_, err := generator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
assert.ErrorIs(t, err, ErrSCMProvidersDisabled)
}

View File

@@ -1,5 +0,0 @@
package generators
type SCMGeneratorWithCustomApiUrl interface { //nolint:revive //FIXME(var-naming)
CustomApiUrl() string
}

View File

@@ -1,49 +0,0 @@
package generators
import (
"context"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v3/applicationset/services"
)
func GetGenerators(ctx context.Context, c client.Client, k8sClient kubernetes.Interface, controllerNamespace string, argoCDService services.Repos, dynamicClient dynamic.Interface, scmConfig SCMConfig) map[string]Generator {
terminalGenerators := map[string]Generator{
"List": NewListGenerator(),
"Clusters": NewClusterGenerator(ctx, c, k8sClient, controllerNamespace),
"Git": NewGitGenerator(argoCDService, controllerNamespace),
"SCMProvider": NewSCMProviderGenerator(c, scmConfig),
"ClusterDecisionResource": NewDuckTypeGenerator(ctx, dynamicClient, k8sClient, controllerNamespace),
"PullRequest": NewPullRequestGenerator(c, scmConfig),
"Plugin": NewPluginGenerator(c, controllerNamespace),
}
nestedGenerators := map[string]Generator{
"List": terminalGenerators["List"],
"Clusters": terminalGenerators["Clusters"],
"Git": terminalGenerators["Git"],
"SCMProvider": terminalGenerators["SCMProvider"],
"ClusterDecisionResource": terminalGenerators["ClusterDecisionResource"],
"PullRequest": terminalGenerators["PullRequest"],
"Plugin": terminalGenerators["Plugin"],
"Matrix": NewMatrixGenerator(terminalGenerators),
"Merge": NewMergeGenerator(terminalGenerators),
}
topLevelGenerators := map[string]Generator{
"List": terminalGenerators["List"],
"Clusters": terminalGenerators["Clusters"],
"Git": terminalGenerators["Git"],
"SCMProvider": terminalGenerators["SCMProvider"],
"ClusterDecisionResource": terminalGenerators["ClusterDecisionResource"],
"PullRequest": terminalGenerators["PullRequest"],
"Plugin": terminalGenerators["Plugin"],
"Matrix": NewMatrixGenerator(nestedGenerators),
"Merge": NewMergeGenerator(nestedGenerators),
}
return topLevelGenerators
}

View File

@@ -4,14 +4,15 @@ import (
"fmt"
)
func appendTemplatedValues(values map[string]string, params map[string]any, useGoTemplate bool, goTemplateOptions []string) error {
func appendTemplatedValues(values map[string]string, params map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) error {
// We create a local map to ensure that we do not fall victim to a billion-laughs attack. We iterate through the
// cluster values map and only replace values in said map if it has already been allowlisted in the params map.
// Once we iterate through all the cluster values we can then safely merge the `tmp` map into the main params map.
tmp := map[string]any{}
tmp := map[string]interface{}{}
for key, value := range values {
result, err := replaceTemplatedString(value, params, useGoTemplate, goTemplateOptions)
if err != nil {
return fmt.Errorf("failed to replace templated string: %w", err)
}
@@ -22,7 +23,7 @@ func appendTemplatedValues(values map[string]string, params map[string]any, useG
}
tmp["values"].(map[string]string)[key] = result
} else {
tmp["values."+key] = result
tmp[fmt.Sprintf("values.%s", key)] = result
}
}
@@ -33,7 +34,7 @@ func appendTemplatedValues(values map[string]string, params map[string]any, useG
return nil
}
func replaceTemplatedString(value string, params map[string]any, useGoTemplate bool, goTemplateOptions []string) (string, error) {
func replaceTemplatedString(value string, params map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (string, error) {
replacedTmplStr, err := render.Replace(value, params, useGoTemplate, goTemplateOptions)
if err != nil {
return "", fmt.Errorf("failed to replace templated string with rendered values: %w", err)

View File

@@ -4,25 +4,24 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestValueInterpolation(t *testing.T) {
testCases := []struct {
name string
values map[string]string
params map[string]any
expected map[string]any
params map[string]interface{}
expected map[string]interface{}
}{
{
name: "Simple interpolation",
values: map[string]string{
"hello": "{{ world }}",
},
params: map[string]any{
params: map[string]interface{}{
"world": "world!",
},
expected: map[string]any{
expected: map[string]interface{}{
"world": "world!",
"values.hello": "world!",
},
@@ -32,8 +31,8 @@ func TestValueInterpolation(t *testing.T) {
values: map[string]string{
"non-existent": "{{ non-existent }}",
},
params: map[string]any{},
expected: map[string]any{
params: map[string]interface{}{},
expected: map[string]interface{}{
"values.non-existent": "{{ non-existent }}",
},
},
@@ -44,8 +43,8 @@ func TestValueInterpolation(t *testing.T) {
"lol2": "{{values.lol1}}{{values.lol1}}",
"lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}",
},
params: map[string]any{},
expected: map[string]any{
params: map[string]interface{}{},
expected: map[string]interface{}{
"values.lol1": "lol",
"values.lol2": "{{values.lol1}}{{values.lol1}}",
"values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}",
@@ -54,10 +53,11 @@ func TestValueInterpolation(t *testing.T) {
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
err := appendTemplatedValues(testCase.values, testCase.params, false, nil)
require.NoError(t, err)
assert.Equal(t, testCase.expected, testCase.params)
assert.NoError(t, err)
assert.EqualValues(t, testCase.expected, testCase.params)
})
}
}
@@ -66,18 +66,18 @@ func TestValueInterpolationWithGoTemplating(t *testing.T) {
testCases := []struct {
name string
values map[string]string
params map[string]any
expected map[string]any
params map[string]interface{}
expected map[string]interface{}
}{
{
name: "Simple interpolation",
values: map[string]string{
"hello": "{{ .world }}",
},
params: map[string]any{
params: map[string]interface{}{
"world": "world!",
},
expected: map[string]any{
expected: map[string]interface{}{
"world": "world!",
"values": map[string]string{
"hello": "world!",
@@ -89,8 +89,8 @@ func TestValueInterpolationWithGoTemplating(t *testing.T) {
values: map[string]string{
"non_existent": "{{ default \"bar\" .non_existent }}",
},
params: map[string]any{},
expected: map[string]any{
params: map[string]interface{}{},
expected: map[string]interface{}{
"values": map[string]string{
"non_existent": "bar",
},
@@ -103,8 +103,8 @@ func TestValueInterpolationWithGoTemplating(t *testing.T) {
"lol2": "{{.values.lol1}}{{.values.lol1}}",
"lol3": "{{.values.lol2}}{{.values.lol2}}{{.values.lol2}}",
},
params: map[string]any{},
expected: map[string]any{
params: map[string]interface{}{},
expected: map[string]interface{}{
"values": map[string]string{
"lol1": "lol",
"lol2": "<no value><no value>",
@@ -115,10 +115,11 @@ func TestValueInterpolationWithGoTemplating(t *testing.T) {
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
err := appendTemplatedValues(testCase.values, testCase.params, true, nil)
require.NoError(t, err)
assert.Equal(t, testCase.expected, testCase.params)
assert.NoError(t, err)
assert.EqualValues(t, testCase.expected, testCase.params)
})
}
}

View File

@@ -1,21 +0,0 @@
package metrics
import (
"github.com/prometheus/client_golang/prometheus"
)
// Fake implementation for testing
func NewFakeAppsetMetrics() *ApplicationsetMetrics {
reconcileHistogram := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "argocd_appset_reconcile",
Help: "Application reconciliation performance in seconds.",
// Buckets can be set later on after observing median time
},
[]string{"name", "namespace"},
)
return &ApplicationsetMetrics{
reconcileHistogram: reconcileHistogram,
}
}

View File

@@ -1,135 +0,0 @@
package metrics
import (
"time"
"github.com/prometheus/client_golang/prometheus"
"k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/controller-runtime/pkg/metrics"
argoappv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
applisters "github.com/argoproj/argo-cd/v3/pkg/client/listers/application/v1alpha1"
metricsutil "github.com/argoproj/argo-cd/v3/util/metrics"
"github.com/argoproj/argo-cd/v3/util/metrics/kubectl"
)
var (
descAppsetLabels *prometheus.Desc
descAppsetDefaultLabels = []string{"namespace", "name"}
descAppsetInfo = prometheus.NewDesc(
"argocd_appset_info",
"Information about applicationset",
append(descAppsetDefaultLabels, "resource_update_status"),
nil,
)
descAppsetGeneratedApps = prometheus.NewDesc(
"argocd_appset_owned_applications",
"Number of applications owned by the applicationset",
descAppsetDefaultLabels,
nil,
)
)
type ApplicationsetMetrics struct {
reconcileHistogram *prometheus.HistogramVec
}
type appsetCollector struct {
lister applisters.ApplicationSetLister
// appsClientSet appclientset.Interface
labels []string
filter func(appset *argoappv1.ApplicationSet) bool
}
func NewApplicationsetMetrics(appsetLister applisters.ApplicationSetLister, appsetLabels []string, appsetFilter func(appset *argoappv1.ApplicationSet) bool) ApplicationsetMetrics {
reconcileHistogram := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "argocd_appset_reconcile",
Help: "Application reconciliation performance in seconds.",
// Buckets can be set later on after observing median time
},
descAppsetDefaultLabels,
)
appsetCollector := newAppsetCollector(appsetLister, appsetLabels, appsetFilter)
// Register collectors and metrics
metrics.Registry.MustRegister(reconcileHistogram)
metrics.Registry.MustRegister(appsetCollector)
kubectl.RegisterWithClientGo()
kubectl.RegisterWithPrometheus(metrics.Registry)
return ApplicationsetMetrics{
reconcileHistogram: reconcileHistogram,
}
}
func (m *ApplicationsetMetrics) ObserveReconcile(appset *argoappv1.ApplicationSet, duration time.Duration) {
m.reconcileHistogram.WithLabelValues(appset.Namespace, appset.Name).Observe(duration.Seconds())
}
func newAppsetCollector(lister applisters.ApplicationSetLister, labels []string, filter func(appset *argoappv1.ApplicationSet) bool) *appsetCollector {
descAppsetDefaultLabels = []string{"namespace", "name"}
if len(labels) > 0 {
descAppsetLabels = prometheus.NewDesc(
"argocd_appset_labels",
"Applicationset labels translated to Prometheus labels",
append(descAppsetDefaultLabels, metricsutil.NormalizeLabels("label", labels)...),
nil,
)
}
return &appsetCollector{
lister: lister,
labels: labels,
filter: filter,
}
}
// Describe implements the prometheus.Collector interface
func (c *appsetCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- descAppsetInfo
ch <- descAppsetGeneratedApps
if len(c.labels) > 0 {
ch <- descAppsetLabels
}
}
// Collect implements the prometheus.Collector interface
func (c *appsetCollector) Collect(ch chan<- prometheus.Metric) {
appsets, _ := c.lister.List(labels.NewSelector())
for _, appset := range appsets {
if c.filter(appset) {
collectAppset(appset, c.labels, ch)
}
}
}
func collectAppset(appset *argoappv1.ApplicationSet, labelsToCollect []string, ch chan<- prometheus.Metric) {
labelValues := make([]string, 0)
commonLabelValues := []string{appset.Namespace, appset.Name}
for _, label := range labelsToCollect {
labelValues = append(labelValues, appset.GetLabels()[label])
}
resourceUpdateStatus := "Unknown"
for _, condition := range appset.Status.Conditions {
if condition.Type == argoappv1.ApplicationSetConditionResourcesUpToDate {
resourceUpdateStatus = condition.Reason
}
}
if len(labelsToCollect) > 0 {
ch <- prometheus.MustNewConstMetric(descAppsetLabels, prometheus.GaugeValue, 1, append(commonLabelValues, labelValues...)...)
}
ch <- prometheus.MustNewConstMetric(descAppsetInfo, prometheus.GaugeValue, 1, appset.Namespace, appset.Name, resourceUpdateStatus)
ch <- prometheus.MustNewConstMetric(descAppsetGeneratedApps, prometheus.GaugeValue, float64(len(appset.Status.Resources)), appset.Namespace, appset.Name)
}

View File

@@ -1,252 +0,0 @@
package metrics
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/metrics"
"sigs.k8s.io/yaml"
"github.com/argoproj/argo-cd/v3/applicationset/utils"
argoappv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
metricsutil "github.com/argoproj/argo-cd/v3/util/metrics"
)
var (
applicationsetNamespaces = []string{"argocd", "test-namespace1"}
filter = func(appset *argoappv1.ApplicationSet) bool {
return utils.IsNamespaceAllowed(applicationsetNamespaces, appset.Namespace)
}
collectedLabels = []string{"included/test"}
)
const fakeAppsetList = `
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: test1
namespace: argocd
labels:
included/test: test
not-included.label/test: test
spec:
generators:
- git:
directories:
- path: test/*
repoURL: https://github.com/test/test.git
revision: HEAD
template:
metadata:
name: '{{.path.basename}}'
spec:
destination:
namespace: '{{.path.basename}}'
server: https://kubernetes.default.svc
project: default
source:
path: '{{.path.path}}'
repoURL: https://github.com/test/test.git
targetRevision: HEAD
status:
resources:
- group: argoproj.io
health:
status: Missing
kind: Application
name: test-app1
namespace: argocd
status: OutOfSync
version: v1alpha1
- group: argoproj.io
health:
status: Missing
kind: Application
name: test-app2
namespace: argocd
status: OutOfSync
version: v1alpha1
conditions:
- lastTransitionTime: "2024-01-01T00:00:00Z"
message: Successfully generated parameters for all Applications
reason: ApplicationSetUpToDate
status: "False"
type: ErrorOccurred
- lastTransitionTime: "2024-01-01T00:00:00Z"
message: Successfully generated parameters for all Applications
reason: ParametersGenerated
status: "True"
type: ParametersGenerated
- lastTransitionTime: "2024-01-01T00:00:00Z"
message: ApplicationSet up to date
reason: ApplicationSetUpToDate
status: "True"
type: ResourcesUpToDate
---
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: test2
namespace: argocd
labels:
not-included.label/test: test
spec:
generators:
- git:
directories:
- path: test/*
repoURL: https://github.com/test/test.git
revision: HEAD
template:
metadata:
name: '{{.path.basename}}'
spec:
destination:
namespace: '{{.path.basename}}'
server: https://kubernetes.default.svc
project: default
source:
path: '{{.path.path}}'
repoURL: https://github.com/test/test.git
targetRevision: HEAD
---
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: should-be-filtered-out
namespace: not-allowed
spec:
generators:
- git:
directories:
- path: test/*
repoURL: https://github.com/test/test.git
revision: HEAD
template:
metadata:
name: '{{.path.basename}}'
spec:
destination:
namespace: '{{.path.basename}}'
server: https://kubernetes.default.svc
project: default
source:
path: '{{.path.path}}'
repoURL: https://github.com/test/test.git
targetRevision: HEAD
`
func newFakeAppsets(fakeAppsetYAML string) []argoappv1.ApplicationSet {
var results []argoappv1.ApplicationSet
appsetRawYamls := strings.Split(fakeAppsetYAML, "---")
for _, appsetRawYaml := range appsetRawYamls {
var appset argoappv1.ApplicationSet
err := yaml.Unmarshal([]byte(appsetRawYaml), &appset)
if err != nil {
panic(err)
}
results = append(results, appset)
}
return results
}
func TestApplicationsetCollector(t *testing.T) {
appsetList := newFakeAppsets(fakeAppsetList)
client := initializeClient(appsetList)
metrics.Registry = prometheus.NewRegistry()
appsetCollector := newAppsetCollector(utils.NewAppsetLister(client), collectedLabels, filter)
metrics.Registry.MustRegister(appsetCollector)
req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, "/metrics", http.NoBody)
require.NoError(t, err)
rr := httptest.NewRecorder()
handler := promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
// Test correct appset_info and owned applications
assert.Contains(t, rr.Body.String(), `
argocd_appset_info{name="test1",namespace="argocd",resource_update_status="ApplicationSetUpToDate"} 1
`)
assert.Contains(t, rr.Body.String(), `
argocd_appset_owned_applications{name="test1",namespace="argocd"} 2
`)
// Test labels collection - should not include labels not included in the list of collected labels and include the ones that do.
assert.Contains(t, rr.Body.String(), `
argocd_appset_labels{label_included_test="test",name="test1",namespace="argocd"} 1
`)
assert.NotContains(t, rr.Body.String(), normalizeLabel("not-included.label/test"))
// If collected label is not present on the applicationset the value should be empty
assert.Contains(t, rr.Body.String(), `
argocd_appset_labels{label_included_test="",name="test2",namespace="argocd"} 1
`)
// If ResourcesUpToDate condition is not present on the applicationset the status should be reported as 'Unknown'
assert.Contains(t, rr.Body.String(), `
argocd_appset_info{name="test2",namespace="argocd",resource_update_status="Unknown"} 1
`)
// If there are no resources on the applicationset the owned application gague should return 0
assert.Contains(t, rr.Body.String(), `
argocd_appset_owned_applications{name="test2",namespace="argocd"} 0
`)
// Test that filter is working
assert.NotContains(t, rr.Body.String(), `name="should-be-filtered-out"`)
}
func TestObserveReconcile(t *testing.T) {
appsetList := newFakeAppsets(fakeAppsetList)
client := initializeClient(appsetList)
metrics.Registry = prometheus.NewRegistry()
appsetMetrics := NewApplicationsetMetrics(utils.NewAppsetLister(client), collectedLabels, filter)
req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, "/metrics", http.NoBody)
require.NoError(t, err)
rr := httptest.NewRecorder()
handler := promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{})
appsetMetrics.ObserveReconcile(&appsetList[0], 5*time.Second)
handler.ServeHTTP(rr, req)
assert.Contains(t, rr.Body.String(), `
argocd_appset_reconcile_sum{name="test1",namespace="argocd"} 5
`)
// If there are no resources on the applicationset the owned application gague should return 0
assert.Contains(t, rr.Body.String(), `
argocd_appset_reconcile_count{name="test1",namespace="argocd"} 1
`)
}
func initializeClient(appsets []argoappv1.ApplicationSet) ctrlclient.WithWatch {
scheme := runtime.NewScheme()
err := argoappv1.AddToScheme(scheme)
if err != nil {
panic(err)
}
var clientObjects []ctrlclient.Object
for _, appset := range appsets {
clientObjects = append(clientObjects, appset.DeepCopy())
}
return fake.NewClientBuilder().WithScheme(scheme).WithObjects(clientObjects...).Build()
}
func normalizeLabel(label string) string {
return metricsutil.NormalizeLabels("label", []string{label})[0]
}

View File

@@ -1,241 +0,0 @@
package services
import (
"net/http"
"strconv"
"time"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"sigs.k8s.io/controller-runtime/pkg/metrics"
)
// Doc for the GitHub API rate limit headers:
// https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#checking-the-status-of-your-rate-limit
// Metric names as constants
const (
githubAPIRequestTotalMetricName = "argocd_github_api_requests_total"
githubAPIRequestDurationMetricName = "argocd_github_api_request_duration_seconds"
githubAPIRateLimitRemainingMetricName = "argocd_github_api_rate_limit_remaining"
githubAPIRateLimitLimitMetricName = "argocd_github_api_rate_limit_limit"
githubAPIRateLimitResetMetricName = "argocd_github_api_rate_limit_reset_seconds"
githubAPIRateLimitUsedMetricName = "argocd_github_api_rate_limit_used"
)
// GitHubMetrics groups all metric vectors for easier injection and registration
type GitHubMetrics struct {
RequestTotal *prometheus.CounterVec
RequestDuration *prometheus.HistogramVec
RateLimitRemaining *prometheus.GaugeVec
RateLimitLimit *prometheus.GaugeVec
RateLimitReset *prometheus.GaugeVec
RateLimitUsed *prometheus.GaugeVec
}
// Factory for a new set of GitHub metrics (for tests or custom registries)
func NewGitHubMetrics() *GitHubMetrics {
return &GitHubMetrics{
RequestTotal: NewGitHubAPIRequestTotal(),
RequestDuration: NewGitHubAPIRequestDuration(),
RateLimitRemaining: NewGitHubAPIRateLimitRemaining(),
RateLimitLimit: NewGitHubAPIRateLimitLimit(),
RateLimitReset: NewGitHubAPIRateLimitReset(),
RateLimitUsed: NewGitHubAPIRateLimitUsed(),
}
}
// Factory functions for each metric vector
func NewGitHubAPIRequestTotal() *prometheus.CounterVec {
return prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: githubAPIRequestTotalMetricName,
Help: "Total number of GitHub API requests",
},
[]string{"method", "endpoint", "status", "appset_namespace", "appset_name"},
)
}
func NewGitHubAPIRequestDuration() *prometheus.HistogramVec {
return prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: githubAPIRequestDurationMetricName,
Help: "GitHub API request duration in seconds",
Buckets: prometheus.DefBuckets,
},
[]string{"method", "endpoint", "appset_namespace", "appset_name"},
)
}
func NewGitHubAPIRateLimitRemaining() *prometheus.GaugeVec {
return prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: githubAPIRateLimitRemainingMetricName,
Help: "The number of requests remaining in the current rate limit window",
},
[]string{"endpoint", "appset_namespace", "appset_name", "resource"},
)
}
func NewGitHubAPIRateLimitLimit() *prometheus.GaugeVec {
return prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: githubAPIRateLimitLimitMetricName,
Help: "The maximum number of requests that you can make per hour",
},
[]string{"endpoint", "appset_namespace", "appset_name", "resource"},
)
}
func NewGitHubAPIRateLimitReset() *prometheus.GaugeVec {
return prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: githubAPIRateLimitResetMetricName,
Help: "The time left till the current rate limit window resets, in seconds",
},
[]string{"endpoint", "appset_namespace", "appset_name", "resource"},
)
}
func NewGitHubAPIRateLimitUsed() *prometheus.GaugeVec {
return prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: githubAPIRateLimitUsedMetricName,
Help: "The number of requests used in the current rate limit window",
},
[]string{"endpoint", "appset_namespace", "appset_name", "resource"},
)
}
// Global metrics (registered with the default registry)
var globalGitHubMetrics = NewGitHubMetrics()
func init() {
log.Debug("Registering GitHub API AppSet metrics")
metrics.Registry.MustRegister(globalGitHubMetrics.RequestTotal)
metrics.Registry.MustRegister(globalGitHubMetrics.RequestDuration)
metrics.Registry.MustRegister(globalGitHubMetrics.RateLimitRemaining)
metrics.Registry.MustRegister(globalGitHubMetrics.RateLimitLimit)
metrics.Registry.MustRegister(globalGitHubMetrics.RateLimitReset)
metrics.Registry.MustRegister(globalGitHubMetrics.RateLimitUsed)
}
type MetricsContext struct {
AppSetNamespace string
AppSetName string
}
// GitHubMetricsTransport is a custom http.RoundTripper that collects GitHub API metrics
type GitHubMetricsTransport struct {
transport http.RoundTripper
metricsContext *MetricsContext
metrics *GitHubMetrics
}
// RoundTrip implements http.RoundTripper interface and collects metrics along with debug logging
func (t *GitHubMetricsTransport) RoundTrip(req *http.Request) (*http.Response, error) {
endpoint := req.URL.Path
method := req.Method
appsetNamespace := "unknown"
appsetName := "unknown"
if t.metricsContext != nil {
appsetNamespace = t.metricsContext.AppSetNamespace
appsetName = t.metricsContext.AppSetName
}
log.WithFields(log.Fields{
"method": method,
"endpoint": endpoint,
"applicationset": map[string]string{"name": appsetName, "namespace": appsetNamespace},
}).Debugf("Invoking GitHub API")
startTime := time.Now()
resp, err := t.transport.RoundTrip(req)
duration := time.Since(startTime)
// Record metrics
t.metrics.RequestDuration.WithLabelValues(method, endpoint, appsetNamespace, appsetName).Observe(duration.Seconds())
status := "0"
if resp != nil {
status = strconv.Itoa(resp.StatusCode)
}
t.metrics.RequestTotal.WithLabelValues(method, endpoint, status, appsetNamespace, appsetName).Inc()
if resp != nil {
resetHumanReadableTime := ""
remainingInt := 0
limitInt := 0
usedInt := 0
resource := resp.Header.Get("X-RateLimit-Resource")
// Record rate limit metrics if available
if resetTime := resp.Header.Get("X-RateLimit-Reset"); resetTime != "" {
if resetUnix, err := strconv.ParseInt(resetTime, 10, 64); err == nil {
// Calculate seconds until reset (reset timestamp - current time)
secondsUntilReset := resetUnix - time.Now().Unix()
t.metrics.RateLimitReset.WithLabelValues(endpoint, appsetNamespace, appsetName, resource).Set(float64(secondsUntilReset))
resetHumanReadableTime = time.Unix(resetUnix, 0).Local().Format("2006-01-02 15:04:05 MST")
}
}
if remaining := resp.Header.Get("X-RateLimit-Remaining"); remaining != "" {
if remainingInt, err = strconv.Atoi(remaining); err == nil {
t.metrics.RateLimitRemaining.WithLabelValues(endpoint, appsetNamespace, appsetName, resource).Set(float64(remainingInt))
}
}
if limit := resp.Header.Get("X-RateLimit-Limit"); limit != "" {
if limitInt, err = strconv.Atoi(limit); err == nil {
t.metrics.RateLimitLimit.WithLabelValues(endpoint, appsetNamespace, appsetName, resource).Set(float64(limitInt))
}
}
if used := resp.Header.Get("X-RateLimit-Used"); used != "" {
if usedInt, err = strconv.Atoi(used); err == nil {
t.metrics.RateLimitUsed.WithLabelValues(endpoint, appsetNamespace, appsetName, resource).Set(float64(usedInt))
}
}
log.WithFields(log.Fields{
"endpoint": endpoint,
"reset": resetHumanReadableTime,
"remaining": remainingInt,
"limit": limitInt,
"used": usedInt,
"resource": resource,
"applicationset": map[string]string{"name": appsetName, "namespace": appsetNamespace},
}).Debugf("GitHub API rate limit info")
}
return resp, err
}
// Full constructor (for tests and advanced use)
func NewGitHubMetricsTransport(
transport http.RoundTripper,
metricsContext *MetricsContext,
metrics *GitHubMetrics,
) *GitHubMetricsTransport {
return &GitHubMetricsTransport{
transport: transport,
metricsContext: metricsContext,
metrics: metrics,
}
}
// Default constructor
func NewDefaultGitHubMetricsTransport(transport http.RoundTripper, metricsContext *MetricsContext) *GitHubMetricsTransport {
return NewGitHubMetricsTransport(
transport,
metricsContext,
globalGitHubMetrics,
)
}
// NewGitHubMetricsClient wraps an http.Client with metrics middleware
func NewGitHubMetricsClient(metricsContext *MetricsContext) *http.Client {
log.Debug("Creating new GitHub metrics client")
return &http.Client{
Transport: NewDefaultGitHubMetricsTransport(http.DefaultTransport, metricsContext),
}
}

View File

@@ -1,235 +0,0 @@
package services
import (
"io"
"net/http"
"net/http/httptest"
"sort"
"strconv"
"strings"
"testing"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/stretchr/testify/assert"
)
type Metric struct {
name string
labels []string
value string
}
var (
endpointLabel = "endpoint=\"/api/test\""
URL = "/api/test"
appsetNamespaceLabel = "appset_namespace=\"test-ns\""
appsetNamespace = "test-ns"
appsetName = "test-appset"
appsetNameLabel = "appset_name=\"test-appset\""
resourceLabel = "resource=\"core\""
rateLimitMetrics = []Metric{
{
name: githubAPIRateLimitRemainingMetricName,
labels: []string{endpointLabel, appsetNamespaceLabel, appsetNameLabel, resourceLabel},
value: "42",
},
{
name: githubAPIRateLimitLimitMetricName,
labels: []string{endpointLabel, appsetNamespaceLabel, appsetNameLabel, resourceLabel},
value: "100",
},
{
name: githubAPIRateLimitUsedMetricName,
labels: []string{endpointLabel, appsetNamespaceLabel, appsetNameLabel, resourceLabel},
value: "58",
},
{
name: githubAPIRateLimitResetMetricName,
labels: []string{endpointLabel, appsetNamespaceLabel, appsetNameLabel, resourceLabel},
value: "1",
},
}
successRequestMetrics = Metric{
name: githubAPIRequestTotalMetricName,
labels: []string{"method=\"GET\"", endpointLabel, "status=\"201\"", appsetNamespaceLabel, appsetNameLabel},
value: "1",
}
failureRequestMetrics = Metric{
name: githubAPIRequestTotalMetricName,
labels: []string{"method=\"GET\"", endpointLabel, "status=\"0\"", appsetNamespaceLabel, appsetNameLabel},
value: "1",
}
)
func TestGitHubMetrics_CollectorApproach_Success(t *testing.T) {
metrics := NewGitHubMetrics()
reg := prometheus.NewRegistry()
reg.MustRegister(
metrics.RequestTotal,
metrics.RequestDuration,
metrics.RateLimitRemaining,
metrics.RateLimitLimit,
metrics.RateLimitReset,
metrics.RateLimitUsed,
)
// Setup a fake HTTP server
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("X-RateLimit-Reset", strconv.FormatInt(time.Now().Unix()+1, 10))
w.Header().Set("X-RateLimit-Remaining", "42")
w.Header().Set("X-RateLimit-Limit", "100")
w.Header().Set("X-RateLimit-Used", "58")
w.Header().Set("X-RateLimit-Resource", "core")
w.WriteHeader(http.StatusCreated)
_, _ = w.Write([]byte("ok"))
}))
defer ts.Close()
metricsCtx := &MetricsContext{AppSetNamespace: appsetNamespace, AppSetName: appsetName}
client := &http.Client{
Transport: NewGitHubMetricsTransport(
http.DefaultTransport,
metricsCtx,
metrics,
),
}
ctx := t.Context()
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL+URL, http.NoBody)
resp, err := client.Do(req)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
resp.Body.Close()
// Expose and scrape metrics
handler := promhttp.HandlerFor(reg, promhttp.HandlerOpts{})
server := httptest.NewServer(handler)
defer server.Close()
req, err = http.NewRequestWithContext(ctx, http.MethodGet, server.URL, http.NoBody)
if err != nil {
t.Fatalf("failed to create request: %v", err)
}
resp, err = http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("failed to scrape metrics: %v", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
metricsOutput := string(body)
sort.Strings(successRequestMetrics.labels)
assert.Contains(t, metricsOutput, successRequestMetrics.name+"{"+strings.Join(successRequestMetrics.labels, ",")+"} "+successRequestMetrics.value)
for _, metric := range rateLimitMetrics {
sort.Strings(metric.labels)
assert.Contains(t, metricsOutput, metric.name+"{"+strings.Join(metric.labels, ",")+"} "+metric.value)
}
}
type RoundTripperFunc func(*http.Request) (*http.Response, error)
func (f RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
func TestGitHubMetrics_CollectorApproach_NoRateLimitMetricsOnNilResponse(t *testing.T) {
metrics := NewGitHubMetrics()
reg := prometheus.NewRegistry()
reg.MustRegister(
metrics.RequestTotal,
metrics.RequestDuration,
metrics.RateLimitRemaining,
metrics.RateLimitLimit,
metrics.RateLimitReset,
metrics.RateLimitUsed,
)
client := &http.Client{
Transport: &GitHubMetricsTransport{
transport: RoundTripperFunc(func(*http.Request) (*http.Response, error) {
return nil, http.ErrServerClosed
}),
metricsContext: &MetricsContext{AppSetNamespace: appsetNamespace, AppSetName: appsetName},
metrics: metrics,
},
}
ctx := t.Context()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, URL, http.NoBody)
if err != nil {
t.Fatalf("failed to create request: %v", err)
}
_, _ = client.Do(req)
handler := promhttp.HandlerFor(reg, promhttp.HandlerOpts{})
server := httptest.NewServer(handler)
defer server.Close()
req, err = http.NewRequestWithContext(ctx, http.MethodGet, server.URL, http.NoBody)
if err != nil {
t.Fatalf("failed to create request: %v", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("failed to scrape metrics: %v", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
metricsOutput := string(body)
// Verify request metric exists with status "0"
sort.Strings(failureRequestMetrics.labels)
assert.Contains(t, metricsOutput, failureRequestMetrics.name+"{"+strings.Join(failureRequestMetrics.labels, ",")+"} "+failureRequestMetrics.value)
// Verify rate limit metrics don't exist
for _, metric := range rateLimitMetrics {
sort.Strings(metric.labels)
assert.NotContains(t, metricsOutput, metric.name+"{"+strings.Join(metric.labels, ",")+"} "+metric.value)
}
}
func TestNewGitHubMetricsClient(t *testing.T) {
// Test cases
testCases := []struct {
name string
metricsCtx *MetricsContext
}{
{
name: "with metrics context",
metricsCtx: &MetricsContext{
AppSetNamespace: appsetNamespace,
AppSetName: appsetName,
},
},
{
name: "with nil metrics context",
metricsCtx: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Create client
client := NewGitHubMetricsClient(tc.metricsCtx)
// Assert client is not nil
assert.NotNil(t, client)
// Assert transport is properly configured
transport, ok := client.Transport.(*GitHubMetricsTransport)
assert.True(t, ok, "Transport should be GitHubMetricsTransport")
// Verify transport configuration
assert.Equal(t, tc.metricsCtx, transport.metricsContext)
assert.NotNil(t, transport.metrics, "Metrics should not be nil")
assert.Equal(t, http.DefaultTransport, transport.transport, "Base transport should be http.DefaultTransport")
// Verify metrics are global metrics
assert.Equal(t, globalGitHubMetrics, transport.metrics, "Should use global metrics")
})
}
}

View File

@@ -5,27 +5,14 @@ import (
"net/http"
"github.com/bradleyfalzon/ghinstallation/v2"
"github.com/google/go-github/v69/github"
"github.com/google/go-github/v35/github"
"github.com/argoproj/argo-cd/v3/applicationset/services/github_app_auth"
appsetutils "github.com/argoproj/argo-cd/v3/applicationset/utils"
"github.com/argoproj/argo-cd/v2/applicationset/services/github_app_auth"
)
func getOptionalHTTPClientAndTransport(optionalHTTPClient ...*http.Client) (*http.Client, http.RoundTripper) {
httpClient := appsetutils.GetOptionalHTTPClient(optionalHTTPClient...)
if len(optionalHTTPClient) > 0 && optionalHTTPClient[0] != nil && optionalHTTPClient[0].Transport != nil {
// will either use the provided custom httpClient and it's transport
return httpClient, optionalHTTPClient[0].Transport
}
// or the default httpClient and transport
return httpClient, http.DefaultTransport
}
// Client builds a github client for the given app authentication.
func Client(g github_app_auth.Authentication, url string, optionalHTTPClient ...*http.Client) (*github.Client, error) {
httpClient, transport := getOptionalHTTPClientAndTransport(optionalHTTPClient...)
rt, err := ghinstallation.New(transport, g.Id, g.InstallationId, []byte(g.PrivateKey))
func Client(g github_app_auth.Authentication, url string) (*github.Client, error) {
rt, err := ghinstallation.New(http.DefaultTransport, g.Id, g.InstallationId, []byte(g.PrivateKey))
if err != nil {
return nil, fmt.Errorf("failed to create github app install: %w", err)
}
@@ -33,12 +20,13 @@ func Client(g github_app_auth.Authentication, url string, optionalHTTPClient ...
url = g.EnterpriseBaseURL
}
var client *github.Client
httpClient.Transport = rt
if url == "" {
client = github.NewClient(httpClient)
httpClient := http.Client{Transport: rt}
client = github.NewClient(&httpClient)
} else {
rt.BaseURL = url
client, err = github.NewClient(httpClient).WithEnterpriseURLs(url, url)
httpClient := http.Client{Transport: rt}
client, err = github.NewEnterpriseClient(url, url, &httpClient)
if err != nil {
return nil, fmt.Errorf("failed to create github enterprise client: %w", err)
}

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