Compare commits

..

103 Commits

Author SHA1 Message Date
github-actions[bot]
794b2e050d Bump version to 2.10.14 (#19062)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2024-07-15 13:35:23 -04:00
Andy Goldstein
d8ddce87be chore: update gitops-engine (#19054)
This update includes critical fixes that resolve an issue where
cluster-scoped resources could have been accidentally deleted during an
application sync if pruning is enabled.

Signed-off-by: Andy Goldstein <andy.goldstein@gmail.com>
2024-07-15 11:17:03 -04:00
gcp-cherry-pick-bot[bot]
7e99a1340e fix(cli): Get Redis password from secret in loadClusters() (#18951) (#18957)
* Get Redis password from secret in `loadClusters()`



* feat: support redis password in admin stats command



* Simplify code



---------

Signed-off-by: David Wu <155603967+david-wu-octopus@users.noreply.github.com>
Signed-off-by: pashakostohrys <pavel@codefresh.io>
Co-authored-by: david-wu-octopus <155603967+david-wu-octopus@users.noreply.github.com>
Co-authored-by: pashakostohrys <pavel@codefresh.io>
2024-07-05 11:12:49 -04:00
github-actions[bot]
63a30293fe Bump version to 2.10.13 (#18895)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2024-07-02 13:39:12 -04:00
Michael Crenshaw
2fbb69b892 fix: update static schemas (#18890)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-02 12:51:07 -04:00
Michael Crenshaw
9c711337e7 fix(controller): bad server-side diffs (#18213) (2.10) (#18869)
* fix(controller): bad server-side diffs (#18213) (2.10)

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

* fix revision

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

* hopefully the right hash now

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

---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-01 14:32:24 -04:00
gcp-cherry-pick-bot[bot]
2c2064be7b docs: Fix .path to .path.segments go template (#18872) (#18875)
Signed-off-by: Jaeseok Lee <devsunb@gmail.com>
Co-authored-by: Jaeseok Lee <devsunb@gmail.com>
2024-07-01 10:54:18 -04:00
gcp-cherry-pick-bot[bot]
9d8148bc61 fix: Update braces package to 3.0.3 (#18459) (#18664)
Signed-off-by: Keith Chong <kykchong@redhat.com>
Co-authored-by: Keith Chong <kykchong@redhat.com>
2024-06-14 09:18:30 -04:00
gcp-cherry-pick-bot[bot]
9ba6943111 test: fix e2e tests after GHSA-3cqf-953p-h5cp (#18543) (#18553)
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-06-07 11:50:21 -04:00
Justin Marquis
c79714d660 chore: bump go version to 1.21.10 (#18541)
Signed-off-by: Justin Marquis <justin@akuity.io>
2024-06-07 08:49:20 -07:00
github-actions[bot]
cb6f5ac8b8 Bump version to 2.10.12 (#18521)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: pasha-codefresh <39732895+pasha-codefresh@users.noreply.github.com>
2024-06-06 11:44:25 +03:00
pasha-codefresh
ec35043a64 Merge pull request from GHSA-3cqf-953p-h5cp
* fix: prevent enumerating by cluster name, return exact error for case when cluster exists and not

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

* fix: prevent cluster enumeration by name

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

* fix: prevent cluster enumeration by name

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

* fix linter and add unit test

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

* fix linter and add unit test

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

---------

Signed-off-by: pashakostohrys <pavel@codefresh.io>
2024-06-06 11:30:10 +03:00
Blake Pettersson
531123b70c Merge pull request from GHSA-87p9-x75h-p4j2
Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
2024-06-06 11:25:55 +03:00
github-actions[bot]
27e49f8b78 Bump version to 2.10.11 (#18385)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: pasha-codefresh <39732895+pasha-codefresh@users.noreply.github.com>
2024-05-23 16:33:02 +03:00
pasha-codefresh
82ae349929 fix: remove Egress NetworkPolicy for argocd-redis and argocd-redis-ha-haproxy - 2.10 (#18381)
Signed-off-by: pashakostohrys <pavel@codefresh.io>
2024-05-23 16:03:33 +03:00
Michael Crenshaw
f61f47264f fix: revert registry change (#18328) (#18336)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-05-21 17:37:42 -04:00
gcp-cherry-pick-bot[bot]
0a179fb98e fix(deps): upgrade otel dependency (#18285) (#18325)
Signed-off-by: Justin Marquis <justin@akuity.io>
Co-authored-by: Justin Marquis <76892343+34fathombelow@users.noreply.github.com>
Co-authored-by: Soumya Ghosh Dastidar <44349253+gdsoumya@users.noreply.github.com>
2024-05-21 10:47:58 -07:00
gcp-cherry-pick-bot[bot]
a960c6be07 chore(ci): fix release notes (#18132) (#18331)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-05-21 12:53:25 -04:00
github-actions[bot]
0895ebc135 Bump version to 2.10.10 (#18320)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: pasha-codefresh <39732895+pasha-codefresh@users.noreply.github.com>
2024-05-21 16:56:30 +03:00
Leonardo Luz Almeida
2de0ceade2 Merge pull request from GHSA-9766-5277-j5hr
* fix: Enable Redis authentication in the default installation

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

* chore: fix git_test unit test

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

---------

Signed-off-by: May Zhang <may_zhang@intuit.com>
Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>
Co-authored-by: May Zhang <may_zhang@intuit.com>
2024-05-21 16:23:09 +03:00
Leonardo Luz Almeida
bdd889d439 Merge pull request from GHSA-9766-5277-j5hr
* fix: Enable Redis authentication in the default installation

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

* chore: fix git_test unit test

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

---------

Signed-off-by: May Zhang <may_zhang@intuit.com>
Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>
Co-authored-by: May Zhang <may_zhang@intuit.com>
2024-05-21 16:22:43 +03:00
Ishita Sequeira
d58c96b456 chore(deps): cherry-pick bump protobuf (#18293)
Signed-off-by: ishitasequeira <ishiseq29@gmail.com>
2024-05-20 17:20:18 +03:00
gcp-cherry-pick-bot[bot]
5425568bd1 fix: copy visited map #11699 (#12667) (#18220)
This commit fixed an issue #11699 that caused a warning even if the cycle didn't exist.
Fix false cycle discovery by copying the visited resource map before recursively calling of getAppRecursive.

Fixes #11699

Signed-off-by: Arata Furukawa <old.river.new@gmail.com>
Co-authored-by: Arata Furukawa <old.river.new@gmail.com>
Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
2024-05-20 13:28:59 +03:00
gcp-cherry-pick-bot[bot]
320ced67b8 chore(deps): upgrade helm to 3.14.4 (#18255) (#18287)
* chore(deps): upgrade helm to 3.14.4



* place checksums where they belong



---------

Signed-off-by: Justin Marquis <justin@akuity.io>
Co-authored-by: Justin Marquis <76892343+34fathombelow@users.noreply.github.com>
Co-authored-by: Dan Garfield <dan@codefresh.io>
2024-05-20 11:13:00 +03:00
pasha-codefresh
f2d31330ff chore: update gitops engine for force sync option (#5882) - 2.10 (#18123)
Signed-off-by: pashakostohrys <pavel@codefresh.io>
2024-05-08 19:24:11 +03:00
gcp-cherry-pick-bot[bot]
fa7f330ab3 docs: fix 404 styling (#18094) (#18105)
* docs: fix 404 styling



* hack around custom tag destruction



---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-05-07 09:07:49 -04:00
Jayendra Parsai
1466755aeb chore: bump go-jose from 3.0.1 to 3.0.3 (#18102)
Signed-off-by: Jayendra Parsai <jparsai@jparsai-thinkpadp1gen4i.remote.csb>
Co-authored-by: Jayendra Parsai <jparsai@jparsai-thinkpadp1gen4i.remote.csb>
2024-05-07 08:48:14 -04:00
Alexander Matyushentsev
8d267c0136 fix: status.sync.comparedTo should use replace patch strategy (#18061) (#18075)
* fix: status.sync.comparedTo should use replace patch strategy



* add e2e tests



---------

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2024-05-05 11:53:51 -07:00
github-actions[bot]
c071af8081 Bump version to 2.10.9 (#18033)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: jannfis <3942683+jannfis@users.noreply.github.com>
2024-04-30 11:46:51 -04:00
gcp-cherry-pick-bot[bot]
04785a4861 fix: enable sha256 and sha512 for git ssh (#18028) (#18029)
* fix: bumping the knownhosts to v1.2.2 since this contains a fix that allows for sha256 and sha512 algorithms when using git ssh




* chore: remove older version of module from go sum



---------

Signed-off-by: Marc Arndt <marc@marcarndt.com>
Signed-off-by: Marc Arndt <m.arndt@evana.de>
Co-authored-by: Marc Arndt <marc@marcarndt.com>
Co-authored-by: Marc Arndt <m.arndt@evana.de>
2024-04-30 10:57:41 -04:00
github-actions[bot]
37b1cf5306 Bump version to 2.10.8 (#17990)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: pasha-codefresh <39732895+pasha-codefresh@users.noreply.github.com>
2024-04-26 16:17:41 +03:00
pasha-codefresh
15865b9a04 fix: codegen after security fix - 2.10 (#17985)
* fix: codegen after security fix

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

* fix: codegen after security fix

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

---------

Signed-off-by: pashakostohrys <pavel@codefresh.io>
2024-04-26 16:09:59 +03:00
pasha-codefresh
47a35c1a11 Merge pull request from GHSA-9m6p-x4h2-6frq
* feat: limit jq.Run with timeout

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

* feat: ignore normalizer jq execution timeout as env variable

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

* feat: customize error message and add doc section

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

* feat: improve log and change a way how to get variable

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

* chore: fix import`s order

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

* chore: rename variable inside sts

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

* chore: fix import order

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

* chore: fix import`s order

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

---------

Signed-off-by: pashakostohrys <pavel@codefresh.io>
2024-04-26 12:24:32 +03:00
pasha-codefresh
9e5cc5a26f Merge pull request from GHSA-9m6p-x4h2-6frq
* feat: limit jq.Run with timeout

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

* feat: ignore normalizer jq execution timeout as env variable

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

* feat: customize error message and add doc section

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

* feat: improve log and change a way how to get variable

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

* chore: fix import`s order

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

* chore: rename variable inside sts

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

* chore: fix import order

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

* chore: fix import`s order

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

---------

Signed-off-by: pashakostohrys <pavel@codefresh.io>
2024-04-26 12:24:02 +03:00
Tais P. Hansen
19addbd9bb chore: upgrade redis to 7.0.15 (#17666)
Upgrade to latest stable 7.0.x version to fix CVEs:

CVE-2023-41056

Signed-off-by: Tais P. Hansen <taishansen@gmail.com>
2024-04-16 11:49:39 +03:00
pasha-codefresh
744df40552 fix: codegen and e2e tests in release-2.10 (#17844)
* fix: codegen and e2e tests

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

* fix: codegen and e2e tests

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

---------

Signed-off-by: pashakostohrys <pavel@codefresh.io>
2024-04-15 17:14:50 +03:00
pashakostohrys
b060053b09 fix: docker build fails due to "The repository 'http://deb.debian.org/debian buster-backports Release' does not have a Release file."
Signed-off-by: pashakostohrys <pavel@codefresh.io>
2024-04-15 11:42:52 +03:00
github-actions[bot]
696ca0a57f Bump version to 2.10.7 (#17831)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: pasha-codefresh <pasha-codefresh@users.noreply.github.com>
2024-04-15 10:28:57 +03:00
pasha-codefresh
c514105af7 Merge pull request from GHSA-2gvw-w6fj-7m3c
Signed-off-by: pashakostohrys <pavel@codefresh.io>
2024-04-15 10:20:07 +03:00
github-actions[bot]
d504d2b1d9 Bump version to 2.10.6 (#17744)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: alexmt <alexmt@users.noreply.github.com>
2024-04-04 17:26:33 -07:00
gcp-cherry-pick-bot[bot]
5814864d6c fix(security): use Chainguard fork of git-urls (#17732) (#17735)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-04-04 16:47:25 -04:00
Alexander Matyushentsev
da65596511 fix: fix calculating patch for respect ignore diff feature (#17693)
* test: unit test for respectIgnoreDifferences bug

Signed-off-by: Jesse Suen <jesse@akuity.io>

* test: simplify unit test

Signed-off-by: Jesse Suen <jesse@akuity.io>

* fix: fix calculating patch for respect ignore diff feature

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

---------

Signed-off-by: Jesse Suen <jesse@akuity.io>
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Co-authored-by: Jesse Suen <jesse@akuity.io>
2024-04-04 11:25:44 -07:00
gcp-cherry-pick-bot[bot]
73be9c4c2c fix(ui): Fix color generation for pod name in logs viewer. Fixes #17704 (#17706) (#17710)
* Fix color generation for pod name in logs viewer



* Add rebuy to users.md



---------

Signed-off-by: Philipp Trulson <der-eismann@users.noreply.github.com>
Co-authored-by: Philipp Trulson <der-eismann@users.noreply.github.com>
2024-04-02 16:12:34 -04:00
gcp-cherry-pick-bot[bot]
d124f1603e chore(deps): bump webpack-dev-middleware from 5.3.1 to 5.3.4 in /ui (#17598) (#17686)
Bumps [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) from 5.3.1 to 5.3.4.
- [Release notes](https://github.com/webpack/webpack-dev-middleware/releases)
- [Changelog](https://github.com/webpack/webpack-dev-middleware/blob/v5.3.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-middleware/compare/v5.3.1...v5.3.4)

---
updated-dependencies:
- dependency-name: webpack-dev-middleware
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 10:24:11 -04:00
Justin Marquis
335875d13e fix cosign (#17656)
Signed-off-by: Justin Marquis <justin@akuity.io>
2024-03-28 16:59:22 +02:00
github-actions[bot]
4192e3f3ac Bump version to 2.10.5 (#17654)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: pasha-codefresh <pasha-codefresh@users.noreply.github.com>
2024-03-28 14:55:45 +02:00
pasha-codefresh
3e5a878f6e Merge pull request from GHSA-jhwx-mhww-rgc3
* sec: limit helm index max size

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

* sec: limit helm index max size

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

* feat: fix tests and linter

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

---------

Signed-off-by: pashakostohrys <pavel@codefresh.io>
2024-03-28 14:38:03 +02:00
gcp-cherry-pick-bot[bot]
47d586169f fix: elements should be optional (#17424) (#17510)
A bug was reported, where an applicationset with an empty elements
array, when created with `argocd appset create <filename>.yaml` gets a
`...list.elements: Required value` error.

My hypothesis is that when calling the K8s API, golang JSON marshalling
mangles the empty `elements` array to `nil`, rather than creating an
empty array when submitting the `POST`.

Still need to figure out why the same setup seemingly works fine when
the same appset is in an app-of-apps.

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
2024-03-28 00:38:47 -04:00
github-actions[bot]
f5d63a5c77 Bump version to 2.10.4 (#17557)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2024-03-18 04:03:42 -04:00
Dan Garfield
ce04dc5c6f Merge pull request from GHSA-6v85-wr92-q4p7
* fix: Fix concurrency issue in session manager

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

* Add note that modification to the map must be done in a thread safe manner

* chore: fix linter issues

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

---------

Signed-off-by: jannfis <jann@mistrust.net>
Signed-off-by: pashakostohrys <pavel@codefresh.io>
Co-authored-by: jannfis <jann@mistrust.net>
Co-authored-by: pashakostohrys <pavel@codefresh.io>
2024-03-18 03:59:06 -04:00
pasha-codefresh
cebb6538f7 Merge pull request from GHSA-2vgg-9h6w-m454
* feat: pick random user and exclude admin user and current user from deletion candidates

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

* feat: increase default max cache size

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

* add nil protection

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

* Update util/session/sessionmanager.go

Signed-off-by: Dan Garfield <dan@codefresh.io>

Signed-off-by: Dan Garfield <dan@codefresh.io>

* chore: fix linter issues

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

---------

Signed-off-by: pashakostohrys <pavel@codefresh.io>
Signed-off-by: Dan Garfield <dan@codefresh.io>
Co-authored-by: Dan Garfield <dan@codefresh.io>
2024-03-18 03:58:18 -04:00
gcp-cherry-pick-bot[bot]
ab7e45da13 fix(notifications): Helm.GetParameterValueByName should take helm.parametes first (#17472) (#17512)
* fix: Helm.GetParameterValueByName should take helm.parametes first



* fix linters



---------

Signed-off-by: pashakostohrys <pavel@codefresh.io>
Co-authored-by: pasha-codefresh <pavel@codefresh.io>
2024-03-16 20:52:45 +02:00
gcp-cherry-pick-bot[bot]
a8ae929d55 fix: registry argument to be only the host instead full URL (#17381) (#17534)
Signed-off-by: Pablo Aguilar <pablo.aguilar@outlook.com.br>
Co-authored-by: Pablo Aguilar <pablo.aguilar@outlook.com.br>
2024-03-15 17:32:04 -04:00
gcp-cherry-pick-bot[bot]
f3fdaa7eab fix(ui): add confirmation box in resource summary delete action (#17485) (#17545)
Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>
Co-authored-by: AS <11219262+ashutosh16@users.noreply.github.com>
2024-03-15 12:06:55 -04:00
gcp-cherry-pick-bot[bot]
0b4659c046 chore(deps): upgrade helm to 3.14.3 (#17531) (#17533)
* chore(deps): upgrade helm to 3.14.3



* chore(deps): upgrade helm to 3.14.3



---------

Signed-off-by: David Bunn <david.bunn@prodigygame.com>
Co-authored-by: David Bunn <david.bunn@prodigygame.com>
2024-03-14 14:04:55 -04:00
github-actions[bot]
0fd6344537 Bump version to 2.10.3 (#17513)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2024-03-13 14:46:18 -04:00
Michael Crenshaw
0977f61554 Merge pull request from GHSA-g623-jcgg-mhmm
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-03-13 14:28:43 -04:00
Alexandre Gaudreault
3dd069b049 Merge pull request from GHSA-jwv5-8mqv-g387
* fix: Validate external URLs for applicatins

Signed-off-by: Ry0taK <49341894+Ry0taK@users.noreply.github.com>

* fix(ui): remove invalid external-link

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

* linting

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

---------

Signed-off-by: Ry0taK <49341894+Ry0taK@users.noreply.github.com>
Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
Co-authored-by: Ry0taK <49341894+Ry0taK@users.noreply.github.com>
2024-03-13 14:26:47 -04:00
gcp-cherry-pick-bot[bot]
37da5e2ae5 fix: multi-source app breaks application parameters UI (#16910) (#17033) (#17332)
Signed-off-by: Keith Chong <kykchong@redhat.com>
Co-authored-by: Keith Chong <kykchong@redhat.com>
2024-03-04 18:22:56 -05:00
gcp-cherry-pick-bot[bot]
12886657ac fix!: disable rate limiting completely by default (#17355) (#17393)
Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>
Co-authored-by: Soumya Ghosh Dastidar <44349253+gdsoumya@users.noreply.github.com>
2024-03-04 11:51:15 -05:00
gcp-cherry-pick-bot[bot]
fcf5d8c238 chore(ci): free up disk space for goreleaser (#17373) (#17374)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-03-01 16:09:55 -05:00
github-actions[bot]
1ee3c80bc8 Bump version to 2.10.2 (#17370)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2024-03-01 13:35:54 -05:00
gcp-cherry-pick-bot[bot]
a79fcad0e9 fix(ui): The tiles in Applications List are too wide #17220 (#17340) (#17351)
* fix(ui): The tiles in Applications List are too wide



* change min width of app tiles



---------

Signed-off-by: Rafal Pelczar <rafal@akuity.io>
Co-authored-by: Rafal <rafal@akuity.io>
2024-02-28 12:38:47 -08:00
gcp-cherry-pick-bot[bot]
67e57a47a2 fix: The argocd server api-content-type flag does not allow empty content-type header (#17331) (#17346)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Co-authored-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2024-02-28 10:15:28 -08:00
gcp-cherry-pick-bot[bot]
d99ee9d28b chore(deps): upgrade helm to 3.14.2 (#17330) (#17333)
* chore(deps): upgrade helm to 3.14.2



* Signing commit



---------

Signed-off-by: David Bunn <david.bunn@prodigygame.com>
Co-authored-by: David Bunn <debunn@yahoo.com>
2024-02-27 21:52:51 -05:00
gcp-cherry-pick-bot[bot]
28a9225e7b Corrected certificate managment for OCI helm charts (#16656) (#17320)
Signed-off-by: Andrew Block <andy.block@gmail.com>
Co-authored-by: Andrew Block <andy.block@gmail.com>
Co-authored-by: Soumya Ghosh Dastidar <44349253+gdsoumya@users.noreply.github.com>
2024-02-27 16:37:31 -05:00
gcp-cherry-pick-bot[bot]
f5d6b2972b chore(deps): upgrade helm to 3.14.1 (#17213) (#17216)
* chore(deps): upgrade helm to 3.14.1



* move files to folder......



---------

Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
Co-authored-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
2024-02-19 14:52:17 +02:00
Carlos Santana
06e2e0da9a chore(deps): Upgrade aws-sdk-go to support eks pod identity (#17063) (#17225)
* chore: Upgrade aws-sdk-go to support eks pod identity

---------




* add cogen for notifications



---------

Signed-off-by: Carlos Santana <carrlos@amazon.com>
Co-authored-by: Mathieu Bruneau <brunemat@amazon.com>
2024-02-16 00:18:54 -05:00
github-actions[bot]
a79e0eaca4 Bump version to 2.10.1 (#17211)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2024-02-14 12:36:58 -05:00
gcp-cherry-pick-bot[bot]
65461a1b61 fix(controller): add missing workqueue metrics (#16315) (#17013) (#17209)
* fix(controller): add missing kubernetes metrics



* validate workqueue metrics are present



* use newer metrics registry



* fix duplicated



* init runtime controller in test to have correct metrics



* fix lint error



* update controller-runtime to remove metrics with high cardinality



---------

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>
Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
Co-authored-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
2024-02-14 12:36:43 -05:00
Ishita Sequeira
2268f08819 fix: infer correct shard in statefulset setup (#17124, #17016) (#17167) (#17204)
* fix: infer correct shard in statefulset setup (#17124, #17016)  (#17167)

* fix: infer correct shard in statefulset setup

Signed-off-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>

* fix the case if only a single replica

Signed-off-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>

* fix: resolving pointer on shard compare

Signed-off-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>

* fix: add readlock for cluster accessor

Signed-off-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>

* fix: use defer to protect access of 'shard'

Signed-off-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>

* fix: revert locking in getclusteraccessor

Signed-off-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>

* fix: handle nil shard case

Signed-off-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>

* fix: handle any nil shard value as false

Signed-off-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>

* fix: handle nil case and fix another missing pointer dereference

Signed-off-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>

* revert

Signed-off-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>

* fix: added tests and fixed some behaviour bugs

Signed-off-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>

* test: add test to validate that Shard value is not overriden

Signed-off-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>

* fix: added tests and fixe the case when server is changed inside a secret

Signed-off-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>

* tests: add test cases for infering the shard logic

Signed-off-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>

---------

Signed-off-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>
(cherry picked from commit 5d6111b745)

* fix lint issues

Signed-off-by: ishitasequeira <ishiseq29@gmail.com>

---------

Signed-off-by: ishitasequeira <ishiseq29@gmail.com>
Co-authored-by: Lukas Wöhrl <lukas.woehrl@plentymarkets.com>
2024-02-13 14:28:30 -05:00
gcp-cherry-pick-bot[bot]
a1a5c58a7d fix: stop initializing deployment informer if dynamic sharding is disabled (#17097) (#17202)
* fix: stop initializing deployment informer if dynamic sharding is disabled



* feat: updated sharding cache getter func



---------

Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>
Co-authored-by: Soumya Ghosh Dastidar <44349253+gdsoumya@users.noreply.github.com>
2024-02-13 12:05:56 -05:00
gcp-cherry-pick-bot[bot]
9c379af169 fix(kustomize): set build dir (#15057) #16229 #16652 (#16653) (#17129)
* use repo root, not app path



correct patch



* use Getwd to find the root path for diff commands



* set dot a default for argo app commands



* revert default values



* patch diff in TestNamespacedResourceDiffing



* patching some diff and sync



* patch remaining diff in error



---------

Signed-off-by: Prune <prune@lecentre.net>
Co-authored-by: Prune Sebastien THOMAS <prune@lecentre.net>
2024-02-07 14:01:03 -05:00
gcp-cherry-pick-bot[bot]
4e01115a48 fix(manifests): applicationset-controller dir is not added to cluster-rbac/kustomization.yaml. (#16810) (#17120)
* fix(manifests): applicationset-controller dir is not added to cluster-rbac/kustomization.yaml.

Related PR: https://github.com/argoproj/argo-cd/pull/16699.

I missed adding a new folder(applicationset-controller) to kustomization.yaml.

So, i addressed it.



* chore: exec `make manifests`.



* chore: exec `make manifests`.



---------

Signed-off-by: mugioka <okamugi0722@gmail.com>
Co-authored-by: mugi <62197019+mugioka@users.noreply.github.com>
2024-02-07 09:31:20 -05:00
gcp-cherry-pick-bot[bot]
eddf0a5f30 chore(manifests): add ClsuterRole/ClusterRoleBinding for applicationset controller. (#16699) (#17119)
Closes https://github.com/argoproj/argo-cd/issues/16698.

Signed-off-by: mugioka <okamugi0722@gmail.com>
Co-authored-by: mugi <62197019+mugioka@users.noreply.github.com>
2024-02-07 09:23:14 -05:00
gcp-cherry-pick-bot[bot]
2175939ed6 fix(ci): correct helm checksum path (#17081) (#17083)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-02-06 09:26:59 -05:00
github-actions[bot]
1c959b7b0d Bump version to 2.10.0 (#17094)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2024-02-05 18:53:42 -05:00
gcp-cherry-pick-bot[bot]
dfba4098d1 fix(controller): fix application controller deployment crashing (#16984) (#17093)
* fix application controller deployment crashing and update manifests



* remove environment variable ARGOCD_ENABLE_DYNAMIC_CLUSTER_DISTRIBUTION



* fix auto-generated docs



---------

Signed-off-by: ishitasequeira <ishiseq29@gmail.com>
Co-authored-by: Ishita Sequeira <46771830+ishitasequeira@users.noreply.github.com>
2024-02-05 16:40:13 -05:00
github-actions[bot]
1835210574 Bump version to 2.10.0-rc5 (#17077)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2024-02-02 13:11:48 -05:00
gcp-cherry-pick-bot[bot]
b78befe441 chore(deps): bump Helm to 3.14.0 (#17031) (#17032) (#17061)
* bump helm to 3.14.0



* Add a note about helm bump in upgrade instructions



---------

Signed-off-by: Simon HEGE <simonhege@gmail.com>
Co-authored-by: Simon HEGE <simonhege@gmail.com>
2024-01-31 15:26:54 -05:00
gcp-cherry-pick-bot[bot]
e932556758 docs(hooks): add postdelete to table (#17048) (#17051)
Add `PostDelete` to the hooks table, and clean up wording and old availability statement (all the way back to v1, probably irrelevant).

Signed-off-by: Nicholas Morey <nicholas@morey.tech>
Co-authored-by: Nicholas Morey <nicholas@morey.tech>
2024-01-30 20:52:15 -05:00
gcp-cherry-pick-bot[bot]
28c29380d4 docs(kustomize): add components yaml example (#17043) (#17046)
Signed-off-by: Nicholas Morey <nicholas@morey.tech>
Co-authored-by: Nicholas Morey <nicholas@morey.tech>
2024-01-30 14:08:49 -05:00
gcp-cherry-pick-bot[bot]
18f82913b6 docs(argocd-cm): add timeout.reconciliation.jitter example (#17044) (#17047)
Signed-off-by: Nicholas Morey <nicholas@morey.tech>
Co-authored-by: Nicholas Morey <nicholas@morey.tech>
2024-01-30 14:08:09 -05:00
gcp-cherry-pick-bot[bot]
d9ece9295e docs(applicationset): explain impact of empty spec in templatePatch (#17042) (#17045)
* docs: explain impact of empty spec in templatePatch



* fix: not conditional helm values



---------

Signed-off-by: Nicholas Morey <nicholas@morey.tech>
Co-authored-by: Nicholas Morey <nicholas@morey.tech>
2024-01-30 14:07:31 -05:00
gcp-cherry-pick-bot[bot]
0c62f6d6b9 fix(redis): go-redis v9 regression missing metrics and reconnect hook (#13415) (#15275) (#17025)
* fix(redis): go-redis v9 regression missing metrics and reconnect hook



* fix: golangci lint return values not checked in tests



* chore: move dnsError var locally into func



---------

Signed-off-by: phanama <yudiandreanp@gmail.com>
Co-authored-by: Yudi A Phanama <11147376+phanama@users.noreply.github.com>
2024-01-30 09:45:00 -05:00
gcp-cherry-pick-bot[bot]
14b8762684 fix(ui): Badge for apps in any namespace (#16739) (#17007)
Signed-off-by: sshenoy6 <sonamkaup_shenoy@intuit.com>
Co-authored-by: Sonam <49382298+sonamkshenoy@users.noreply.github.com>
Co-authored-by: sshenoy6 <sonamkaup_shenoy@intuit.com>
2024-01-26 15:03:48 -05:00
github-actions[bot]
c6469aef7e Bump version to 2.10.0-rc4 (#16993)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2024-01-25 16:13:51 -05:00
Michael Crenshaw
a848105a6d fix(server): allow disabling content-type check (#16959) (#16976)
* fix(server): allow disabling content-type check



* fix spacing



---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-01-24 18:07:38 -05:00
gcp-cherry-pick-bot[bot]
b657e97448 chore(deps): rm go-jose Cxb6dee8d5-b814 high vuln (#16947) (#16970)
Signed-off-by: fengshunli <1171313930@qq.com>
Co-authored-by: fsl <1171313930@qq.com>
2024-01-24 09:43:22 -05:00
gcp-cherry-pick-bot[bot]
00fae11d99 fix(appcontroller): Uptake fix in gitops engine which fixes application sync with auto create ns and server side apply (#16942) (#16963)
* Uptake fix in gitops engine to fix auto create ns with server side apply



* Moved the new e2e test to different location



* Fix test name to be less than 63 char for creating ns



* update gitops-engine with latest master



---------

Signed-off-by: anandf <anjoseph@redhat.com>
Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>
Co-authored-by: Anand Francis Joseph <anjoseph@redhat.com>
Co-authored-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>
2024-01-23 16:41:01 -05:00
gcp-cherry-pick-bot[bot]
c3d125f616 feat(controller): add sync jitter(#14241) (#16820) (#16960)
* feat(controller): add sync jitter



* convert to duration for simplicity



* docs



* add config to manifests



* fix tests



---------

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>
Co-authored-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>
2024-01-23 11:15:26 -05:00
github-actions[bot]
1041086231 Bump version to 2.10.0-rc3 (#16936)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2024-01-19 12:20:23 -05:00
gcp-cherry-pick-bot[bot]
da10276c8d fix(ui): set content-type for certain UI requests (#16923) (#16930) (#16932)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-01-19 11:50:21 -05:00
github-actions[bot]
4d0f940e04 Bump version to 2.10.0-rc2 (#16916)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2024-01-18 15:37:38 -05:00
Michael Crenshaw
8b865d7e30 chore(deps): bump github.com/go-git/go-git/v5 from 5.8.1 to 5.11.0 (#16911)
* chore(deps): bump github.com/go-git/go-git/v5 from 5.8.1 to 5.11.0

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

* tidy

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

---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-01-18 10:56:25 -05:00
gcp-cherry-pick-bot[bot]
a6d8c924ee fix: Use the cache for sharding (#15237) (#16902)
* feat(sharding): use a cache



* cluster cmd



* - Assign shard 0 to in-cluster cluster and nil check updates
- Caching clusters while sharding: Fixing unit tests
- Update generated docs
- Debug e2e tests
- Default the shardNumber to the number of replicas if it is calculated to a higher value
- defered Unlock only when a lock is set
- Disabling temporarly other versions of k3s to check if e2e passes
- Do not fail if hostname format is not abc-n
- Fix unit test and skip some e2e
- Skip TestGitSubmoduleHTTPSSupport test
- Remove breaking defer c.lock.Unlock()
- Reverting testing all k3s version
- Default sharding fix




* fixes related to code review: renaming structure param, moving db initialisation



* Code review



* Set default shard to 0



* Set different default value for Sts and Deployment mode



* Expose ClusterShardingCache



* Removing use of argoDB.db for DistributionFunction



* Update generated documentation



* Fix comment about NoShardingDistributionFunction and NoShardingAlgorithm



---------

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>
Signed-off-by: Akram Ben Aissi <akram.benaissi@gmail.com>
Co-authored-by: Akram Ben Aissi <akram.benaissi@gmail.com>
Co-authored-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>
2024-01-17 16:28:07 -05:00
Alexander Matyushentsev
3c5878ecf4 fix: enforce content type header for API requests (#16860) (#16877)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2024-01-16 12:12:15 -08:00
gcp-cherry-pick-bot[bot]
12f2252700 Added Openkruise workload integration health check scripts (#16238) (#16805)
Signed-off-by: Mahesh <maheshkasbe010@gmail.com>
Co-authored-by: Mahesh Kasbe <60398112+maheshkasabe@users.noreply.github.com>
Co-authored-by: Ishita Sequeira <46771830+ishitasequeira@users.noreply.github.com>
2024-01-09 21:10:42 -05:00
gcp-cherry-pick-bot[bot]
6f6a9a940b fix: add list permission deployments (#16785) (#16803)
* add list permissions for deployments to application controller



* revert redis-ha chart changes



* revert redis-ha chart changes



---------

Signed-off-by: ishitasequeira <ishiseq29@gmail.com>
Co-authored-by: Ishita Sequeira <46771830+ishitasequeira@users.noreply.github.com>
2024-01-09 21:07:05 -05:00
gcp-cherry-pick-bot[bot]
3ca67858f0 docs: add context to configmap example (#16763) (#16764)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-01-05 19:04:04 -05:00
gcp-cherry-pick-bot[bot]
3f18c21c07 fix(ui):Fixed log horizontal scroll for issue #16411 (#16727) (#16762)
* Fixed log horizontal scroll



* Updated log line-height



---------

Signed-off-by: Yi Cai <yicai@redhat.com>
Co-authored-by: Yi Cai <yicai@redhat.com>
2024-01-05 13:44:47 -05:00
gcp-cherry-pick-bot[bot]
3ebcca66f3 docs: configmap items are strings (#16737) (#16738)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-01-03 12:58:49 -05:00
github-actions[bot]
d9196060c2 Bump version to 2.10.0-rc1 (#16642)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-12-18 15:44:17 -05:00
2299 changed files with 98865 additions and 279018 deletions

View File

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

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

@@ -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: ''
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

@@ -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,20 +17,10 @@ updates:
schedule:
interval: "daily"
- 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/"

View File

@@ -6,10 +6,9 @@
| 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

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@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.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@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.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@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
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,5 +1,5 @@
name: Integration tests
on:
on:
push:
branches:
- 'master'
@@ -13,8 +13,7 @@ on:
env:
# Golang version to use across CI steps
# renovate: datasource=golang-version packageName=golang
GOLANG_VERSION: '1.24.1'
GOLANG_VERSION: '1.21'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -24,65 +23,36 @@ 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@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- uses: tj-actions/changed-files@bab30c2299617f6615ec02a68b9a40d10bd21366 # v45.0.5
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@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # 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@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Restore go build cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -94,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@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Run golangci-lint
uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1
uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0
with:
# renovate: datasource=go packageName=github.com/golangci/golangci-lint versioning=regex:^v(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)?$
version: v1.64.7
args: --verbose
version: v1.54.0
args: --enable gofmt --timeout 10m --exclude SA5011 --verbose --max-issues-per-linter 0 --max-same-issues 0
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@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Create symlink in GOPATH
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
- name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Install required packages
@@ -153,7 +117,7 @@ jobs:
run: |
echo "/usr/local/bin" >> $GITHUB_PATH
- name: Restore go build cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -173,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@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: code-coverage
path: coverage.out
- name: Generate test results artifacts
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
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@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Create symlink in GOPATH
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
- name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Install required packages
@@ -217,7 +184,7 @@ jobs:
run: |
echo "/usr/local/bin" >> $GITHUB_PATH
- name: Restore go build cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -238,22 +205,19 @@ jobs:
- name: Run all unit tests
run: make test-race-local
- name: Generate test results artifacts
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
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@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Create symlink in GOPATH
@@ -296,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@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup NodeJS
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
# renovate: datasource=node-version packageName=node versioning=node
node-version: '22.9.0'
node-version: '20.7.0'
- name: Restore node dependency cache
id: cache-dependencies
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
with:
path: ui/node_modules
key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }}
@@ -326,8 +285,6 @@ jobs:
NODE_ENV: production
NODE_ONLINE_ENV: online
HOST_ARCH: amd64
# If we're on the master branch, set the codecov token so that we upload bundle analysis
CODECOV_TOKEN: ${{ github.ref == 'refs/heads/master' && secrets.CODECOV_TOKEN || '' }}
working-directory: ui/
- name: Run ESLint
run: yarn lint
@@ -335,112 +292,97 @@ jobs:
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@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
with:
fetch-depth: 0
- name: Restore node dependency cache
id: cache-dependencies
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
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@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
- 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@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
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@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0
uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4
with:
file: 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@9739113ad922ea0a9abb4b2c0f8bf6a4aa8ef820 # v1.0.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@bfd4e558cda28cda6b5defafb9232d191be8c203 # v4.2.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: 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.32.1
latest: true
- version: v1.31.0
latest: false
- version: v1.30.4
latest: false
- version: v1.29.8
latest: false
needs:
k3s-version: [v1.28.2, v1.27.6, v1.26.9, v1.25.14]
needs:
- build-go
- changes
env:
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-e2e-external-2'
ARGOCD_SERVER: '127.0.0.1:8088'
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"
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@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: GH actions workaround - Kill XSP4 process
@@ -448,7 +390,7 @@ 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 -
@@ -459,7 +401,7 @@ jobs:
sudo chmod go-r $HOME/.kube/config
kubectl version
- name: Restore go build cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -485,9 +427,9 @@ jobs:
git config --global user.email "john.doe@example.com"
- name: Pull Docker image required for tests
run: |
docker pull ghcr.io/dexidp/dex:v2.41.1
docker pull ghcr.io/dexidp/dex:v2.37.0
docker pull argoproj/argo-cd-ci-builder:v1.0.0
docker pull redis:7.2.7-alpine
docker pull redis:7.0.15-alpine
- name: Create target directory for binaries in the build-process
run: |
mkdir -p dist
@@ -500,7 +442,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;
@@ -514,40 +456,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@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: e2e-code-coverage
path: /tmp/coverage
if: ${{ matrix.k3s.latest }}
- name: Upload e2e-server logs
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
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,23 @@ 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@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
# Use correct go version. https://github.com/github/codeql-action/issues/1842#issuecomment-1704398087
- name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
with:
go-version-file: go.mod
# 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 +47,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 +61,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,26 +58,26 @@ jobs:
image-digest: ${{ steps.image.outputs.digest }}
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
if: ${{ github.ref_type == 'tag'}}
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
if: ${{ github.ref_type != 'tag'}}
- name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
go-version: ${{ inputs.go-version }}
- name: Install cosign
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 # v3.4.0
- uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0
- uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
- uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
- uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
- name: Setup tags for container image as a CSV type
run: |
@@ -102,7 +104,7 @@ jobs:
echo 'EOF' >> $GITHUB_ENV
- name: Login to Quay.io
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
with:
registry: quay.io
username: ${{ secrets.quay_username }}
@@ -110,7 +112,7 @@ jobs:
if: ${{ inputs.quay_image_name && inputs.push }}
- name: Login to GitHub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
with:
registry: ghcr.io
username: ${{ secrets.ghcr_username }}
@@ -118,7 +120,7 @@ jobs:
if: ${{ inputs.ghcr_image_name && inputs.push }}
- name: Login to dockerhub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
with:
username: ${{ secrets.docker_username }}
password: ${{ secrets.docker_password }}
@@ -132,7 +134,7 @@ jobs:
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
uses: jlumbroso/free-disk-space@4d9e71b726748f254fe64fa44d273194bd18ec91
with:
large-packages: false
docker-images: false
@@ -141,7 +143,7 @@ jobs:
- name: Build and push container image
id: image
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 #v6.10.0
uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 #v5.1.0
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@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- 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.24.1
go-version: 1.21
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.24.1
go-version: 1.21
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.0.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.10.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@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.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@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.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@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
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.24.1' # Note: go-version must also be set in job argocd-image.with.go-version
GOLANG_VERSION: '1.21' # Note: go-version must also be set in job argocd-image.with.go-version
jobs:
argocd-image:
@@ -24,8 +23,7 @@ jobs:
with:
quay_image_name: quay.io/argoproj/argocd:${{ github.ref_name }}
# Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations)
# renovate: datasource=golang-version packageName=golang
go-version: 1.24.1
go-version: 1.21
platforms: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
push: true
secrets:
@@ -33,20 +31,20 @@ jobs:
quay_password: ${{ secrets.RELEASE_QUAY_TOKEN }}
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.0.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.10.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:
@@ -61,7 +59,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
@@ -69,15 +67,19 @@ jobs:
- name: Fetch all tags
run: git fetch --force --tags
- name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- 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
echo "GORELEASER_PREVIOUS_TAG=$(go run hack/get-previous-release/get-previous-version-for-release-notes.go ${{ github.ref_name }})" >> $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@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Set environment variables for ldflags
id: set_ldflag
@@ -86,7 +88,7 @@ jobs:
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
uses: jlumbroso/free-disk-space@4d9e71b726748f254fe64fa44d273194bd18ec91
with:
large-packages: false
docker-images: false
@@ -94,20 +96,20 @@ jobs:
tool-cache: false
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf # v6.1.0
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0
id: run-goreleaser
with:
version: latest
args: release --clean --timeout 55m
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
KUBECTL_VERSION: ${{ env.KUBECTL_VERSION }}
KUBECTL_VERSION: ${{ env.KUBECTL_VERSION }}
GIT_TREE_STATE: ${{ env.GIT_TREE_STATE }}
- name: Generate subject for provenance
id: hash
env:
ARTIFACTS: '${{ steps.run-goreleaser.outputs.artifacts }}'
ARTIFACTS: "${{ steps.run-goreleaser.outputs.artifacts }}"
run: |
set -euo pipefail
@@ -126,10 +128,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.0.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0
with:
base64-subjects: '${{ needs.goreleaser.outputs.hashes }}'
provenance-name: 'argocd-cli.intoto.jsonl'
base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
provenance-name: "argocd-cli.intoto.jsonl"
upload-assets: true
generate-sbom:
@@ -145,13 +147,13 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
go-version: ${{ env.GOLANG_VERSION }}
@@ -164,7 +166,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: |
@@ -184,7 +186,7 @@ jobs:
fi
cd /tmp && tar -zcf sbom.tar.gz *.spdx
- name: Generate SBOM hash
shell: bash
id: sbom-hash
@@ -193,15 +195,15 @@ jobs:
# base64 -w0 encodes to base64 and outputs on a single line.
# sha256sum /tmp/sbom.tar.gz ... | base64 -w0
echo "hashes=$(sha256sum /tmp/sbom.tar.gz | base64 -w0)" >> "$GITHUB_OUTPUT"
- name: Upload SBOM
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: |
/tmp/sbom.tar.gz
sbom-provenance:
needs: [generate-sbom]
permissions:
@@ -209,13 +211,13 @@ 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.0.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.10.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:
- argocd-image
@@ -228,7 +230,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
@@ -289,14 +291,14 @@ jobs:
# 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
sed -i "s/commit-hash: .*/commit-hash: ${{ env.NEW_VERSION }}/" SECURITY-INSIGHTS.yml
if: ${{ env.UPDATE_VERSION == 'true' }}
- name: Create PR to update VERSION on master branch
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
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

@@ -30,12 +30,12 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
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@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
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@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Build reports

2
.gitignore vendored
View File

@@ -1,7 +1,6 @@
.vscode/
.idea/
.DS_Store
.run/
vendor/
dist/*
ui/dist/app/*
@@ -20,7 +19,6 @@ node_modules/
./test/cmp/*.sock
.envrc.remote
.*.swp
rerunreport.txt
# ignore built binaries
cmd/argocd/argocd

2
.gitpod.Dockerfile vendored
View File

@@ -1,4 +1,4 @@
FROM gitpod/workspace-full@sha256:a47a68ee7f9da10cd889ccce4661bc73f2c0d5a98d3d087e8bdfc0230b27964c
FROM gitpod/workspace-full@sha256:511cecde4dc129ca9eb4cc4c479d61f95e5485ebe320a07f5b902f11899956a3
USER root

View File

@@ -1,156 +0,0 @@
issues:
exclude:
- SA5011
max-issues-per-linter: 0
max-same-issues: 0
exclude-rules:
- path: '(.+)_test\.go'
linters:
- unparam
linters:
enable:
- errcheck
- errorlint
- gocritic
- gofumpt
- goimports
- gomodguard
- gosimple
- govet
- importas
- ineffassign
- misspell
# Disabled because of https://github.com/argoproj/argo-cd/issues/21705
# - nolintlint
- perfsprint
- revive
- staticcheck
- testifylint
- thelper
- unparam
- unused
- usestdlibvars
- usetesting
- whitespace
linters-settings:
gocritic:
disabled-checks:
- appendAssign
- assignOp # Keep it disabled for readability
- exitAfterDefer
- mapKey
- typeSwitchVar
goimports:
local-prefixes: github.com/argoproj/argo-cd/v3
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
importas:
alias:
- alias: jwtgo
pkg: github.com/golang-jwt/jwt/v5
- alias: appsv1
pkg: k8s.io/api/apps/v1
- alias: corev1
pkg: k8s.io/api/core/v1
- alias: rbacv1
pkg: k8s.io/api/rbac/v1
- alias: apierrors
pkg: k8s.io/apimachinery/pkg/api/errors
- alias: apiextensionsv1
pkg: k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1
- alias: metav1
pkg: k8s.io/apimachinery/pkg/apis/meta/v1
- alias: informersv1
pkg: k8s.io/client-go/informers/core/v1
- alias: stderrors
pkg: errors
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
testifylint:
enable-all: true
disable:
- go-require
usetesting:
os-mkdir-temp: false
run:
timeout: 50m

View File

@@ -1,5 +1,3 @@
version: 2
project_name: argocd
before:
@@ -16,11 +14,11 @@ 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
@@ -79,8 +77,6 @@ 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-v2-14-release-candidate-57a664791e2a)
## Upgrading

View File

@@ -1,82 +0,0 @@
# global config
filename: "{{.InterfaceName}}.go"
dir: "{{.InterfaceDir}}/mocks"
outpkg: "mocks"
mockname: "{{.InterfaceName}}"
with-expecter: false
# individual interface config
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:
config:
dir: "applicationset/services/scm_provider/aws_codecommit/mocks"
interfaces:
AWSCodeCommitClient:
AWSTaggingClient:
github.com/microsoft/azure-devops-go-api/azuredevops/git:
config:
dir: "applicationset/services/scm_provider/azure_devops/git/mocks"
interfaces:
Client:
github.com/argoproj/argo-cd/v3/applicationset/utils:
interfaces:
Renderer:
github.com/argoproj/argo-cd/v3/commitserver/commit:
interfaces:
RepoClientFactory:
github.com/argoproj/argo-cd/v3/commitserver/apiclient:
interfaces:
CommitServiceClient:
Clientset:
github.com/argoproj/argo-cd/v3/controller/cache:
interfaces:
LiveStateCache:
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:
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/workloadidentity:
interfaces:
TokenProvider:
# These mocks are not currently used, but they are part of the public API of this package.
github.com/argoproj/argo-cd/v3/pkg/apiclient/session:
interfaces:
SessionServiceServer:
SessionServiceClient:
github.com/argoproj/argo-cd/v3/pkg/apiclient/cluster:
interfaces:
ClusterServiceServer:
github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned/typed/application/v1alpha1:
interfaces:
AppProjectInterface:

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

@@ -2,13 +2,10 @@
** @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
/docs/** @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
/USERS.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
/.github/** @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
/.goreleaser.yaml @argoproj/argocd-approvers @argoproj/argocd-approvers-ci

View File

@@ -1,10 +1,10 @@
ARG BASE_IMAGE=docker.io/library/ubuntu:24.04@sha256:80dd3c3b9c6cecb9f1667e9290b3bc61b78c2678c02cbdae5f0fea92cc6734ab
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.24.1@sha256:c5adecdb7b3f8c5ca3c88648a861882849cc8b02fed68ece31e25de88ad13418 AS builder
FROM docker.io/library/golang:1.21.10@sha256:16438a8e66c0c984f732e815ee5b7d715b8e33e81bac6d6a3750b1067744e7ca AS builder
RUN echo 'deb http://archive.debian.org/debian buster-backports main' >> /etc/apt/sources.list
@@ -28,7 +28,7 @@ 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
####################################################################################################
@@ -51,7 +51,7 @@ 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/*
@@ -83,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.6.1@sha256:14bd39208dbc0eb171cbfb26ccb9ac09fa1b2eba04ccd528ab5d12983fd9ee24 AS argocd-ui
WORKDIR /src
COPY ["ui/package.json", "ui/yarn.lock", "./"]
@@ -101,7 +101,7 @@ RUN HOST_ARCH=$TARGETARCH NODE_ENV='production' NODE_ONLINE_ENV='online' NODE_OP
####################################################################################################
# Argo CD Build stage which performs the actual build of Argo CD binaries
####################################################################################################
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.24.1@sha256:c5adecdb7b3f8c5ca3c88648a861882849cc8b02fed68ece31e25de88ad13418 AS argocd-build
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.21.10@sha256:16438a8e66c0c984f732e815ee5b7d715b8e33e81bac6d6a3750b1067744e7ca AS argocd-build
WORKDIR /go/src/github.com/argoproj/argo-cd
@@ -140,8 +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", "--"]

146
Makefile
View File

@@ -1,9 +1,8 @@
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
CGO_FLAG=0
GEN_RESOURCES_CLI_NAME=argocd-resources-gen
@@ -23,21 +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
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
@@ -84,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) \
@@ -109,14 +101,13 @@ define run-in-test-server
-p ${ARGOCD_E2E_APISERVER_PORT}:8080 \
-p 4000:4000 \
-p 5000:5000 \
$(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 \
@@ -131,14 +122,13 @@ define run-in-test-client
-v ${HOME}/.kube:/home/user/.kube${VOLUME_MOUNT} \
-v /tmp:/tmp${VOLUME_MOUNT} \
-w ${DOCKER_WORKDIR} \
$(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
@@ -153,13 +143,6 @@ 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} \
@@ -192,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
@@ -225,25 +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: codegen-local
codegen-local: mod-vendor-local mockgen gogen protogen clientgen openapigen clidocsgen actionsdocsgen 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)
@@ -254,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
@@ -273,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
@@ -288,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)
@@ -315,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
@@ -355,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
@@ -366,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 --enable gofmt --fix --verbose --timeout 3000s --max-issues-per-linter 0 --max-same-issues 0
.PHONY: lint-ui
lint-ui: test-tools-image
@@ -400,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"; \
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES=`go list ./... | grep -v 'test/e2e'` ./hack/test.sh -coverprofile=coverage.out; \
else \
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES="$(TEST_MODULE)" ./hack/test.sh -args -test.gocoverdir="$(PWD)/test-results" "$(TEST_MODULE)"; \
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES="$(TEST_MODULE)" ./hack/test.sh -coverprofile=coverage.out "$(TEST_MODULE)"; \
fi
.PHONY: test-race
@@ -414,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"; \
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES=`go list ./... | grep -v 'test/e2e'` ./hack/test.sh -race -coverprofile=coverage.out; \
else \
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES="$(TEST_MODULE)" ./hack/test.sh -race -args -test.gocoverdir="$(PWD)/test-results"; \
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES="$(TEST_MODULE)" ./hack/test.sh -race -coverprofile=coverage.out; \
fi
# Run the E2E test suite. E2E test servers (see start-e2e target) must be
@@ -430,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"
DIST_DIR=${DIST_DIR} RERUN_FAILS=5 PACKAGES="./test/e2e" ARGOCD_E2E_RECORD=${ARGOCD_E2E_RECORD} ARGOCD_GPG_ENABLED=true NO_PROXY=* ./hack/test.sh -timeout $(ARGOCD_E2E_TEST_TIMEOUT) -v
# Spawns a shell in the test server container for debugging purposes
debug-test-server: test-tools-image
@@ -443,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)
@@ -461,13 +447,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 \
@@ -482,12 +461,9 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
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_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 \
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
@@ -500,7 +476,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
@@ -513,7 +489,6 @@ 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) \
@@ -551,7 +526,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:
@@ -559,7 +534,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
@@ -582,7 +558,7 @@ 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 helm-linux
./hack/install.sh gotestsum
# Installs all tools required for running codegen (Linux packages)
@@ -594,7 +570,6 @@ install-codegen-tools-local:
.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
@@ -612,7 +587,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:
@@ -675,6 +650,7 @@ 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'

1
OWNERS
View File

@@ -1,6 +1,5 @@
owners:
- alexmt
- crenshaw-dev
- jessesuen
approvers:

View File

@@ -1,13 +1,13 @@
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 "HOSTNAME=testappcontroller-1 FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''} --server-side-diff-enabled=${ARGOCD_APPLICATION_CONTROLLER_SERVER_SIDE_DIFF:-'false'}"
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
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'}"
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 --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''} --self-service-notification-enabled=${ARGOCD_NOTIFICATION_CONTROLLER_SELF_SERVICE_NOTIFICATION_ENABLED:-'false'}"

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://akuity.io/blog/unveil-the-secret-ingredients-of-continuous-delivery-at-enterprise-scale-with-argocd-kubecon-china-2021/)
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

@@ -3,9 +3,9 @@ header:
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: 74a367d10e7110209610ba3ec225539ebe5f7522
commit-hash: b71277c6beb949d0199d647a582bc25822b88838
project-url: https://github.com/argoproj/argo-cd
project-release: v2.14.0
project-release: v2.9.0-rc3
changelog: https://github.com/argoproj/argo-cd/releases
license: https://github.com/argoproj/argo-cd/blob/master/LICENSE
project-lifecycle:

View File

@@ -11,76 +11,57 @@ Currently, the following organizations are **officially** using Argo CD:
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. [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. [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. [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. [CARFAX Europe](https://www.carfax.eu)
1. [CARFAX](https://www.carfax.com)
1. [CARFAX Europe](https://www.carfax.eu)
1. [Carrefour Group](https://www.carrefour.com)
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. [Cisco ET&I](https://eti.cisco.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/)
@@ -89,13 +70,11 @@ 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. [Devopsi - Poland Software/DevOps Consulting](https://devopsi.pl/)
1. [Devtron Labs](https://github.com/devtron-labs/devtron)
1. [DigitalOcean](https://www.digitalocean.com)
1. [Divar](https://divar.ir)
1. [Divistant](https://divistant.com)
1. [Dott](https://ridedott.com)
1. [Doximity](https://www.doximity.com/)
@@ -115,19 +94,15 @@ Currently, the following organizations are **officially** using Argo CD:
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)
@@ -137,34 +112,28 @@ 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. [IABAI](https://www.iab.ai)
1. [IBM](https://www.ibm.com/)
1. [Ibotta](https://home.ibotta.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. [Intuit](https://www.intuit.com/)
1. [Jellysmack](https://www.jellysmack.com)
1. [Joblift](https://joblift.com/)
@@ -174,7 +143,6 @@ 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)
@@ -186,17 +154,14 @@ Currently, the following organizations are **officially** using Argo CD:
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)
@@ -209,7 +174,6 @@ Currently, the following organizations are **officially** using Argo CD:
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/)
@@ -218,28 +182,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)
@@ -251,54 +207,43 @@ 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. [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](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/)
@@ -308,17 +253,12 @@ Currently, the following organizations are **officially** using Argo CD:
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. [Shield](https://shield.com)
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/)
@@ -329,7 +269,6 @@ Currently, the following organizations are **officially** using Argo CD:
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/)
@@ -338,17 +277,12 @@ Currently, the following organizations are **officially** using Argo CD:
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. [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)
@@ -371,13 +305,11 @@ Currently, the following organizations are **officially** using Argo CD:
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/)
@@ -392,12 +324,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. [ZDF](https://www.zdf.de/)
1. [Zimpler](https://www.zimpler.com/)
1. [ZipRecuiter](https://www.ziprecruiter.com/)
1. [ZOZO](https://corp.zozo.com/)

View File

@@ -1 +1 @@
3.0.0
2.10.14

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,30 +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"
)
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
@@ -38,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,
},
},
},
@@ -52,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",
},
@@ -66,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,
},
},
},
@@ -82,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",
},
@@ -95,7 +94,7 @@ func TestClusterEventHandler(t *testing.T) {
},
},
{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set2",
Namespace: "argocd",
},
@@ -109,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,
},
},
},
@@ -126,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",
},
@@ -139,7 +138,7 @@ func TestClusterEventHandler(t *testing.T) {
},
},
{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: "app-set-non-cluster",
Namespace: "argocd",
},
@@ -153,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,
},
},
},
@@ -169,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",
},
@@ -183,7 +182,7 @@ func TestClusterEventHandler(t *testing.T) {
},
},
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-non-argocd-secret",
},
@@ -194,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",
},
@@ -214,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,
},
},
},
@@ -230,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",
},
@@ -250,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,
},
},
},
@@ -264,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",
},
@@ -300,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,
},
},
},
@@ -316,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",
},
@@ -351,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,
},
},
},
@@ -365,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",
},
@@ -385,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,
},
},
},
@@ -401,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",
},
@@ -421,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,
},
},
},
@@ -435,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",
},
@@ -471,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,
},
},
},
@@ -487,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",
},
@@ -522,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,
},
},
},
@@ -535,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,
}
@@ -549,20 +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 reconcile.Request) {
obj.addedItems = append(obj.addedItems, item)
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 {
addedItems []reconcile.Request
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, 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{""}, true),
"ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, fakeDynClient, appClientset, "argocd"),
"PullRequest": generators.NewPullRequestGenerator(k8sClient, scmConfig),
"PullRequest": generators.NewPullRequestGenerator(k8sClient, generators.SCMAuthProviders{}, "", []string{""}, true),
}
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.DefaultRequeueAfterSeconds, 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.DefaultRequeueAfterSeconds, 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.DefaultRequeueAfterSeconds, 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,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.On("GenerateParams", &generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).
Return(cc.params, cc.generateParamsError)
generatorMock.On("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.On("RenderTemplateParams", GetTempApplication(cc.template), mock.AnythingOfType("*v1alpha1.ApplicationSetSyncPolicy"), p, false, []string(nil)).
Return(nil, cc.rendererError)
} else {
rendererMock.On("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.On("GenerateParams", &generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).
Return(cc.params, nil)
generatorMock.On("GetTemplate", &generator).
Return(&cc.overrideTemplate)
rendererMock := rendmock.Renderer{}
rendererMock.On("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.On("GenerateParams", &generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).
Return(cases.params, nil)
generatorMock.On("GetTemplate", &generator).
Return(&cases.template, nil)
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.EqualValues(t, cases.expectedApp[0].ObjectMeta.Name, gotApp[0].ObjectMeta.Name)
assert.EqualValues(t, cases.expectedApp[0].Spec.Source.TargetRevision, gotApp[0].Spec.Source.TargetRevision)
assert.EqualValues(t, cases.expectedApp[0].Spec.Destination.Namespace, gotApp[0].Spec.Destination.Namespace)
assert.True(t, maps.Equal(cases.expectedApp[0].ObjectMeta.Labels, gotApp[0].ObjectMeta.Labels))
})
}
}

View File

@@ -1,4 +1,4 @@
package template
package controllers
import (
"encoding/json"
@@ -6,17 +6,19 @@ import (
"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"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
appv1 "github.com/argoproj/argo-cd/v2/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)
}
@@ -26,6 +28,7 @@ func applyTemplatePatch(app *appv1.Application, templatePatch string) (*appv1.Ap
}
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)
}

View File

@@ -1,4 +1,4 @@
package template
package controllers
import (
"testing"
@@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
appv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func Test_ApplyTemplatePatch(t *testing.T) {

View File

@@ -7,16 +7,20 @@ import (
log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v3/util/settings"
"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)
@@ -33,7 +37,8 @@ type ClusterGenerator struct {
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{
@@ -48,7 +53,7 @@ func NewClusterGenerator(ctx context.Context, c client.Client, clientset kuberne
// 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
}
@@ -56,8 +61,8 @@ 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, EmptyAppSetGeneratorError
}
@@ -70,7 +75,7 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
// - 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)
@@ -80,64 +85,50 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
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
}
res := []map[string]any{}
res := []map[string]interface{}{}
secretsFound := []corev1.Secret{}
isFlatMode := appSetGenerator.Clusters.FlatList
logCtx.Debugf("Using flat mode = %t for cluster generator", isFlatMode)
clustersParams := make([]map[string]any, 0)
for _, cluster := range clustersFromArgoCD.Items {
for _, cluster := range clustersFromArgoCD {
// 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
}
if isFlatMode {
clustersParams = append(clustersParams, params)
} else {
res = append(res, params)
}
res = append(res, params)
logCtx.WithField("cluster", "local cluster").Info("matched local cluster")
log.WithField("cluster", "local cluster").Info("matched local cluster")
}
}
// For each matching cluster secret (non-local clusters only)
for _, cluster := range secretsFound {
params := map[string]any{}
params := map[string]interface{}{}
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{}
meta := map[string]interface{}{}
if len(cluster.ObjectMeta.Annotations) > 0 {
meta["annotations"] = cluster.ObjectMeta.Annotations
@@ -149,49 +140,41 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
params["metadata"] = meta
} else {
for key, value := range cluster.ObjectMeta.Annotations {
params["metadata.annotations."+key] = value
params[fmt.Sprintf("metadata.annotations.%s", key)] = value
}
for key, value := range cluster.ObjectMeta.Labels {
params["metadata.labels."+key] = value
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
}
if isFlatMode {
clustersParams = append(clustersParams, params)
} else {
res = append(res, params)
}
res = append(res, params)
logCtx.WithField("cluster", cluster.Name).Debug("matched cluster secret")
log.WithField("cluster", cluster.Name).Info("matched cluster secret")
}
if isFlatMode {
res = append(res, map[string]any{
"clusters": clustersParams,
})
}
return res, nil
}
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)
return nil, err
}
if err := g.Client.List(context.Background(), clusterSecretList, client.MatchingLabelsSelector{Selector: secretSelector}); err != nil {
return nil, err
}
log.Debugf("clusters matching labels: %d", len(clusterSecretList.Items))
log.Debug("clusters matching labels", "count", len(clusterSecretList.Items))
res := map[string]corev1.Secret{}
@@ -202,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,15 +2,13 @@ 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/v3/util/settings"
"github.com/argoproj/argo-cd/v2/util/settings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
@@ -18,8 +16,8 @@ import (
"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)
@@ -34,6 +32,7 @@ type DuckTypeGenerator struct {
}
func NewDuckTypeGenerator(ctx context.Context, dynClient dynamic.Interface, clientset kubernetes.Interface, namespace string) Generator {
settingsManager := settings.NewSettingsManager(ctx, clientset, namespace)
g := &DuckTypeGenerator{
@@ -47,20 +46,22 @@ func NewDuckTypeGenerator(ctx context.Context, dynClient dynamic.Interface, clie
}
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, EmptyAppSetGeneratorError
}
@@ -82,6 +83,7 @@ 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)
}
@@ -97,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
@@ -119,19 +122,20 @@ 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
@@ -145,78 +149,87 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
if matchKey == "" {
log.WithField("matchKey", matchKey).Warning("matchKey not found in " + cm.Name)
return nil, nil
}
res := []map[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))
if len(clusterDecisions) == 0 {
log.Warningf("clusterDecisionResource status.%s missing", statusListKey)
return nil, nil
}
for _, cluster := range clusterDecisions {
// generated instance of cluster params
params := map[string]any{}
// Read this outside the loop to improve performance
argoClusters := clustersFromArgoCD.Items
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))
continue
}
if len(clusterDecisions) > 0 {
for _, cluster := range clusterDecisions {
strMatchValue := matchValue.(string)
log.WithField(matchKey, strMatchValue).Debug("validate against ArgoCD")
// generated instance of cluster params
params := map[string]interface{}{}
found := false
for _, argoCluster := range clustersFromArgoCD {
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
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
}
}
if !found {
log.WithField(matchKey, strMatchValue).Warning("unmatched cluster in ArgoCD")
continue
}
strMatchValue := matchValue.(string)
log.WithField(matchKey, strMatchValue).Debug("validate against ArgoCD")
for key, value := range cluster.(map[string]any) {
params[key] = value.(string)
}
found := false
for key, value := range appSetGenerator.ClusterDecisionResource.Values {
if appSet.Spec.GoTemplate {
if params["values"] == nil {
params["values"] = map[string]string{}
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
}
params["values"].(map[string]string)[key] = value
} else {
params["values."+key] = value
}
}
res = append(res, params)
}
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)
}
} else {
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,7 +122,7 @@ 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)
@@ -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": "",
}},
},
}
@@ -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)
})
}
@@ -342,17 +337,18 @@ func getMockClusterGenerator() Generator {
appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
return NewClusterGenerator(context.Background(), fakeClient, appClientset, "namespace")
return NewClusterGenerator(fakeClient, context.Background(), appClientset, "namespace")
}
func getMockGitGenerator() Generator {
argoCDServiceMock := mocks.Repos{}
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return([]string{"app1", "app2", "app_3", "p1/app4"}, nil)
gitGenerator := NewGitGenerator(&argoCDServiceMock, "namespace")
var gitGenerator = NewGitGenerator(&argoCDServiceMock)
return gitGenerator
}
func TestGetRelevantGenerators(t *testing.T) {
testGenerators := map[string]Generator{
"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,29 +11,23 @@ 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
}
func NewGitGenerator(repos services.Repos, namespace string) Generator {
func NewGitGenerator(repos services.Repos) Generator {
g := &GitGenerator{
repos: repos,
namespace: namespace,
repos: repos,
}
return g
}
@@ -42,16 +36,18 @@ func (g *GitGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Applicati
}
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
}
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, EmptyAppSetGeneratorError
}
@@ -62,37 +58,13 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
noRevisionCache := appSet.RefreshRequired()
verifyCommit := false
// When the project field is templated, the contents of the git repo are required to run the git generator and get the templated value,
// but git generator cannot be called without verifying the commit signature.
// In this case, we skip the signature verification.
if !strings.Contains(appSet.Spec.Template.Spec.Project, "{{") {
project := appSet.Spec.Template.Spec.Project
appProject := &argoprojiov1alpha1.AppProject{}
namespace := g.namespace
if namespace == "" {
namespace = appSet.Namespace
}
if err := client.Get(context.TODO(), types.NamespacedName{Name: project, Namespace: namespace}, appProject); err != nil {
return nil, fmt.Errorf("error getting project %s: %w", project, err)
}
// we need to verify the signature on the Git revision if GPG is enabled
verifyCommit = 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:
var res []map[string]interface{}
if len(appSetGenerator.Git.Directories) != 0 {
res, err = g.generateParamsForGitDirectories(appSetGenerator, noRevisionCache, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
} else if len(appSetGenerator.Git.Files) != 0 {
res, err = g.generateParamsForGitFiles(appSetGenerator, noRevisionCache, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
} else {
return nil, EmptyAppSetGeneratorError
}
if err != nil {
@@ -102,9 +74,10 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
return res, nil
}
func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit, useGoTemplate bool, project string, goTemplateOptions []string) ([]map[string]any, error) {
func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache bool, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
// Directories, not files
allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, project, noRevisionCache, verifyCommit)
allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, noRevisionCache)
if err != nil {
return nil, fmt.Errorf("error getting directories from repo: %w", err)
}
@@ -127,11 +100,12 @@ func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoproj
return res, nil
}
func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit, useGoTemplate bool, project string, goTemplateOptions []string) ([]map[string]any, error) {
func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache bool, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
// 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, project, requestedPath.Path, noRevisionCache, verifyCommit)
files, err := g.repos.GetFiles(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, requestedPath.Path, noRevisionCache)
if err != nil {
return nil, err
}
@@ -149,12 +123,13 @@ func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1al
sort.Strings(allPaths)
// Generate params from each path, and return
res := []map[string]any{}
res := []map[string]interface{}{}
for _, path := range allPaths {
// A JSON / YAML file path can contain multiple sets of parameters (ie it is an array)
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", path, err)
return nil, fmt.Errorf("unable to process file '%s': %v", path, err)
}
res = append(res, paramsArray...)
@@ -162,35 +137,36 @@ func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1al
return res, nil
}
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 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]any)
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)
} else if len(objectsFound) == 0 {
// If file is valid but empty, add a default empty item
objectsFound = append(objectsFound, map[string]any{})
objectsFound = append(objectsFound, map[string]interface{}{})
}
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))
@@ -199,7 +175,7 @@ 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
}
@@ -238,13 +214,13 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
return res, nil
}
func (g *GitGenerator) filterApps(directories []argoprojiov1alpha1.GitDirectoryGeneratorItem, allPaths []string) []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).
@@ -266,19 +242,20 @@ func (g *GitGenerator) filterApps(directories []argoprojiov1alpha1.GitDirectoryG
return res
}
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
}
@@ -307,11 +284,3 @@ func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGene
return res, nil
}
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 (
EmptyAppSetGeneratorError = 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 (
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", DefaultRequeueAfterSeconds, 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: DefaultRequeueAfterSeconds},
{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: DefaultRequeueAfterSeconds},
{name: "MoreThanMax", requeueAfterEnv: "8761h", want: DefaultRequeueAfterSeconds},
}
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,7 +28,7 @@ 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, EmptyAppSetGeneratorError
}
@@ -38,14 +37,14 @@ func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appli
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
}
@@ -78,10 +77,11 @@ func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appli
// Append elements from ElementsYaml to the response
if len(appSetGenerator.List.ElementsYaml) > 0 {
var yamlElements []map[string]any
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...)
}

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,7 +32,8 @@ 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, EmptyAppSetGeneratorError
}
@@ -45,20 +46,21 @@ 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)
}
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{}
tmp := map[string]interface{}{}
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)
}
@@ -79,15 +81,27 @@ 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)
}
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(
argoprojiov1alpha1.ApplicationSetGenerator{
@@ -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 {
@@ -155,8 +169,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) {

View File

@@ -1,6 +1,7 @@
package generators
import (
"context"
"testing"
"time"
@@ -12,35 +13,36 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/argoproj/argo-cd/v3/applicationset/services/mocks"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func TestMatrixGenerate(t *testing.T) {
gitGenerator := &v1alpha1.GitGenerator{
gitGenerator := &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL",
Revision: "Revision",
Directories: []v1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
Directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
}
listGenerator := &v1alpha1.ListGenerator{
listGenerator := &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url", "templated": "test-{{path.basenameNormalized}}"}`)}},
}
testCases := []struct {
name string
baseGenerators []v1alpha1.ApplicationSetNestedGenerator
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
expectedErr error
expected []map[string]any
expected []map[string]interface{}
}{
{
name: "happy flow - generate params",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
Git: gitGenerator,
},
@@ -48,16 +50,16 @@ func TestMatrixGenerate(t *testing.T) {
List: listGenerator,
},
},
expected: []map[string]any{
expected: []map[string]interface{}{
{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "cluster": "Cluster", "url": "Url", "templated": "test-app1"},
{"path": "app2", "path.basename": "app2", "path.basenameNormalized": "app2", "cluster": "Cluster", "url": "Url", "templated": "test-app2"},
},
},
{
name: "happy flow - generate params from two lists",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
List: &v1alpha1.ListGenerator{
List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{
{Raw: []byte(`{"a": "1"}`)},
{Raw: []byte(`{"a": "2"}`)},
@@ -65,7 +67,7 @@ func TestMatrixGenerate(t *testing.T) {
},
},
{
List: &v1alpha1.ListGenerator{
List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{
{Raw: []byte(`{"b": "1"}`)},
{Raw: []byte(`{"b": "2"}`)},
@@ -73,7 +75,7 @@ func TestMatrixGenerate(t *testing.T) {
},
},
},
expected: []map[string]any{
expected: []map[string]interface{}{
{"a": "1", "b": "1"},
{"a": "1", "b": "2"},
{"a": "2", "b": "1"},
@@ -82,7 +84,7 @@ func TestMatrixGenerate(t *testing.T) {
},
{
name: "returns error if there is less than two base generators",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
Git: gitGenerator,
},
@@ -91,7 +93,7 @@ func TestMatrixGenerate(t *testing.T) {
},
{
name: "returns error if there is more than two base generators",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
List: listGenerator,
},
@@ -106,7 +108,7 @@ func TestMatrixGenerate(t *testing.T) {
},
{
name: "returns error if there is more than one inner generator in the first base generator",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
Git: gitGenerator,
List: listGenerator,
@@ -119,7 +121,7 @@ func TestMatrixGenerate(t *testing.T) {
},
{
name: "returns error if there is more than one inner generator in the second base generator",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
List: listGenerator,
},
@@ -137,19 +139,20 @@ func TestMatrixGenerate(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) {
genMock := &generatorMock{}
appSet := &v1alpha1.ApplicationSet{
appSet := &argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: v1alpha1.ApplicationSetSpec{},
Spec: argoprojiov1alpha1.ApplicationSetSpec{},
}
for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := v1alpha1.ApplicationSetGenerator{
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git,
List: g.List,
}
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet, mock.Anything).Return([]map[string]any{
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]interface{}{
{
"path": "app1",
"path.basename": "app1",
@@ -163,53 +166,56 @@ func TestMatrixGenerate(t *testing.T) {
}, nil)
genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&v1alpha1.ApplicationSetTemplate{})
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
}
matrixGenerator := NewMatrixGenerator(
var matrixGenerator = NewMatrixGenerator(
map[string]Generator{
"Git": genMock,
"List": &ListGenerator{},
},
)
got, err := matrixGenerator.GenerateParams(&v1alpha1.ApplicationSetGenerator{
Matrix: &v1alpha1.MatrixGenerator{
got, err := matrixGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
Matrix: &argoprojiov1alpha1.MatrixGenerator{
Generators: testCaseCopy.baseGenerators,
Template: v1alpha1.ApplicationSetTemplate{},
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
},
}, appSet, nil)
}, appSet)
if testCaseCopy.expectedErr != nil {
require.ErrorIs(t, err, testCaseCopy.expectedErr)
assert.ErrorIs(t, err, testCaseCopy.expectedErr)
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got)
}
})
}
}
func TestMatrixGenerateGoTemplate(t *testing.T) {
gitGenerator := &v1alpha1.GitGenerator{
gitGenerator := &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL",
Revision: "Revision",
Directories: []v1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
Directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
}
listGenerator := &v1alpha1.ListGenerator{
listGenerator := &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url"}`)}},
}
testCases := []struct {
name string
baseGenerators []v1alpha1.ApplicationSetNestedGenerator
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
expectedErr error
expected []map[string]any
expected []map[string]interface{}
}{
{
name: "happy flow - generate params",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
Git: gitGenerator,
},
@@ -217,7 +223,7 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
List: listGenerator,
},
},
expected: []map[string]any{
expected: []map[string]interface{}{
{
"path": map[string]string{
"path": "app1",
@@ -240,9 +246,9 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
},
{
name: "happy flow - generate params from two lists",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
List: &v1alpha1.ListGenerator{
List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{
{Raw: []byte(`{"a": "1"}`)},
{Raw: []byte(`{"a": "2"}`)},
@@ -250,7 +256,7 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
},
},
{
List: &v1alpha1.ListGenerator{
List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{
{Raw: []byte(`{"b": "1"}`)},
{Raw: []byte(`{"b": "2"}`)},
@@ -258,7 +264,7 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
},
},
},
expected: []map[string]any{
expected: []map[string]interface{}{
{"a": "1", "b": "1"},
{"a": "1", "b": "2"},
{"a": "2", "b": "1"},
@@ -267,29 +273,29 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
},
{
name: "parameter override: first list elements take precedence",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
List: &v1alpha1.ListGenerator{
List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{
{Raw: []byte(`{"booleanFalse": false, "booleanTrue": true, "stringFalse": "false", "stringTrue": "true"}`)},
},
},
},
{
List: &v1alpha1.ListGenerator{
List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{
{Raw: []byte(`{"booleanFalse": true, "booleanTrue": false, "stringFalse": "true", "stringTrue": "false"}`)},
},
},
},
},
expected: []map[string]any{
expected: []map[string]interface{}{
{"booleanFalse": false, "booleanTrue": true, "stringFalse": "false", "stringTrue": "true"},
},
},
{
name: "returns error if there is less than two base generators",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
Git: gitGenerator,
},
@@ -298,7 +304,7 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
},
{
name: "returns error if there is more than two base generators",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
List: listGenerator,
},
@@ -313,7 +319,7 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
},
{
name: "returns error if there is more than one inner generator in the first base generator",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
Git: gitGenerator,
List: listGenerator,
@@ -326,7 +332,7 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
},
{
name: "returns error if there is more than one inner generator in the second base generator",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
List: listGenerator,
},
@@ -344,21 +350,22 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) {
genMock := &generatorMock{}
appSet := &v1alpha1.ApplicationSet{
appSet := &argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: v1alpha1.ApplicationSetSpec{
Spec: argoprojiov1alpha1.ApplicationSetSpec{
GoTemplate: true,
},
}
for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := v1alpha1.ApplicationSetGenerator{
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git,
List: g.List,
}
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet, mock.Anything).Return([]map[string]any{
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]interface{}{
{
"path": map[string]string{
"path": "app1",
@@ -376,59 +383,62 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
}, nil)
genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&v1alpha1.ApplicationSetTemplate{})
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
}
matrixGenerator := NewMatrixGenerator(
var matrixGenerator = NewMatrixGenerator(
map[string]Generator{
"Git": genMock,
"List": &ListGenerator{},
},
)
got, err := matrixGenerator.GenerateParams(&v1alpha1.ApplicationSetGenerator{
Matrix: &v1alpha1.MatrixGenerator{
got, err := matrixGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
Matrix: &argoprojiov1alpha1.MatrixGenerator{
Generators: testCaseCopy.baseGenerators,
Template: v1alpha1.ApplicationSetTemplate{},
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
},
}, appSet, nil)
}, appSet)
if testCaseCopy.expectedErr != nil {
require.ErrorIs(t, err, testCaseCopy.expectedErr)
assert.ErrorIs(t, err, testCaseCopy.expectedErr)
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got)
}
})
}
}
func TestMatrixGetRequeueAfter(t *testing.T) {
gitGenerator := &v1alpha1.GitGenerator{
gitGenerator := &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL",
Revision: "Revision",
Directories: []v1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
Directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
}
listGenerator := &v1alpha1.ListGenerator{
listGenerator := &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url"}`)}},
}
pullRequestGenerator := &v1alpha1.PullRequestGenerator{}
pullRequestGenerator := &argoprojiov1alpha1.PullRequestGenerator{}
scmGenerator := &v1alpha1.SCMProviderGenerator{}
scmGenerator := &argoprojiov1alpha1.SCMProviderGenerator{}
duckTypeGenerator := &v1alpha1.DuckTypeGenerator{}
duckTypeGenerator := &argoprojiov1alpha1.DuckTypeGenerator{}
testCases := []struct {
name string
baseGenerators []v1alpha1.ApplicationSetNestedGenerator
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
gitGetRequeueAfter time.Duration
expected time.Duration
}{
{
name: "return NoRequeueAfter if all the inner baseGenerators returns it",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
Git: gitGenerator,
},
@@ -441,7 +451,7 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
},
{
name: "returns the minimal time",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
Git: gitGenerator,
},
@@ -454,7 +464,7 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
},
{
name: "returns the minimal time for pull request",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
Git: gitGenerator,
},
@@ -467,7 +477,7 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
},
{
name: "returns the default time if no requeueAfterSeconds is provided",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
Git: gitGenerator,
},
@@ -479,7 +489,7 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
},
{
name: "returns the default time for duck type generator",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
Git: gitGenerator,
},
@@ -491,7 +501,7 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
},
{
name: "returns the default time for scm generator",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
Git: gitGenerator,
},
@@ -510,7 +520,7 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
mock := &generatorMock{}
for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := v1alpha1.ApplicationSetGenerator{
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git,
List: g.List,
PullRequest: g.PullRequest,
@@ -520,7 +530,7 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
mock.On("GetRequeueAfter", &gitGeneratorSpec).Return(testCaseCopy.gitGetRequeueAfter, nil)
}
matrixGenerator := NewMatrixGenerator(
var matrixGenerator = NewMatrixGenerator(
map[string]Generator{
"Git": mock,
"List": &ListGenerator{},
@@ -530,29 +540,31 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
},
)
got := matrixGenerator.GetRequeueAfter(&v1alpha1.ApplicationSetGenerator{
Matrix: &v1alpha1.MatrixGenerator{
got := matrixGenerator.GetRequeueAfter(&argoprojiov1alpha1.ApplicationSetGenerator{
Matrix: &argoprojiov1alpha1.MatrixGenerator{
Generators: testCaseCopy.baseGenerators,
Template: v1alpha1.ApplicationSetTemplate{},
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
},
})
assert.Equal(t, testCaseCopy.expected, got)
})
}
}
func TestInterpolatedMatrixGenerate(t *testing.T) {
interpolatedGitGenerator := &v1alpha1.GitGenerator{
interpolatedGitGenerator := &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL",
Revision: "Revision",
Files: []v1alpha1.GitFileGeneratorItem{
Files: []argoprojiov1alpha1.GitFileGeneratorItem{
{Path: "examples/git-generator-files-discovery/cluster-config/dev/config.json"},
{Path: "examples/git-generator-files-discovery/cluster-config/prod/config.json"},
},
}
interpolatedClusterGenerator := &v1alpha1.ClusterGenerator{
interpolatedClusterGenerator := &argoprojiov1alpha1.ClusterGenerator{
Selector: metav1.LabelSelector{
MatchLabels: map[string]string{"environment": "{{path.basename}}"},
MatchExpressions: nil,
@@ -560,14 +572,14 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
}
testCases := []struct {
name string
baseGenerators []v1alpha1.ApplicationSetNestedGenerator
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
expectedErr error
expected []map[string]any
expected []map[string]interface{}
clientError bool
}{
{
name: "happy flow - generate interpolated params",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
Git: interpolatedGitGenerator,
},
@@ -575,9 +587,9 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
Clusters: interpolatedClusterGenerator,
},
},
expected: []map[string]any{
{"path": "examples/git-generator-files-discovery/cluster-config/dev/config.json", "path.basename": "dev", "path.basenameNormalized": "dev", "name": "dev-01", "nameNormalized": "dev-01", "server": "https://dev-01.example.com", "metadata.labels.environment": "dev", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "project": ""},
{"path": "examples/git-generator-files-discovery/cluster-config/prod/config.json", "path.basename": "prod", "path.basenameNormalized": "prod", "name": "prod-01", "nameNormalized": "prod-01", "server": "https://prod-01.example.com", "metadata.labels.environment": "prod", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "project": ""},
expected: []map[string]interface{}{
{"path": "examples/git-generator-files-discovery/cluster-config/dev/config.json", "path.basename": "dev", "path.basenameNormalized": "dev", "name": "dev-01", "nameNormalized": "dev-01", "server": "https://dev-01.example.com", "metadata.labels.environment": "dev", "metadata.labels.argocd.argoproj.io/secret-type": "cluster"},
{"path": "examples/git-generator-files-discovery/cluster-config/prod/config.json", "path.basename": "prod", "path.basenameNormalized": "prod", "name": "prod-01", "nameNormalized": "prod-01", "server": "https://prod-01.example.com", "metadata.labels.environment": "prod", "metadata.labels.argocd.argoproj.io/secret-type": "cluster"},
},
clientError: false,
},
@@ -635,7 +647,7 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) {
genMock := &generatorMock{}
appSet := &v1alpha1.ApplicationSet{}
appSet := &argoprojiov1alpha1.ApplicationSet{}
appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
@@ -643,14 +655,15 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
fakeClient,
testCase.clientError,
}
clusterGenerator := NewClusterGenerator(t.Context(), cl, appClientset, "namespace")
var clusterGenerator = NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := v1alpha1.ApplicationSetGenerator{
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git,
Clusters: g.Clusters,
}
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]any{
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]interface{}{
{
"path": "examples/git-generator-files-discovery/cluster-config/dev/config.json",
"path.basename": "dev",
@@ -663,43 +676,44 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
},
}, nil)
genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&v1alpha1.ApplicationSetTemplate{})
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
}
matrixGenerator := NewMatrixGenerator(
var matrixGenerator = NewMatrixGenerator(
map[string]Generator{
"Git": genMock,
"Clusters": clusterGenerator,
},
)
got, err := matrixGenerator.GenerateParams(&v1alpha1.ApplicationSetGenerator{
Matrix: &v1alpha1.MatrixGenerator{
got, err := matrixGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
Matrix: &argoprojiov1alpha1.MatrixGenerator{
Generators: testCaseCopy.baseGenerators,
Template: v1alpha1.ApplicationSetTemplate{},
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
},
}, appSet, nil)
}, appSet)
if testCaseCopy.expectedErr != nil {
require.ErrorIs(t, err, testCaseCopy.expectedErr)
assert.ErrorIs(t, err, testCaseCopy.expectedErr)
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got)
}
})
}
}
func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
interpolatedGitGenerator := &v1alpha1.GitGenerator{
interpolatedGitGenerator := &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL",
Revision: "Revision",
Files: []v1alpha1.GitFileGeneratorItem{
Files: []argoprojiov1alpha1.GitFileGeneratorItem{
{Path: "examples/git-generator-files-discovery/cluster-config/dev/config.json"},
{Path: "examples/git-generator-files-discovery/cluster-config/prod/config.json"},
},
}
interpolatedClusterGenerator := &v1alpha1.ClusterGenerator{
interpolatedClusterGenerator := &argoprojiov1alpha1.ClusterGenerator{
Selector: metav1.LabelSelector{
MatchLabels: map[string]string{"environment": "{{.path.basename}}"},
MatchExpressions: nil,
@@ -707,14 +721,14 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
}
testCases := []struct {
name string
baseGenerators []v1alpha1.ApplicationSetNestedGenerator
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
expectedErr error
expected []map[string]any
expected []map[string]interface{}
clientError bool
}{
{
name: "happy flow - generate interpolated params",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
Git: interpolatedGitGenerator,
},
@@ -722,7 +736,7 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
Clusters: interpolatedClusterGenerator,
},
},
expected: []map[string]any{
expected: []map[string]interface{}{
{
"path": map[string]string{
"path": "examples/git-generator-files-discovery/cluster-config/dev/config.json",
@@ -732,8 +746,7 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
"name": "dev-01",
"nameNormalized": "dev-01",
"server": "https://dev-01.example.com",
"project": "",
"metadata": map[string]any{
"metadata": map[string]interface{}{
"labels": map[string]string{
"environment": "dev",
"argocd.argoproj.io/secret-type": "cluster",
@@ -749,8 +762,7 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
"name": "prod-01",
"nameNormalized": "prod-01",
"server": "https://prod-01.example.com",
"project": "",
"metadata": map[string]any{
"metadata": map[string]interface{}{
"labels": map[string]string{
"environment": "prod",
"argocd.argoproj.io/secret-type": "cluster",
@@ -814,8 +826,8 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) {
genMock := &generatorMock{}
appSet := &v1alpha1.ApplicationSet{
Spec: v1alpha1.ApplicationSetSpec{
appSet := &argoprojiov1alpha1.ApplicationSet{
Spec: argoprojiov1alpha1.ApplicationSetSpec{
GoTemplate: true,
},
}
@@ -826,14 +838,16 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
fakeClient,
testCase.clientError,
}
clusterGenerator := NewClusterGenerator(t.Context(), cl, appClientset, "namespace")
var clusterGenerator = NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := v1alpha1.ApplicationSetGenerator{
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git,
Clusters: g.Clusters,
}
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]any{
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]interface{}{
{
"path": map[string]string{
"path": "examples/git-generator-files-discovery/cluster-config/dev/config.json",
@@ -850,55 +864,58 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
},
}, nil)
genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&v1alpha1.ApplicationSetTemplate{})
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
}
matrixGenerator := NewMatrixGenerator(
var matrixGenerator = NewMatrixGenerator(
map[string]Generator{
"Git": genMock,
"Clusters": clusterGenerator,
},
)
got, err := matrixGenerator.GenerateParams(&v1alpha1.ApplicationSetGenerator{
Matrix: &v1alpha1.MatrixGenerator{
got, err := matrixGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
Matrix: &argoprojiov1alpha1.MatrixGenerator{
Generators: testCaseCopy.baseGenerators,
Template: v1alpha1.ApplicationSetTemplate{},
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
},
}, appSet, nil)
}, appSet)
if testCaseCopy.expectedErr != nil {
require.ErrorIs(t, err, testCaseCopy.expectedErr)
assert.ErrorIs(t, err, testCaseCopy.expectedErr)
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got)
}
})
}
}
func TestMatrixGenerateListElementsYaml(t *testing.T) {
gitGenerator := &v1alpha1.GitGenerator{
gitGenerator := &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL",
Revision: "Revision",
Files: []v1alpha1.GitFileGeneratorItem{
Files: []argoprojiov1alpha1.GitFileGeneratorItem{
{Path: "config.yaml"},
},
}
listGenerator := &v1alpha1.ListGenerator{
listGenerator := &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{},
ElementsYaml: "{{ .foo.bar | toJson }}",
}
testCases := []struct {
name string
baseGenerators []v1alpha1.ApplicationSetNestedGenerator
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
expectedErr error
expected []map[string]any
expected []map[string]interface{}
}{
{
name: "happy flow - generate params",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
Git: gitGenerator,
},
@@ -906,23 +923,23 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
List: listGenerator,
},
},
expected: []map[string]any{
expected: []map[string]interface{}{
{
"chart": "a",
"version": "1",
"foo": map[string]any{
"bar": []any{
map[string]any{
"foo": map[string]interface{}{
"bar": []interface{}{
map[string]interface{}{
"chart": "a",
"version": "1",
},
map[string]any{
map[string]interface{}{
"chart": "b",
"version": "2",
},
},
},
"path": map[string]any{
"path": map[string]interface{}{
"basename": "dir",
"basenameNormalized": "dir",
"filename": "file_name.yaml",
@@ -937,19 +954,19 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
{
"chart": "b",
"version": "2",
"foo": map[string]any{
"bar": []any{
map[string]any{
"foo": map[string]interface{}{
"bar": []interface{}{
map[string]interface{}{
"chart": "a",
"version": "1",
},
map[string]any{
map[string]interface{}{
"chart": "b",
"version": "2",
},
},
},
"path": map[string]any{
"path": map[string]interface{}{
"basename": "dir",
"basenameNormalized": "dir",
"filename": "file_name.yaml",
@@ -970,34 +987,35 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) {
genMock := &generatorMock{}
appSet := &v1alpha1.ApplicationSet{
appSet := &argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: v1alpha1.ApplicationSetSpec{
Spec: argoprojiov1alpha1.ApplicationSetSpec{
GoTemplate: true,
},
}
for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := v1alpha1.ApplicationSetGenerator{
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git,
List: g.List,
}
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]any{{
"foo": map[string]any{
"bar": []any{
map[string]any{
"foo": map[string]interface{}{
"bar": []interface{}{
map[string]interface{}{
"chart": "a",
"version": "1",
},
map[string]any{
map[string]interface{}{
"chart": "b",
"version": "2",
},
},
},
"path": map[string]any{
"path": map[string]interface{}{
"basename": "dir",
"basenameNormalized": "dir",
"filename": "file_name.yaml",
@@ -1010,30 +1028,33 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
},
}}, nil)
genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&v1alpha1.ApplicationSetTemplate{})
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
}
matrixGenerator := NewMatrixGenerator(
var matrixGenerator = NewMatrixGenerator(
map[string]Generator{
"Git": genMock,
"List": &ListGenerator{},
},
)
got, err := matrixGenerator.GenerateParams(&v1alpha1.ApplicationSetGenerator{
Matrix: &v1alpha1.MatrixGenerator{
got, err := matrixGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
Matrix: &argoprojiov1alpha1.MatrixGenerator{
Generators: testCaseCopy.baseGenerators,
Template: v1alpha1.ApplicationSetTemplate{},
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
},
}, appSet, nil)
}, appSet)
if testCaseCopy.expectedErr != nil {
require.ErrorIs(t, err, testCaseCopy.expectedErr)
assert.ErrorIs(t, err, testCaseCopy.expectedErr)
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got)
}
})
}
}
@@ -1041,22 +1062,23 @@ type generatorMock struct {
mock.Mock
}
func (g *generatorMock) GetTemplate(appSetGenerator *v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate {
func (g *generatorMock) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
args := g.Called(appSetGenerator)
return args.Get(0).(*v1alpha1.ApplicationSetTemplate)
return args.Get(0).(*argoprojiov1alpha1.ApplicationSetTemplate)
}
func (g *generatorMock) GenerateParams(appSetGenerator *v1alpha1.ApplicationSetGenerator, appSet *v1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) {
func (g *generatorMock) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
args := g.Called(appSetGenerator, appSet)
return args.Get(0).([]map[string]any), args.Error(1)
return args.Get(0).([]map[string]interface{}), args.Error(1)
}
func (g *generatorMock) GetRequeueAfter(appSetGenerator *v1alpha1.ApplicationSetGenerator) time.Duration {
func (g *generatorMock) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
args := g.Called(appSetGenerator)
return args.Get(0).(time.Duration)
}
func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
@@ -1073,33 +1095,33 @@ func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
// of that bug.
listGeneratorMock := &generatorMock{}
listGeneratorMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).Return([]map[string]any{
listGeneratorMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), mock.AnythingOfType("*v1alpha1.ApplicationSet")).Return([]map[string]interface{}{
{"some": "value"},
}, nil)
listGeneratorMock.On("GetTemplate", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator")).Return(&v1alpha1.ApplicationSetTemplate{})
listGeneratorMock.On("GetTemplate", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator")).Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
gitGeneratorSpec := &v1alpha1.GitGenerator{
gitGeneratorSpec := &argoprojiov1alpha1.GitGenerator{
RepoURL: "https://git.example.com",
Files: []v1alpha1.GitFileGeneratorItem{
Files: []argoprojiov1alpha1.GitFileGeneratorItem{
{Path: "some/path.json"},
},
}
repoServiceMock := &mocks.Repos{}
repoServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(map[string][]byte{
repoServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(map[string][]byte{
"some/path.json": []byte("test: content"),
}, nil)
gitGenerator := NewGitGenerator(repoServiceMock, "")
gitGenerator := NewGitGenerator(repoServiceMock)
matrixGenerator := NewMatrixGenerator(map[string]Generator{
"List": listGeneratorMock,
"Git": gitGenerator,
})
matrixGeneratorSpec := &v1alpha1.MatrixGenerator{
Generators: []v1alpha1.ApplicationSetNestedGenerator{
matrixGeneratorSpec := &argoprojiov1alpha1.MatrixGenerator{
Generators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
List: &v1alpha1.ListGenerator{
List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{
{
Raw: []byte(`{"some": "value"}`),
@@ -1112,19 +1134,11 @@ func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
},
},
}
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
appProject := v1alpha1.AppProject{}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
params, err := matrixGenerator.GenerateParams(&v1alpha1.ApplicationSetGenerator{
params, err := matrixGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
Matrix: matrixGeneratorSpec,
}, &v1alpha1.ApplicationSet{}, client)
}, &argoprojiov1alpha1.ApplicationSet{})
require.NoError(t, err)
assert.Equal(t, []map[string]any{{
assert.Equal(t, []map[string]interface{}{{
"path": "some",
"path.basename": "some",
"path.basenameNormalized": "some",

View File

@@ -2,23 +2,23 @@ package generators
import (
"encoding/json"
"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 = (*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,10 +36,10 @@ 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
func (m *MergeGenerator) getParamSetsForAllGenerators(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([][]map[string]interface{}, error) {
var paramSets [][]map[string]interface{}
for i, generator := range generators {
generatorParamSets, err := m.getParams(generator, appSet, client)
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)
}
@@ -50,7 +50,7 @@ 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, EmptyAppSetGeneratorError
}
@@ -59,7 +59,7 @@ func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appl
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)
}
@@ -77,6 +77,7 @@ func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appl
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)
@@ -93,11 +94,11 @@ func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appl
}
}
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
@@ -106,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
}
@@ -116,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)
}
paramSetKeyString := string(paramSetKeyJSON)
paramSetKeyString := string(paramSetKeyJson)
if _, exists := paramSetsByMergeKey[paramSetKeyString]; exists {
return nil, fmt.Errorf("%w. Duplicate key was %s", ErrNonUniqueParamSets, paramSetKeyString)
}
@@ -137,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{
@@ -163,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 {
@@ -210,8 +224,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) {

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,26 +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) {
testCases := []struct {
name string
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
mergeKeys []string
expectedErr error
expected []map[string]any
expected []map[string]interface{}
}{
{
name: "no generators",
@@ -79,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"},
},
},
@@ -90,7 +90,7 @@ func TestMergeGenerate(t *testing.T) {
*getNestedListGenerator(`{"a": "a"}`),
},
mergeKeys: []string{"b"},
expected: []map[string]any{
expected: []map[string]interface{}{
{"a": "a"},
},
},
@@ -101,7 +101,7 @@ func TestMergeGenerate(t *testing.T) {
*getNestedListGenerator(`{"b": "b"}`),
},
mergeKeys: []string{"b"},
expected: []map[string]any{
expected: []map[string]interface{}{
{"a": "a"},
},
},
@@ -119,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"},
@@ -141,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"},
},
@@ -156,7 +156,7 @@ func TestMergeGenerate(t *testing.T) {
appSet := &argoprojiov1alpha1.ApplicationSet{}
mergeGenerator := NewMergeGenerator(
var mergeGenerator = NewMergeGenerator(
map[string]Generator{
"List": &ListGenerator{},
"Matrix": &MatrixGenerator{
@@ -178,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)
@@ -213,9 +213,9 @@ func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
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",
@@ -225,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"},
},
@@ -239,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"},
},
@@ -263,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"},
@@ -283,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"},
},
@@ -297,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"},
@@ -311,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"},
@@ -321,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"},
@@ -339,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,100 +0,0 @@
// Code generated by mockery v2.52.4. DO NOT EDIT.
package mocks
import (
client "sigs.k8s.io/controller-runtime/pkg/client"
mock "github.com/stretchr/testify/mock"
time "time"
v1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
)
// Generator is an autogenerated mock type for the Generator type
type Generator struct {
mock.Mock
}
// GenerateParams provides a mock function with given fields: appSetGenerator, applicationSetInfo, _a2
func (_m *Generator) GenerateParams(appSetGenerator *v1alpha1.ApplicationSetGenerator, applicationSetInfo *v1alpha1.ApplicationSet, _a2 client.Client) ([]map[string]interface{}, error) {
ret := _m.Called(appSetGenerator, applicationSetInfo, _a2)
if len(ret) == 0 {
panic("no return value specified for GenerateParams")
}
var r0 []map[string]interface{}
var r1 error
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) ([]map[string]interface{}, error)); ok {
return rf(appSetGenerator, applicationSetInfo, _a2)
}
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) []map[string]interface{}); ok {
r0 = rf(appSetGenerator, applicationSetInfo, _a2)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]map[string]interface{})
}
}
if rf, ok := ret.Get(1).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) error); ok {
r1 = rf(appSetGenerator, applicationSetInfo, _a2)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetRequeueAfter provides a mock function with given fields: appSetGenerator
func (_m *Generator) GetRequeueAfter(appSetGenerator *v1alpha1.ApplicationSetGenerator) time.Duration {
ret := _m.Called(appSetGenerator)
if len(ret) == 0 {
panic("no return value specified for GetRequeueAfter")
}
var r0 time.Duration
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator) time.Duration); ok {
r0 = rf(appSetGenerator)
} else {
r0 = ret.Get(0).(time.Duration)
}
return r0
}
// GetTemplate provides a mock function with given fields: appSetGenerator
func (_m *Generator) GetTemplate(appSetGenerator *v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate {
ret := _m.Called(appSetGenerator)
if len(ret) == 0 {
panic("no return value specified for GetTemplate")
}
var r0 *v1alpha1.ApplicationSetTemplate
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate); ok {
r0 = rf(appSetGenerator)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.ApplicationSetTemplate)
}
}
return r0
}
// 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
}

View File

@@ -2,7 +2,6 @@ package generators
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
@@ -13,10 +12,10 @@ import (
"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 (
@@ -32,7 +31,7 @@ type PluginGenerator struct {
namespace string
}
func NewPluginGenerator(ctx context.Context, client client.Client, clientset kubernetes.Interface, namespace string) Generator {
func NewPluginGenerator(client client.Client, ctx context.Context, clientset kubernetes.Interface, namespace string) Generator {
g := &PluginGenerator{
client: client,
ctx: ctx,
@@ -56,7 +55,8 @@ func (g *PluginGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Applic
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, EmptyAppSetGeneratorError
}
@@ -94,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
@@ -106,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 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 {
@@ -133,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,
},
@@ -151,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)
}
@@ -165,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))
@@ -189,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,7 +11,7 @@ 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"
@@ -19,25 +19,25 @@ import (
"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",
@@ -47,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",
@@ -73,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"`)},
@@ -93,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",
@@ -103,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",
@@ -133,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",
@@ -141,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"`)},
@@ -155,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",
@@ -165,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",
@@ -191,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"`)},
@@ -215,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",
@@ -225,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",
@@ -250,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"`)},
@@ -271,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",
@@ -281,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",
@@ -304,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": {},
},
},
@@ -321,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",
@@ -331,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",
@@ -343,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",
@@ -358,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",
@@ -370,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",
@@ -385,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",
@@ -410,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"`)},
@@ -431,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",
@@ -441,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"`)},
@@ -459,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"`)},
@@ -475,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 getting plugin from generator: 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",
@@ -506,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"`)},
@@ -522,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 getting plugin from generator: error fetching ConfigMap: configmaps \"\" not found"),
},
{
name: "no baseUrl",
configmap: &corev1.ConfigMap{
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
@@ -535,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",
@@ -561,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"`)},
@@ -577,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 getting plugin from generator: 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",
@@ -590,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"`)},
@@ -608,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"`)},
@@ -624,14 +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 getting plugin from generator: error fetching ConfigMap: token not found in ConfigMap"),
},
}
ctx := t.Context()
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},
@@ -643,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
@@ -654,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))
}
})
@@ -670,7 +673,7 @@ func TestPluginGenerateParams(t *testing.T) {
fakeClientWithCache := fake.NewClientBuilder().WithObjects([]client.Object{testCase.configmap, testCase.secret}...).Build()
pluginGenerator := NewPluginGenerator(ctx, fakeClientWithCache, fakeClient, "default")
var pluginGenerator = NewPluginGenerator(fakeClientWithCache, ctx, fakeClient, "default")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -681,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,18 +2,17 @@ package generators
import (
"context"
"errors"
"fmt"
"strconv"
"time"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/gosimple/slug"
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)
@@ -25,13 +24,19 @@ const (
type PullRequestGenerator struct {
client client.Client
selectServiceProviderFunc func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error)
SCMConfig
auth SCMAuthProviders
scmRootCAPath string
allowedSCMProviders []string
enableSCMProviders bool
}
func NewPullRequestGenerator(client client.Client, scmConfig SCMConfig) Generator {
func NewPullRequestGenerator(client client.Client, auth SCMAuthProviders, scmRootCAPath string, allowedScmProviders []string, enableSCMProviders bool) Generator {
g := &PullRequestGenerator{
client: client,
SCMConfig: scmConfig,
client: client,
auth: auth,
scmRootCAPath: scmRootCAPath,
allowedSCMProviders: allowedScmProviders,
enableSCMProviders: enableSCMProviders,
}
g.selectServiceProviderFunc = g.selectServiceProvider
return g
@@ -51,7 +56,7 @@ func (g *PullRequestGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.A
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, EmptyAppSetGeneratorError
}
@@ -68,9 +73,9 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
pulls, err := pullrequest.ListPullRequests(ctx, svc, appSetGenerator.PullRequest.Filters)
if err != nil {
return nil, fmt.Errorf("error listing repos: %w", err)
return nil, fmt.Errorf("error listing repos: %v", err)
}
params := make([]map[string]any, 0, len(pulls))
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
@@ -96,9 +101,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,
@@ -106,12 +110,6 @@ 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,
}
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)
}
// PR lables will only be supported for Go Template appsets, since fasttemplate will be deprecated.
@@ -137,95 +135,99 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
}
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)
}
}
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)
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.NewGiteaService(token, providerConfig.API, providerConfig.Owner, providerConfig.Repo, providerConfig.Insecure)
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 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) {
// 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)
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(token, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
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
}
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
}

View File

@@ -2,23 +2,23 @@ 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
expected []map[string]interface{}
expectedErr error
applicationSet argoprojiov1alpha1.ApplicationSet
}{
@@ -29,20 +29,17 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
[]*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",
@@ -50,7 +47,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb",
"head_short_sha_7": "089d92c",
"author": "testName",
},
},
expectedErr: nil,
@@ -62,20 +58,17 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
[]*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",
@@ -83,7 +76,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
"head_sha": "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
"head_short_sha": "9b34ff5b",
"head_short_sha_7": "9b34ff5",
"author": "testName",
},
},
expectedErr: nil,
@@ -95,20 +87,17 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
[]*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",
@@ -116,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,
@@ -165,11 +114,11 @@ 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"),
expectedErr: fmt.Errorf("error listing repos: fake error"),
},
{
selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
@@ -178,21 +127,18 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
[]*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",
@@ -201,7 +147,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
"head_short_sha": "089d92cb",
"head_short_sha_7": "089d92c",
"labels": []string{"preview"},
"author": "testName",
},
},
expectedErr: nil,
@@ -219,21 +164,18 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
[]*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",
@@ -241,7 +183,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb",
"head_short_sha_7": "089d92c",
"author": "testName",
},
},
expectedErr: nil,
@@ -259,25 +200,85 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
selectServiceProviderFunc: c.selectFunc,
}
generatorConfig := argoprojiov1alpha1.ApplicationSetGenerator{
PullRequest: &argoprojiov1alpha1.PullRequestGenerator{
Values: c.values,
},
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 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 error
}{
{
name: "Error Github",
@@ -286,6 +287,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: &ErrDisallowedSCMProvider{},
},
{
name: "Error Gitlab",
@@ -294,6 +296,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: &ErrDisallowedSCMProvider{},
},
{
name: "Error Gitea",
@@ -302,6 +305,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: &ErrDisallowedSCMProvider{},
},
{
name: "Error Bitbucket",
@@ -310,6 +314,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: &ErrDisallowedSCMProvider{},
},
}
@@ -319,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, nil, true))
}, true)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -338,17 +343,16 @@ 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.ErrorAs(t, err, testCaseCopy.expectedError)
})
}
}
func TestSCMProviderDisabled_PRGenerator(t *testing.T) {
generator := NewPullRequestGenerator(nil, NewSCMConfig("", []string{}, false, nil, true))
generator := NewPullRequestGenerator(nil, SCMAuthProviders{}, "", []string{}, false)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -365,6 +369,6 @@ func TestSCMProviderDisabled_PRGenerator(t *testing.T) {
},
}
_, err := generator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
_, err := generator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
assert.ErrorIs(t, err, ErrSCMProvidersDisabled)
}

View File

@@ -7,15 +7,16 @@ import (
"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_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)
@@ -28,38 +29,29 @@ type SCMProviderGenerator struct {
client client.Client
// Testing hooks.
overrideProvider scm_provider.SCMProviderService
SCMConfig
}
type SCMConfig struct {
SCMAuthProviders
scmRootCAPath string
allowedSCMProviders []string
enableSCMProviders bool
GitHubApps github_app_auth.Credentials
tokenRefStrictMode bool
}
func NewSCMConfig(scmRootCAPath string, allowedSCMProviders []string, enableSCMProviders bool, gitHubApps github_app_auth.Credentials, tokenRefStrictMode bool) SCMConfig {
return SCMConfig{
type SCMAuthProviders struct {
GitHubApps github_app_auth.Credentials
}
func NewSCMProviderGenerator(client client.Client, providers SCMAuthProviders, scmRootCAPath string, allowedSCMProviders []string, enableSCMProviders bool) Generator {
return &SCMProviderGenerator{
client: client,
SCMAuthProviders: providers,
scmRootCAPath: scmRootCAPath,
allowedSCMProviders: allowedSCMProviders,
enableSCMProviders: enableSCMProviders,
GitHubApps: gitHubApps,
tokenRefStrictMode: tokenRefStrictMode,
}
}
func NewSCMProviderGenerator(client client.Client, scmConfig SCMConfig) Generator {
return &SCMProviderGenerator{
client: client,
SCMConfig: scmConfig,
}
}
// Testing generator
func NewTestSCMProviderGenerator(overrideProvider scm_provider.SCMProviderService) Generator {
return &SCMProviderGenerator{overrideProvider: overrideProvider, SCMConfig: SCMConfig{
enableSCMProviders: true,
}}
return &SCMProviderGenerator{overrideProvider: overrideProvider, enableSCMProviders: true}
}
func (g *SCMProviderGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
@@ -116,7 +108,7 @@ func ScmProviderAllowed(applicationSetInfo *argoprojiov1alpha1.ApplicationSet, g
return NewErrDisallowedSCMProvider(url, allowedScmProviders)
}
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, EmptyAppSetGeneratorError
}
@@ -138,105 +130,81 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
ctx := context.Background()
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 {
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)
}
}
token, err := utils.GetSecretRef(ctx, g.client, providerConfig.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
} else if providerConfig.Gitlab != nil {
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.WillIncludeSharedProjects(), providerConfig.Gitlab.Insecure, g.scmRootCAPath, providerConfig.Gitlab.Topic)
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 {
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)
}
}
switch {
case 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)
}
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 {
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 {
@@ -250,7 +218,7 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
shortSHALength7 = len(repo.SHA)
}
params := map[string]any{
params := map[string]interface{}{
"organization": repo.Organization,
"repository": repo.Repository,
"url": repo.URL,
@@ -272,11 +240,34 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
return paramsArray, nil
}
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)
}
return scm_provider.NewGithubAppProviderFor(
@@ -287,9 +278,9 @@ func (g *SCMProviderGenerator) githubProvider(ctx context.Context, github *argop
)
}
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)
}
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,22 +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 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
}{
{
@@ -38,7 +106,7 @@ func TestSCMProviderGenerateParams(t *testing.T) {
SHA: "59d0",
},
},
expected: []map[string]any{
expected: []map[string]interface{}{
{
"organization": "myorg",
"repository": "repo1",
@@ -79,7 +147,7 @@ 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",
@@ -106,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, enableSCMProviders: true}
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -120,14 +188,15 @@ 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)
}
})
}
}
@@ -136,6 +205,7 @@ func TestAllowedSCMProvider(t *testing.T) {
cases := []struct {
name string
providerConfig *argoprojiov1alpha1.SCMProviderGenerator
expectedError error
}{
{
name: "Error Github",
@@ -144,6 +214,7 @@ func TestAllowedSCMProvider(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: &ErrDisallowedSCMProvider{},
},
{
name: "Error Gitlab",
@@ -152,6 +223,7 @@ func TestAllowedSCMProvider(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: &ErrDisallowedSCMProvider{},
},
{
name: "Error Gitea",
@@ -160,6 +232,7 @@ func TestAllowedSCMProvider(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: &ErrDisallowedSCMProvider{},
},
{
name: "Error Bitbucket",
@@ -168,6 +241,7 @@ func TestAllowedSCMProvider(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: &ErrDisallowedSCMProvider{},
},
{
name: "Error AzureDevops",
@@ -176,6 +250,7 @@ func TestAllowedSCMProvider(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: &ErrDisallowedSCMProvider{},
},
}
@@ -186,16 +261,14 @@ func TestAllowedSCMProvider(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,
allowedSCMProviders: []string{
"github.myorg.com",
"gitlab.myorg.com",
"gitea.myorg.com",
"bitbucket.myorg.com",
"azuredevops.myorg.com",
},
enableSCMProviders: true,
}
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
@@ -209,17 +282,16 @@ 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.ErrorAs(t, err, testCaseCopy.expectedError)
})
}
}
func TestSCMProviderDisabled_SCMGenerator(t *testing.T) {
generator := &SCMProviderGenerator{SCMConfig: SCMConfig{enableSCMProviders: false}}
generator := &SCMProviderGenerator{enableSCMProviders: false}
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -236,6 +308,6 @@ func TestSCMProviderDisabled_SCMGenerator(t *testing.T) {
},
}
_, err := generator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
_, err := generator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
assert.ErrorIs(t, err, ErrSCMProvidersDisabled)
}

View File

@@ -1,5 +1,5 @@
package generators
type SCMGeneratorWithCustomApiUrl interface { //nolint:revive //FIXME(var-naming)
type SCMGeneratorWithCustomApiUrl interface {
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, namespace string, argoCDService services.Repos, dynamicClient dynamic.Interface, scmConfig SCMConfig) map[string]Generator {
terminalGenerators := map[string]Generator{
"List": NewListGenerator(),
"Clusters": NewClusterGenerator(ctx, c, k8sClient, namespace),
"Git": NewGitGenerator(argoCDService, namespace),
"SCMProvider": NewSCMProviderGenerator(c, scmConfig),
"ClusterDecisionResource": NewDuckTypeGenerator(ctx, dynamicClient, k8sClient, namespace),
"PullRequest": NewPullRequestGenerator(c, scmConfig),
"Plugin": NewPluginGenerator(ctx, c, k8sClient, namespace),
}
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,9 +53,10 @@ 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.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,9 +115,10 @@ 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.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,136 +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)
kubectlMetricsServer := kubectl.NewKubectlMetrics()
kubectlMetricsServer.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.NewRequest(http.MethodGet, "/metrics", nil)
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.NewRequest(http.MethodGet, "/metrics", nil)
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

@@ -5,9 +5,9 @@ import (
"net/http"
"github.com/bradleyfalzon/ghinstallation/v2"
"github.com/google/go-github/v66/github"
"github.com/google/go-github/v35/github"
"github.com/argoproj/argo-cd/v3/applicationset/services/github_app_auth"
"github.com/argoproj/argo-cd/v2/applicationset/services/github_app_auth"
)
// Client builds a github client for the given app authentication.
@@ -26,7 +26,7 @@ func Client(g github_app_auth.Authentication, url string) (*github.Client, error
} else {
rt.BaseURL = url
httpClient := http.Client{Transport: rt}
client, err = github.NewClient(&httpClient).WithEnterpriseURLs(url, url)
client, err = github.NewEnterpriseClient(url, url, &httpClient)
if err != nil {
return nil, fmt.Errorf("failed to create github enterprise client: %w", err)
}

View File

@@ -65,7 +65,8 @@ func newClient(baseURL string, options ...ClientOptionFunc) (*Client, error) {
return c, nil
}
func (c *Client) NewRequestWithContext(ctx context.Context, method, path string, body any) (*http.Request, error) {
func (c *Client) NewRequest(method, path string, body interface{}, options []ClientOptionFunc) (*http.Request, error) {
// Make sure the given URL end with a slash
if !strings.HasSuffix(c.baseURL, "/") {
c.baseURL += "/"
@@ -82,7 +83,7 @@ func (c *Client) NewRequestWithContext(ctx context.Context, method, path string,
}
}
req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, buf)
req, err := http.NewRequest(method, c.baseURL+path, buf)
if err != nil {
return nil, err
}
@@ -102,7 +103,7 @@ func (c *Client) NewRequestWithContext(ctx context.Context, method, path string,
return req, nil
}
func (c *Client) Do(req *http.Request, v any) (*http.Response, error) {
func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*http.Response, error) {
resp, err := c.client.Do(req)
if err != nil {
return nil, err
@@ -134,16 +135,17 @@ func (c *Client) Do(req *http.Request, v any) (*http.Response, error) {
// CheckResponse checks the API response for errors, and returns them if present.
func CheckResponse(resp *http.Response) error {
if c := resp.StatusCode; http.StatusOK <= c && c < http.StatusMultipleChoices {
if c := resp.StatusCode; 200 <= c && c <= 299 {
return nil
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("API error with status code %d: %w", resp.StatusCode, err)
return fmt.Errorf("API error with status code %d: %v", resp.StatusCode, err)
}
var raw map[string]any
var raw map[string]interface{}
if err := json.Unmarshal(data, &raw); err != nil {
return fmt.Errorf("API error with status code %d: %s", resp.StatusCode, string(data))
}

View File

@@ -2,7 +2,7 @@ package http
import (
"bytes"
"errors"
"context"
"fmt"
"io"
"net/http"
@@ -10,32 +10,36 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestClient(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte("Hello, World!"))
if err != nil {
assert.NoError(t, fmt.Errorf("Error Write %w", err))
assert.NoError(t, fmt.Errorf("Error Write %v", err))
}
}))
defer server.Close()
var clientOptionFns []ClientOptionFunc
_, err := NewClient(server.URL, clientOptionFns...)
require.NoError(t, err, "Failed to create client")
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
}
func TestClientDo(t *testing.T) {
ctx := context.Background()
for _, c := range []struct {
name string
params map[string]string
content []byte
fakeServer *httptest.Server
clientOptionFns []ClientOptionFunc
expected []map[string]any
expected []map[string]interface{}
expectedCode int
expectedError error
}{
@@ -45,7 +49,7 @@ func TestClientDo(t *testing.T) {
"pkey1": "val1",
"pkey2": "val2",
},
fakeServer: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
fakeServer: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte(`[{
"key1": "val1",
@@ -58,23 +62,23 @@ func TestClientDo(t *testing.T) {
"key3": 123
}]`))
if err != nil {
assert.NoError(t, fmt.Errorf("Error Write %w", err))
assert.NoError(t, fmt.Errorf("Error Write %v", err))
}
})),
clientOptionFns: nil,
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),
},
},
expectedCode: http.StatusOK,
expectedCode: 200,
expectedError: nil,
},
{
@@ -101,13 +105,13 @@ func TestClientDo(t *testing.T) {
"key3": 123
}]`))
if err != nil {
assert.NoError(t, fmt.Errorf("Error Write %w", err))
assert.NoError(t, fmt.Errorf("Error Write %v", err))
}
})),
clientOptionFns: nil,
expected: []map[string]any(nil),
expectedCode: http.StatusUnauthorized,
expectedError: errors.New("API error with status code 401: "),
expected: []map[string]interface{}(nil),
expectedCode: 401,
expectedError: fmt.Errorf("API error with status code 401: "),
},
} {
cc := c
@@ -115,20 +119,26 @@ func TestClientDo(t *testing.T) {
defer cc.fakeServer.Close()
client, err := NewClient(cc.fakeServer.URL, cc.clientOptionFns...)
require.NoError(t, err, "NewClient returned unexpected error")
req, err := client.NewRequestWithContext(t.Context(), http.MethodPost, "", cc.params)
require.NoError(t, err, "NewRequest returned unexpected error")
if err != nil {
t.Fatalf("NewClient returned unexpected error: %v", err)
}
var data []map[string]any
req, err := client.NewRequest("POST", "", cc.params, nil)
resp, err := client.Do(req, &data)
if err != nil {
t.Fatalf("NewRequest returned unexpected error: %v", err)
}
var data []map[string]interface{}
resp, err := client.Do(ctx, req, &data)
if cc.expectedError != nil {
assert.EqualError(t, err, cc.expectedError.Error())
} else {
assert.Equal(t, cc.expectedCode, resp.StatusCode)
assert.Equal(t, cc.expected, data)
assert.Equal(t, resp.StatusCode, cc.expectedCode)
assert.Equal(t, data, cc.expected)
assert.NoError(t, err)
}
})
@@ -142,5 +152,12 @@ func TestCheckResponse(t *testing.T) {
}
err := CheckResponse(resp)
require.EqualError(t, err, "API error with status code 400: invalid_request")
if err == nil {
t.Error("Expected an error, got nil")
}
expected := "API error with status code 400: invalid_request"
if err.Error() != expected {
t.Errorf("Expected error '%s', got '%s'", expected, err.Error())
}
}

View File

@@ -1,4 +1,4 @@
// Code generated by mockery v2.52.4. DO NOT EDIT.
// Code generated by mockery v2.25.1. DO NOT EDIT.
package mocks
@@ -13,29 +13,25 @@ type Repos struct {
mock.Mock
}
// GetDirectories provides a mock function with given fields: ctx, repoURL, revision, project, noRevisionCache, verifyCommit
func (_m *Repos) GetDirectories(ctx context.Context, repoURL string, revision string, project string, noRevisionCache bool, verifyCommit bool) ([]string, error) {
ret := _m.Called(ctx, repoURL, revision, project, noRevisionCache, verifyCommit)
if len(ret) == 0 {
panic("no return value specified for GetDirectories")
}
// GetDirectories provides a mock function with given fields: ctx, repoURL, revision, noRevisionCache
func (_m *Repos) GetDirectories(ctx context.Context, repoURL string, revision string, noRevisionCache bool) ([]string, error) {
ret := _m.Called(ctx, repoURL, revision, noRevisionCache)
var r0 []string
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool, bool) ([]string, error)); ok {
return rf(ctx, repoURL, revision, project, noRevisionCache, verifyCommit)
if rf, ok := ret.Get(0).(func(context.Context, string, string, bool) ([]string, error)); ok {
return rf(ctx, repoURL, revision, noRevisionCache)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool, bool) []string); ok {
r0 = rf(ctx, repoURL, revision, project, noRevisionCache, verifyCommit)
if rf, ok := ret.Get(0).(func(context.Context, string, string, bool) []string); ok {
r0 = rf(ctx, repoURL, revision, noRevisionCache)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, bool, bool) error); ok {
r1 = rf(ctx, repoURL, revision, project, noRevisionCache, verifyCommit)
if rf, ok := ret.Get(1).(func(context.Context, string, string, bool) error); ok {
r1 = rf(ctx, repoURL, revision, noRevisionCache)
} else {
r1 = ret.Error(1)
}
@@ -43,29 +39,25 @@ func (_m *Repos) GetDirectories(ctx context.Context, repoURL string, revision st
return r0, r1
}
// GetFiles provides a mock function with given fields: ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit
func (_m *Repos) GetFiles(ctx context.Context, repoURL string, revision string, project string, pattern string, noRevisionCache bool, verifyCommit bool) (map[string][]byte, error) {
ret := _m.Called(ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit)
if len(ret) == 0 {
panic("no return value specified for GetFiles")
}
// GetFiles provides a mock function with given fields: ctx, repoURL, revision, pattern, noRevisionCache
func (_m *Repos) GetFiles(ctx context.Context, repoURL string, revision string, pattern string, noRevisionCache bool) (map[string][]byte, error) {
ret := _m.Called(ctx, repoURL, revision, pattern, noRevisionCache)
var r0 map[string][]byte
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, bool, bool) (map[string][]byte, error)); ok {
return rf(ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit)
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool) (map[string][]byte, error)); ok {
return rf(ctx, repoURL, revision, pattern, noRevisionCache)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, bool, bool) map[string][]byte); ok {
r0 = rf(ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit)
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool) map[string][]byte); ok {
r0 = rf(ctx, repoURL, revision, pattern, noRevisionCache)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string][]byte)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string, bool, bool) error); ok {
r1 = rf(ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit)
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, bool) error); ok {
r1 = rf(ctx, repoURL, revision, pattern, noRevisionCache)
} else {
r1 = ret.Error(1)
}
@@ -73,12 +65,13 @@ func (_m *Repos) GetFiles(ctx context.Context, repoURL string, revision string,
return r0, r1
}
// NewRepos creates a new instance of Repos. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewRepos(t interface {
type mockConstructorTestingTNewRepos interface {
mock.TestingT
Cleanup(func())
}) *Repos {
}
// NewRepos creates a new instance of Repos. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewRepos(t mockConstructorTestingTNewRepos) *Repos {
mock := &Repos{}
mock.Mock.Test(t)

View File

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

View File

@@ -5,8 +5,8 @@ import (
"fmt"
"net/http"
internalhttp "github.com/argoproj/argo-cd/v3/applicationset/services/internal/http"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
internalhttp "github.com/argoproj/argo-cd/v2/applicationset/services/internal/http"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
// ServiceRequest is the request object sent to the plugin service.
@@ -20,7 +20,7 @@ type ServiceRequest struct {
type Output struct {
// Parameters is the list of parameter sets returned by the plugin.
Parameters []map[string]any `json:"parameters"`
Parameters []map[string]interface{} `json:"parameters"`
}
// ServiceResponse is the response object returned by the plugin service.
@@ -34,7 +34,7 @@ type Service struct {
appSetName string
}
func NewPluginService(appSetName string, baseURL string, token string, requestTimeout int) (*Service, error) {
func NewPluginService(ctx context.Context, appSetName string, baseURL string, token string, requestTimeout int) (*Service, error) {
var clientOptionFns []internalhttp.ClientOptionFunc
clientOptionFns = append(clientOptionFns, internalhttp.WithToken(token))
@@ -45,7 +45,7 @@ func NewPluginService(appSetName string, baseURL string, token string, requestTi
client, err := internalhttp.NewClient(baseURL, clientOptionFns...)
if err != nil {
return nil, fmt.Errorf("error creating plugin client: %w", err)
return nil, fmt.Errorf("error creating plugin client: %v", err)
}
return &Service{
@@ -55,16 +55,18 @@ func NewPluginService(appSetName string, baseURL string, token string, requestTi
}
func (p *Service) List(ctx context.Context, parameters v1alpha1.PluginParameters) (*ServiceResponse, error) {
req, err := p.client.NewRequestWithContext(ctx, http.MethodPost, "api/v1/getparams.execute", ServiceRequest{ApplicationSetName: p.appSetName, Input: v1alpha1.PluginInput{Parameters: parameters}})
req, err := p.client.NewRequest(http.MethodPost, "api/v1/getparams.execute", ServiceRequest{ApplicationSetName: p.appSetName, Input: v1alpha1.PluginInput{Parameters: parameters}}, nil)
if err != nil {
return nil, fmt.Errorf("NewRequest returned unexpected error: %w", err)
return nil, fmt.Errorf("NewRequest returned unexpected error: %v", err)
}
var data ServiceResponse
_, err = p.client.Do(req, &data)
_, err = p.client.Do(ctx, req, &data)
if err != nil {
return nil, fmt.Errorf("error get api '%s': %w", p.appSetName, err)
return nil, fmt.Errorf("error get api '%s': %v", p.appSetName, err)
}
return &data, err

View File

@@ -1,6 +1,7 @@
package plugin
import (
"context"
"encoding/json"
"fmt"
"net/http"
@@ -8,7 +9,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPlugin(t *testing.T) {
@@ -23,21 +23,30 @@ func TestPlugin(t *testing.T) {
return
}
_, err := w.Write([]byte(expectedJSON))
if err != nil {
assert.NoError(t, fmt.Errorf("Error Write %w", err))
assert.NoError(t, fmt.Errorf("Error Write %v", err))
}
})
ts := httptest.NewServer(handler)
defer ts.Close()
client, err := NewPluginService("plugin-test", ts.URL, token, 0)
require.NoError(t, err)
client, err := NewPluginService(context.Background(), "plugin-test", ts.URL, token, 0)
data, err := client.List(t.Context(), nil)
require.NoError(t, err)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
data, err := client.List(context.Background(), nil)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
var expectedData ServiceResponse
err = json.Unmarshal([]byte(expectedJSON), &expectedData)
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, &expectedData, data)
}

View File

@@ -1,9 +1,10 @@
package plugin
import (
"fmt"
"strings"
"github.com/argoproj/argo-cd/v3/common"
"github.com/argoproj/argo-cd/v2/common"
)
// ParseSecretKey retrieves secret appSetName if different from common ArgoCDSecretName.
@@ -11,7 +12,7 @@ func ParseSecretKey(key string) (secretName string, tokenKey string) {
if strings.Contains(key, ":") {
parts := strings.Split(key, ":")
secretName = parts[0][1:]
tokenKey = "$" + parts[1]
tokenKey = fmt.Sprintf("$%s", parts[1])
} else {
secretName = common.ArgoCDSecretName
tokenKey = key

View File

@@ -6,8 +6,8 @@ import (
"strings"
"github.com/microsoft/azure-devops-go-api/azuredevops"
"github.com/microsoft/azure-devops-go-api/azuredevops/core"
"github.com/microsoft/azure-devops-go-api/azuredevops/git"
core "github.com/microsoft/azure-devops-go-api/azuredevops/core"
git "github.com/microsoft/azure-devops-go-api/azuredevops/git"
)
const AZURE_DEVOPS_DEFAULT_URL = "https://dev.azure.com"
@@ -36,19 +36,17 @@ type AzureDevOpsService struct {
labels []string
}
var (
_ PullRequestService = (*AzureDevOpsService)(nil)
_ AzureDevOpsClientFactory = &devopsFactoryImpl{}
)
var _ PullRequestService = (*AzureDevOpsService)(nil)
var _ AzureDevOpsClientFactory = &devopsFactoryImpl{}
func NewAzureDevOpsService(token, url, organization, project, repo string, labels []string) (PullRequestService, error) {
organizationURL := buildURL(url, organization)
func NewAzureDevOpsService(ctx context.Context, token, url, organization, project, repo string, labels []string) (PullRequestService, error) {
organizationUrl := buildURL(url, organization)
var connection *azuredevops.Connection
if token == "" {
connection = azuredevops.NewAnonymousConnection(organizationURL)
connection = azuredevops.NewAnonymousConnection(organizationUrl)
} else {
connection = azuredevops.NewPatConnection(organizationURL, token)
connection = azuredevops.NewPatConnection(organizationUrl, token)
}
return &AzureDevOpsService{
@@ -82,7 +80,6 @@ func (a *AzureDevOpsService) List(ctx context.Context) ([]*PullRequest, error) {
pr.Repository.Name == nil ||
pr.PullRequestId == nil ||
pr.SourceRefName == nil ||
pr.TargetRefName == nil ||
pr.LastMergeSourceCommit == nil ||
pr.LastMergeSourceCommit.CommitId == nil {
continue
@@ -95,13 +92,10 @@ func (a *AzureDevOpsService) List(ctx context.Context) ([]*PullRequest, error) {
if *pr.Repository.Name == a.repo {
pullRequests = append(pullRequests, &PullRequest{
Number: *pr.PullRequestId,
Title: *pr.Title,
Branch: strings.Replace(*pr.SourceRefName, "refs/heads/", "", 1),
TargetBranch: strings.Replace(*pr.TargetRefName, "refs/heads/", "", 1),
HeadSHA: *pr.LastMergeSourceCommit.CommitId,
Labels: azureDevOpsLabels,
Author: strings.Split(*pr.CreatedBy.UniqueName, "@")[0], // Get the part before the @ in the email-address
Number: *pr.PullRequestId,
Branch: strings.Replace(*pr.SourceRefName, "refs/heads/", "", 1),
HeadSHA: *pr.LastMergeSourceCommit.CommitId,
Labels: azureDevOpsLabels,
})
}
}

View File

@@ -4,15 +4,12 @@ import (
"context"
"testing"
"github.com/microsoft/azure-devops-go-api/azuredevops/webapi"
"github.com/microsoft/azure-devops-go-api/azuredevops/core"
"github.com/microsoft/azure-devops-go-api/azuredevops/git"
git "github.com/microsoft/azure-devops-go-api/azuredevops/git"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
azureMock "github.com/argoproj/argo-cd/v3/applicationset/services/scm_provider/azure_devops/git/mocks"
azureMock "github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider/azure_devops/git/mocks"
)
func createBoolPtr(x bool) *bool {
@@ -31,10 +28,6 @@ func createLabelsPtr(x []core.WebApiTagDefinition) *[]core.WebApiTagDefinition {
return &x
}
func createUniqueNamePtr(x string) *string {
return &x
}
type AzureClientFactoryMock struct {
mock *mock.Mock
}
@@ -61,28 +54,21 @@ func (m *AzureClientFactoryMock) GetClient(ctx context.Context) (git.Client, err
func TestListPullRequest(t *testing.T) {
teamProject := "myorg_project"
repoName := "myorg_project_repo"
prID := 123
prTitle := "feat(123)"
prHeadSha := "cd4973d9d14a08ffe6b641a89a68891d6aac8056"
ctx := t.Context()
uniqueName := "testName"
pr_id := 123
pr_head_sha := "cd4973d9d14a08ffe6b641a89a68891d6aac8056"
ctx := context.Background()
pullRequestMock := []git.GitPullRequest{
{
PullRequestId: createIntPtr(prID),
Title: createStringPtr(prTitle),
PullRequestId: createIntPtr(pr_id),
SourceRefName: createStringPtr("refs/heads/feature-branch"),
TargetRefName: createStringPtr("refs/heads/main"),
LastMergeSourceCommit: &git.GitCommitRef{
CommitId: createStringPtr(prHeadSha),
CommitId: createStringPtr(pr_head_sha),
},
Labels: &[]core.WebApiTagDefinition{},
Repository: &git.GitRepository{
Name: createStringPtr(repoName),
},
CreatedBy: &webapi.IdentityRef{
UniqueName: createUniqueNamePtr(uniqueName + "@example.com"),
},
},
}
@@ -104,14 +90,11 @@ func TestListPullRequest(t *testing.T) {
}
list, err := provider.List(ctx)
require.NoError(t, err)
assert.Len(t, list, 1)
assert.NoError(t, err)
assert.Equal(t, 1, len(list))
assert.Equal(t, "feature-branch", list[0].Branch)
assert.Equal(t, "main", list[0].TargetBranch)
assert.Equal(t, prHeadSha, list[0].HeadSHA)
assert.Equal(t, "feat(123)", list[0].Title)
assert.Equal(t, prID, list[0].Number)
assert.Equal(t, uniqueName, list[0].Author)
assert.Equal(t, pr_head_sha, list[0].HeadSHA)
assert.Equal(t, pr_id, list[0].Number)
}
func TestConvertLabes(t *testing.T) {
@@ -232,7 +215,7 @@ func TestBuildURL(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := buildURL(tc.url, tc.organization)
assert.Equal(t, tc.expected, result)
assert.Equal(t, result, tc.expected)
})
}
}

View File

@@ -3,7 +3,6 @@ package pull_request
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
@@ -18,9 +17,7 @@ type BitbucketCloudService struct {
type BitbucketCloudPullRequest struct {
ID int `json:"id"`
Title string `json:"title"`
Source BitbucketCloudPullRequestSource `json:"source"`
Author BitbucketCloudPullRequestAuthor `json:"author"`
}
type BitbucketCloudPullRequestSource struct {
@@ -36,11 +33,6 @@ type BitbucketCloudPullRequestSourceCommit struct {
Hash string `json:"hash"`
}
// Also have display_name and uuid, but don't plan to use them.
type BitbucketCloudPullRequestAuthor struct {
Nickname string `json:"nickname"`
}
type PullRequestResponse struct {
Page int32 `json:"page"`
Size int32 `json:"size"`
@@ -52,7 +44,7 @@ type PullRequestResponse struct {
var _ PullRequestService = (*BitbucketCloudService)(nil)
func parseURL(uri string) (*url.URL, error) {
func parseUrl(uri string) (*url.URL, error) {
if uri == "" {
uri = "https://api.bitbucket.org/2.0"
}
@@ -65,10 +57,10 @@ func parseURL(uri string) (*url.URL, error) {
return url, nil
}
func NewBitbucketCloudServiceBasicAuth(baseURL, username, password, owner, repositorySlug string) (PullRequestService, error) {
url, err := parseURL(baseURL)
func NewBitbucketCloudServiceBasicAuth(baseUrl, username, password, owner, repositorySlug string) (PullRequestService, error) {
url, err := parseUrl(baseUrl)
if err != nil {
return nil, fmt.Errorf("error parsing base url of %s for %s/%s: %w", baseURL, owner, repositorySlug, err)
return nil, fmt.Errorf("error parsing base url of %s for %s/%s: %v", baseUrl, owner, repositorySlug, err)
}
bitbucketClient := bitbucket.NewBasicAuth(username, password)
@@ -81,10 +73,10 @@ func NewBitbucketCloudServiceBasicAuth(baseURL, username, password, owner, repos
}, nil
}
func NewBitbucketCloudServiceBearerToken(baseURL, bearerToken, owner, repositorySlug string) (PullRequestService, error) {
url, err := parseURL(baseURL)
func NewBitbucketCloudServiceBearerToken(baseUrl, bearerToken, owner, repositorySlug string) (PullRequestService, error) {
url, err := parseUrl(baseUrl)
if err != nil {
return nil, fmt.Errorf("error parsing base url of %s for %s/%s: %w", baseURL, owner, repositorySlug, err)
return nil, fmt.Errorf("error parsing base url of %s for %s/%s: %v", baseUrl, owner, repositorySlug, err)
}
bitbucketClient := bitbucket.NewOAuthbearerToken(bearerToken)
@@ -97,9 +89,9 @@ func NewBitbucketCloudServiceBearerToken(baseURL, bearerToken, owner, repository
}, nil
}
func NewBitbucketCloudServiceNoAuth(baseURL, owner, repositorySlug string) (PullRequestService, error) {
func NewBitbucketCloudServiceNoAuth(baseUrl, owner, repositorySlug string) (PullRequestService, error) {
// There is currently no method to explicitly not require auth
return NewBitbucketCloudServiceBearerToken(baseURL, "", owner, repositorySlug)
return NewBitbucketCloudServiceBearerToken(baseUrl, "", owner, repositorySlug)
}
func (b *BitbucketCloudService) List(_ context.Context) ([]*PullRequest, error) {
@@ -110,37 +102,35 @@ func (b *BitbucketCloudService) List(_ context.Context) ([]*PullRequest, error)
response, err := b.client.Repositories.PullRequests.Gets(opts)
if err != nil {
return nil, fmt.Errorf("error listing pull requests for %s/%s: %w", b.owner, b.repositorySlug, err)
return nil, fmt.Errorf("error listing pull requests for %s/%s: %v", b.owner, b.repositorySlug, err)
}
resp, ok := response.(map[string]any)
resp, ok := response.(map[string]interface{})
if !ok {
return nil, errors.New("unknown type returned from bitbucket pull requests")
return nil, fmt.Errorf("unknown type returned from bitbucket pull requests")
}
repoArray, ok := resp["values"].([]any)
repoArray, ok := resp["values"].([]interface{})
if !ok {
return nil, errors.New("unknown type returned from response values")
return nil, fmt.Errorf("unknown type returned from response values")
}
jsonStr, err := json.Marshal(repoArray)
if err != nil {
return nil, fmt.Errorf("error marshalling response body to json: %w", err)
return nil, fmt.Errorf("error marshalling response body to json: %v", err)
}
var pulls []BitbucketCloudPullRequest
if err := json.Unmarshal(jsonStr, &pulls); err != nil {
return nil, fmt.Errorf("error unmarshalling json to type '[]BitbucketCloudPullRequest': %w", err)
return nil, fmt.Errorf("error unmarshalling json to type '[]BitbucketCloudPullRequest': %v", err)
}
pullRequests := []*PullRequest{}
for _, pull := range pulls {
pullRequests = append(pullRequests, &PullRequest{
Number: pull.ID,
Title: pull.Title,
Branch: pull.Source.Branch.Name,
HeadSHA: pull.Source.Commit.Hash,
Author: pull.Author.Nickname,
})
}

View File

@@ -1,6 +1,7 @@
package pull_request
import (
"context"
"fmt"
"io"
"net/http"
@@ -8,13 +9,11 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func defaultHandlerCloud(t *testing.T) func(http.ResponseWriter, *http.Request) {
t.Helper()
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var err error
@@ -27,7 +26,6 @@ func defaultHandlerCloud(t *testing.T) func(http.ResponseWriter, *http.Request)
"values": [
{
"id": 101,
"title": "feat(foo-bar)",
"source": {
"branch": {
"name": "feature/foo-bar"
@@ -36,9 +34,6 @@ func defaultHandlerCloud(t *testing.T) func(http.ResponseWriter, *http.Request)
"type": "commit",
"hash": "1a8dd249c04a"
}
},
"author": {
"nickname": "testName"
}
}
]
@@ -53,29 +48,29 @@ func defaultHandlerCloud(t *testing.T) func(http.ResponseWriter, *http.Request)
}
func TestParseUrlEmptyUrl(t *testing.T) {
url, err := parseURL("")
bitbucketURL, _ := url.Parse("https://api.bitbucket.org/2.0")
url, err := parseUrl("")
bitbucketUrl, _ := url.Parse("https://api.bitbucket.org/2.0")
require.NoError(t, err)
assert.Equal(t, bitbucketURL, url)
assert.NoError(t, err)
assert.Equal(t, bitbucketUrl, url)
}
func TestInvalidBaseUrlBasicAuthCloud(t *testing.T) {
_, err := NewBitbucketCloudServiceBasicAuth("http:// example.org", "user", "password", "OWNER", "REPO")
require.Error(t, err)
assert.Error(t, err)
}
func TestInvalidBaseUrlBearerTokenCloud(t *testing.T) {
_, err := NewBitbucketCloudServiceBearerToken("http:// example.org", "TOKEN", "OWNER", "REPO")
require.Error(t, err)
assert.Error(t, err)
}
func TestInvalidBaseUrlNoAuthCloud(t *testing.T) {
_, err := NewBitbucketCloudServiceNoAuth("http:// example.org", "OWNER", "REPO")
require.Error(t, err)
assert.Error(t, err)
}
func TestListPullRequestBearerTokenCloud(t *testing.T) {
@@ -85,15 +80,13 @@ func TestListPullRequestBearerTokenCloud(t *testing.T) {
}))
defer ts.Close()
svc, err := NewBitbucketCloudServiceBearerToken(ts.URL, "TOKEN", "OWNER", "REPO")
require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
assert.NoError(t, err)
assert.Equal(t, 1, len(pullRequests))
assert.Equal(t, 101, pullRequests[0].Number)
assert.Equal(t, "feat(foo-bar)", pullRequests[0].Title)
assert.Equal(t, "feature/foo-bar", pullRequests[0].Branch)
assert.Equal(t, "1a8dd249c04a", pullRequests[0].HeadSHA)
assert.Equal(t, "testName", pullRequests[0].Author)
}
func TestListPullRequestNoAuthCloud(t *testing.T) {
@@ -103,15 +96,13 @@ func TestListPullRequestNoAuthCloud(t *testing.T) {
}))
defer ts.Close()
svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
assert.NoError(t, err)
assert.Equal(t, 1, len(pullRequests))
assert.Equal(t, 101, pullRequests[0].Number)
assert.Equal(t, "feat(foo-bar)", pullRequests[0].Title)
assert.Equal(t, "feature/foo-bar", pullRequests[0].Branch)
assert.Equal(t, "1a8dd249c04a", pullRequests[0].HeadSHA)
assert.Equal(t, "testName", pullRequests[0].Author)
}
func TestListPullRequestBasicAuthCloud(t *testing.T) {
@@ -121,15 +112,13 @@ func TestListPullRequestBasicAuthCloud(t *testing.T) {
}))
defer ts.Close()
svc, err := NewBitbucketCloudServiceBasicAuth(ts.URL, "user", "password", "OWNER", "REPO")
require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
assert.NoError(t, err)
assert.Equal(t, 1, len(pullRequests))
assert.Equal(t, 101, pullRequests[0].Number)
assert.Equal(t, "feat(foo-bar)", pullRequests[0].Title)
assert.Equal(t, "feature/foo-bar", pullRequests[0].Branch)
assert.Equal(t, "1a8dd249c04a", pullRequests[0].HeadSHA)
assert.Equal(t, "testName", pullRequests[0].Author)
}
func TestListPullRequestPaginationCloud(t *testing.T) {
@@ -146,7 +135,6 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
"values": [
{
"id": 101,
"title": "feat(101)",
"source": {
"branch": {
"name": "feature-101"
@@ -155,14 +143,10 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
"type": "commit",
"hash": "1a8dd249c04a"
}
},
"author": {
"nickname": "testName"
}
},
{
"id": 102,
"title": "feat(102)",
"source": {
"branch": {
"name": "feature-102"
@@ -171,9 +155,6 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
"type": "commit",
"hash": "4cf807e67a6d"
}
},
"author": {
"nickname": "testName"
}
}
]
@@ -187,7 +168,6 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
"values": [
{
"id": 103,
"title": "feat(103)",
"source": {
"branch": {
"name": "feature-103"
@@ -196,9 +176,6 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
"type": "commit",
"hash": "6344d9623e3b"
}
},
"author": {
"nickname": "testName"
}
}
]
@@ -212,41 +189,35 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
}))
defer ts.Close()
svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err)
assert.Len(t, pullRequests, 3)
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
assert.NoError(t, err)
assert.Equal(t, 3, len(pullRequests))
assert.Equal(t, PullRequest{
Number: 101,
Title: "feat(101)",
Branch: "feature-101",
HeadSHA: "1a8dd249c04a",
Author: "testName",
}, *pullRequests[0])
assert.Equal(t, PullRequest{
Number: 102,
Title: "feat(102)",
Branch: "feature-102",
HeadSHA: "4cf807e67a6d",
Author: "testName",
}, *pullRequests[1])
assert.Equal(t, PullRequest{
Number: 103,
Title: "feat(103)",
Branch: "feature-103",
HeadSHA: "6344d9623e3b",
Author: "testName",
}, *pullRequests[2])
}
func TestListResponseErrorCloud(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500)
}))
defer ts.Close()
svc, _ := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
_, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.Error(t, err)
_, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
assert.Error(t, err)
}
func TestListResponseMalformedCloud(t *testing.T) {
@@ -269,8 +240,8 @@ func TestListResponseMalformedCloud(t *testing.T) {
}))
defer ts.Close()
svc, _ := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
_, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.Error(t, err)
_, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
assert.Error(t, err)
}
func TestListResponseMalformedValuesCloud(t *testing.T) {
@@ -293,8 +264,8 @@ func TestListResponseMalformedValuesCloud(t *testing.T) {
}))
defer ts.Close()
svc, _ := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
_, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.Error(t, err)
_, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
assert.Error(t, err)
}
func TestListResponseEmptyCloud(t *testing.T) {
@@ -317,9 +288,9 @@ func TestListResponseEmptyCloud(t *testing.T) {
}))
defer ts.Close()
svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err)
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
assert.NoError(t, err)
assert.Empty(t, pullRequests)
}
@@ -337,7 +308,6 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
"values": [
{
"id": 101,
"title": "feat(101)",
"source": {
"branch": {
"name": "feature-101"
@@ -346,14 +316,10 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
"type": "commit",
"hash": "1a8dd249c04a"
}
},
"author": {
"nickname": "testName"
}
},
{
"id": 200,
"title": "feat(200)",
"source": {
"branch": {
"name": "feature-200"
@@ -362,9 +328,6 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
"type": "commit",
"hash": "4cf807e67a6d"
}
},
"author": {
"nickname": "testName"
}
}
]
@@ -378,7 +341,6 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
"values": [
{
"id": 102,
"title": "feat(102)",
"source": {
"branch": {
"name": "feature-102"
@@ -387,9 +349,6 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
"type": "commit",
"hash": "6344d9623e3b"
}
},
"author": {
"nickname": "testName"
}
}
]
@@ -404,54 +363,48 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
defer ts.Close()
regexp := `feature-1[\d]{2}`
svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{
BranchMatch: &regexp,
},
})
require.NoError(t, err)
assert.Len(t, pullRequests, 2)
assert.NoError(t, err)
assert.Equal(t, 2, len(pullRequests))
assert.Equal(t, PullRequest{
Number: 101,
Title: "feat(101)",
Branch: "feature-101",
HeadSHA: "1a8dd249c04a",
Author: "testName",
}, *pullRequests[0])
assert.Equal(t, PullRequest{
Number: 102,
Title: "feat(102)",
Branch: "feature-102",
HeadSHA: "6344d9623e3b",
Author: "testName",
}, *pullRequests[1])
regexp = `.*2$`
svc, err = NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
require.NoError(t, err)
pullRequests, err = ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{
assert.NoError(t, err)
pullRequests, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{
BranchMatch: &regexp,
},
})
require.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.NoError(t, err)
assert.Equal(t, 1, len(pullRequests))
assert.Equal(t, PullRequest{
Number: 102,
Title: "feat(102)",
Branch: "feature-102",
HeadSHA: "6344d9623e3b",
Author: "testName",
}, *pullRequests[0])
regexp = `[\d{2}`
svc, err = NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
require.NoError(t, err)
_, err = ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{
assert.NoError(t, err)
_, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{
BranchMatch: &regexp,
},
})
require.Error(t, err)
assert.Error(t, err)
}

View File

@@ -3,12 +3,10 @@ package pull_request
import (
"context"
"fmt"
"net/http"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
bitbucketv1 "github.com/gfleury/go-bitbucket-v1"
log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v3/applicationset/utils"
)
type BitbucketService struct {
@@ -21,7 +19,7 @@ type BitbucketService struct {
var _ PullRequestService = (*BitbucketService)(nil)
func NewBitbucketServiceBasicAuth(ctx context.Context, username, password, url, projectKey, repositorySlug string, scmRootCAPath string, insecure bool, caCerts []byte) (PullRequestService, error) {
func NewBitbucketServiceBasicAuth(ctx context.Context, username, password, url, projectKey, repositorySlug string) (PullRequestService, error) {
bitbucketConfig := bitbucketv1.NewConfiguration(url)
// Avoid the XSRF check
bitbucketConfig.AddDefaultHeader("x-atlassian-token", "no-check")
@@ -31,29 +29,15 @@ func NewBitbucketServiceBasicAuth(ctx context.Context, username, password, url,
UserName: username,
Password: password,
})
return newBitbucketService(ctx, bitbucketConfig, projectKey, repositorySlug, scmRootCAPath, insecure, caCerts)
return newBitbucketService(ctx, bitbucketConfig, projectKey, repositorySlug)
}
func NewBitbucketServiceBearerToken(ctx context.Context, bearerToken, url, projectKey, repositorySlug string, scmRootCAPath string, insecure bool, caCerts []byte) (PullRequestService, error) {
bitbucketConfig := bitbucketv1.NewConfiguration(url)
// Avoid the XSRF check
bitbucketConfig.AddDefaultHeader("x-atlassian-token", "no-check")
bitbucketConfig.AddDefaultHeader("x-requested-with", "XMLHttpRequest")
ctx = context.WithValue(ctx, bitbucketv1.ContextAccessToken, bearerToken)
return newBitbucketService(ctx, bitbucketConfig, projectKey, repositorySlug, scmRootCAPath, insecure, caCerts)
func NewBitbucketServiceNoAuth(ctx context.Context, url, projectKey, repositorySlug string) (PullRequestService, error) {
return newBitbucketService(ctx, bitbucketv1.NewConfiguration(url), projectKey, repositorySlug)
}
func NewBitbucketServiceNoAuth(ctx context.Context, url, projectKey, repositorySlug string, scmRootCAPath string, insecure bool, caCerts []byte) (PullRequestService, error) {
return newBitbucketService(ctx, bitbucketv1.NewConfiguration(url), projectKey, repositorySlug, scmRootCAPath, insecure, caCerts)
}
func newBitbucketService(ctx context.Context, bitbucketConfig *bitbucketv1.Configuration, projectKey, repositorySlug string, scmRootCAPath string, insecure bool, caCerts []byte) (PullRequestService, error) {
func newBitbucketService(ctx context.Context, bitbucketConfig *bitbucketv1.Configuration, projectKey, repositorySlug string) (PullRequestService, error) {
bitbucketConfig.BasePath = utils.NormalizeBitbucketBasePath(bitbucketConfig.BasePath)
tlsConfig := utils.GetTlsConfig(scmRootCAPath, insecure, caCerts)
bitbucketConfig.HTTPClient = &http.Client{Transport: &http.Transport{
TLSClientConfig: tlsConfig,
}}
bitbucketClient := bitbucketv1.NewAPIClient(ctx, bitbucketConfig)
return &BitbucketService{
@@ -64,7 +48,7 @@ func newBitbucketService(ctx context.Context, bitbucketConfig *bitbucketv1.Confi
}
func (b *BitbucketService) List(_ context.Context) ([]*PullRequest, error) {
paged := map[string]any{
paged := map[string]interface{}{
"limit": 100,
}
@@ -72,23 +56,21 @@ func (b *BitbucketService) List(_ context.Context) ([]*PullRequest, error) {
for {
response, err := b.client.DefaultApi.GetPullRequestsPage(b.projectKey, b.repositorySlug, paged)
if err != nil {
return nil, fmt.Errorf("error listing pull requests for %s/%s: %w", b.projectKey, b.repositorySlug, err)
return nil, fmt.Errorf("error listing pull requests for %s/%s: %v", b.projectKey, b.repositorySlug, err)
}
pulls, err := bitbucketv1.GetPullRequestsResponse(response)
if err != nil {
log.Errorf("error parsing pull request response '%v'", response.Values)
return nil, fmt.Errorf("error parsing pull request response for %s/%s: %w", b.projectKey, b.repositorySlug, err)
return nil, fmt.Errorf("error parsing pull request response for %s/%s: %v", b.projectKey, b.repositorySlug, err)
}
for _, pull := range pulls {
pullRequests = append(pullRequests, &PullRequest{
Number: pull.ID,
Title: pull.Title,
Branch: pull.FromRef.DisplayID, // ID: refs/heads/main DisplayID: main
TargetBranch: pull.ToRef.DisplayID,
HeadSHA: pull.FromRef.LatestCommit, // This is not defined in the official docs, but works in practice
Labels: []string{}, // Not supported by library
Author: pull.Author.User.Name,
})
}

View File

@@ -1,21 +1,17 @@
package pull_request
import (
"crypto/x509"
"encoding/pem"
"context"
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
)
func defaultHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
t.Helper()
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var err error
@@ -28,7 +24,6 @@ func defaultHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
"values": [
{
"id": 101,
"title": "feat(ABC) : 123",
"toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "master",
@@ -38,11 +33,6 @@ func defaultHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
"id": "refs/heads/feature-ABC-123",
"displayId": "feature-ABC-123",
"latestCommit": "cb3cf2e4d1517c83e720d2585b9402dbef71f992"
},
"author": {
"user": {
"name": "testName"
}
}
}
],
@@ -63,17 +53,15 @@ func TestListPullRequestNoAuth(t *testing.T) {
defaultHandler(t)(w, r)
}))
defer ts.Close()
svc, err := NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil)
require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err)
assert.Len(t, pullRequests, 1)
svc, err := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
assert.NoError(t, err)
assert.Equal(t, 1, len(pullRequests))
assert.Equal(t, 101, pullRequests[0].Number)
assert.Equal(t, "feat(ABC) : 123", pullRequests[0].Title)
assert.Equal(t, "feature-ABC-123", pullRequests[0].Branch)
assert.Equal(t, "master", pullRequests[0].TargetBranch)
assert.Equal(t, "cb3cf2e4d1517c83e720d2585b9402dbef71f992", pullRequests[0].HeadSHA)
assert.Equal(t, "testName", pullRequests[0].Author)
}
func TestListPullRequestPagination(t *testing.T) {
@@ -89,7 +77,6 @@ func TestListPullRequestPagination(t *testing.T) {
"values": [
{
"id": 101,
"title": "feat(101)",
"toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "master",
@@ -99,16 +86,10 @@ func TestListPullRequestPagination(t *testing.T) {
"id": "refs/heads/feature-101",
"displayId": "feature-101",
"latestCommit": "ab3cf2e4d1517c83e720d2585b9402dbef71f992"
},
"author": {
"user": {
"name": "testName"
}
}
},
{
"id": 102,
"title": "feat(102)",
"toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "branch",
@@ -118,11 +99,6 @@ func TestListPullRequestPagination(t *testing.T) {
"id": "refs/heads/feature-102",
"displayId": "feature-102",
"latestCommit": "bb3cf2e4d1517c83e720d2585b9402dbef71f992"
},
"author": {
"user": {
"name": "testName"
}
}
}
],
@@ -136,7 +112,6 @@ func TestListPullRequestPagination(t *testing.T) {
"values": [
{
"id": 200,
"title": "feat(200)",
"toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "master",
@@ -146,11 +121,6 @@ func TestListPullRequestPagination(t *testing.T) {
"id": "refs/heads/feature-200",
"displayId": "feature-200",
"latestCommit": "cb3cf2e4d1517c83e720d2585b9402dbef71f992"
},
"author": {
"user": {
"name": "testName"
}
}
}
],
@@ -164,37 +134,31 @@ func TestListPullRequestPagination(t *testing.T) {
}
}))
defer ts.Close()
svc, err := NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil)
require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err)
assert.Len(t, pullRequests, 3)
svc, err := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
assert.NoError(t, err)
assert.Equal(t, 3, len(pullRequests))
assert.Equal(t, PullRequest{
Number: 101,
Title: "feat(101)",
Branch: "feature-101",
TargetBranch: "master",
HeadSHA: "ab3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{},
Author: "testName",
}, *pullRequests[0])
assert.Equal(t, PullRequest{
Number: 102,
Title: "feat(102)",
Branch: "feature-102",
TargetBranch: "branch",
HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{},
Author: "testName",
}, *pullRequests[1])
assert.Equal(t, PullRequest{
Number: 200,
Title: "feat(200)",
Branch: "feature-200",
TargetBranch: "master",
HeadSHA: "cb3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{},
Author: "testName",
}, *pullRequests[2])
}
@@ -206,109 +170,24 @@ func TestListPullRequestBasicAuth(t *testing.T) {
defaultHandler(t)(w, r)
}))
defer ts.Close()
svc, err := NewBitbucketServiceBasicAuth(t.Context(), "user", "password", ts.URL, "PROJECT", "REPO", "", false, nil)
require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err)
assert.Len(t, pullRequests, 1)
svc, err := NewBitbucketServiceBasicAuth(context.Background(), "user", "password", ts.URL, "PROJECT", "REPO")
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
assert.NoError(t, err)
assert.Equal(t, 1, len(pullRequests))
assert.Equal(t, 101, pullRequests[0].Number)
assert.Equal(t, "feature-ABC-123", pullRequests[0].Branch)
assert.Equal(t, "cb3cf2e4d1517c83e720d2585b9402dbef71f992", pullRequests[0].HeadSHA)
}
func TestListPullRequestBearerAuth(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "Bearer tolkien", r.Header.Get("Authorization"))
assert.Equal(t, "no-check", r.Header.Get("X-Atlassian-Token"))
defaultHandler(t)(w, r)
}))
defer ts.Close()
svc, err := NewBitbucketServiceBearerToken(t.Context(), "tolkien", ts.URL, "PROJECT", "REPO", "", false, nil)
require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.Equal(t, 101, pullRequests[0].Number)
assert.Equal(t, "feat(ABC) : 123", pullRequests[0].Title)
assert.Equal(t, "feature-ABC-123", pullRequests[0].Branch)
assert.Equal(t, "cb3cf2e4d1517c83e720d2585b9402dbef71f992", pullRequests[0].HeadSHA)
}
func TestListPullRequestTLS(t *testing.T) {
tests := []struct {
name string
tlsInsecure bool
passCerts bool
requireErr bool
}{
{
name: "TLS Insecure: true, No Certs",
tlsInsecure: true,
passCerts: false,
requireErr: false,
},
{
name: "TLS Insecure: true, With Certs",
tlsInsecure: true,
passCerts: true,
requireErr: false,
},
{
name: "TLS Insecure: false, With Certs",
tlsInsecure: false,
passCerts: true,
requireErr: false,
},
{
name: "TLS Insecure: false, No Certs",
tlsInsecure: false,
passCerts: false,
requireErr: true,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defaultHandler(t)(w, r)
}))
defer ts.Close()
var certs []byte
if test.passCerts {
for _, cert := range ts.TLS.Certificates {
for _, c := range cert.Certificate {
parsedCert, err := x509.ParseCertificate(c)
require.NoError(t, err, "Failed to parse certificate")
certs = append(certs, pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: parsedCert.Raw,
})...)
}
}
}
svc, err := NewBitbucketServiceBasicAuth(t.Context(), "user", "password", ts.URL, "PROJECT", "REPO", "", test.tlsInsecure, certs)
require.NoError(t, err)
_, err = ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
if test.requireErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
func TestListResponseError(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
defer ts.Close()
svc, _ := NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil)
_, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.Error(t, err)
svc, _ := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
_, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
assert.Error(t, err)
}
func TestListResponseMalformed(t *testing.T) {
@@ -331,9 +210,9 @@ func TestListResponseMalformed(t *testing.T) {
}
}))
defer ts.Close()
svc, _ := NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil)
_, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.Error(t, err)
svc, _ := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
_, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
assert.Error(t, err)
}
func TestListResponseEmpty(t *testing.T) {
@@ -356,10 +235,10 @@ func TestListResponseEmpty(t *testing.T) {
}
}))
defer ts.Close()
svc, err := NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil)
require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err)
svc, err := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
assert.NoError(t, err)
assert.Empty(t, pullRequests)
}
@@ -376,7 +255,6 @@ func TestListPullRequestBranchMatch(t *testing.T) {
"values": [
{
"id": 101,
"title": "feat(101)",
"toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "master",
@@ -386,16 +264,10 @@ func TestListPullRequestBranchMatch(t *testing.T) {
"id": "refs/heads/feature-101",
"displayId": "feature-101",
"latestCommit": "ab3cf2e4d1517c83e720d2585b9402dbef71f992"
},
"author": {
"user": {
"name": "testName"
}
}
},
{
"id": 102,
"title": "feat(102)",
"toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "branch",
@@ -405,11 +277,6 @@ func TestListPullRequestBranchMatch(t *testing.T) {
"id": "refs/heads/feature-102",
"displayId": "feature-102",
"latestCommit": "bb3cf2e4d1517c83e720d2585b9402dbef71f992"
},
"author": {
"user": {
"name": "testName"
}
}
}
],
@@ -423,7 +290,6 @@ func TestListPullRequestBranchMatch(t *testing.T) {
"values": [
{
"id": 200,
"title": "feat(200)",
"toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "master",
@@ -433,11 +299,6 @@ func TestListPullRequestBranchMatch(t *testing.T) {
"id": "refs/heads/feature-200",
"displayId": "feature-200",
"latestCommit": "cb3cf2e4d1517c83e720d2585b9402dbef71f992"
},
"author": {
"user": {
"name": "testName"
}
}
}
],
@@ -452,61 +313,55 @@ func TestListPullRequestBranchMatch(t *testing.T) {
}))
defer ts.Close()
regexp := `feature-1[\d]{2}`
svc, err := NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil)
require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{
svc, err := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{
BranchMatch: &regexp,
},
})
require.NoError(t, err)
assert.Len(t, pullRequests, 2)
assert.NoError(t, err)
assert.Equal(t, 2, len(pullRequests))
assert.Equal(t, PullRequest{
Number: 101,
Title: "feat(101)",
Branch: "feature-101",
TargetBranch: "master",
HeadSHA: "ab3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{},
Author: "testName",
}, *pullRequests[0])
assert.Equal(t, PullRequest{
Number: 102,
Title: "feat(102)",
Branch: "feature-102",
TargetBranch: "branch",
HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{},
Author: "testName",
}, *pullRequests[1])
regexp = `.*2$`
svc, err = NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil)
require.NoError(t, err)
pullRequests, err = ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{
svc, err = NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
assert.NoError(t, err)
pullRequests, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{
BranchMatch: &regexp,
},
})
require.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.NoError(t, err)
assert.Equal(t, 1, len(pullRequests))
assert.Equal(t, PullRequest{
Number: 102,
Title: "feat(102)",
Branch: "feature-102",
TargetBranch: "branch",
HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{},
Author: "testName",
}, *pullRequests[0])
regexp = `[\d{2}`
svc, err = NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil)
require.NoError(t, err)
_, err = ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{
svc, err = NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
assert.NoError(t, err)
_, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{
BranchMatch: &regexp,
},
})
require.Error(t, err)
assert.Error(t, err)
}

View File

@@ -18,6 +18,6 @@ func NewFakeService(_ context.Context, listPullReuests []*PullRequest, listError
}, nil
}
func (g *FakeService) List(_ context.Context) ([]*PullRequest, error) {
func (g *FakeService) List(ctx context.Context) ([]*PullRequest, error) {
return g.listPullReuests, g.listError
}

View File

@@ -18,7 +18,7 @@ type GiteaService struct {
var _ PullRequestService = (*GiteaService)(nil)
func NewGiteaService(token, url, owner, repo string, insecure bool) (PullRequestService, error) {
func NewGiteaService(ctx context.Context, token, url, owner, repo string, insecure bool) (PullRequestService, error) {
if token == "" {
token = os.Getenv("GITEA_TOKEN")
}
@@ -49,7 +49,6 @@ func (g *GiteaService) List(ctx context.Context) ([]*PullRequest, error) {
opts := gitea.ListPullRequestsOptions{
State: gitea.StateOpen,
}
g.client.SetContext(ctx)
prs, _, err := g.client.ListRepoPullRequests(g.owner, g.repo, opts)
if err != nil {
return nil, err
@@ -58,12 +57,10 @@ func (g *GiteaService) List(ctx context.Context) ([]*PullRequest, error) {
for _, pr := range prs {
list = append(list, &PullRequest{
Number: int(pr.Index),
Title: pr.Title,
Branch: pr.Head.Ref,
TargetBranch: pr.Base.Ref,
HeadSHA: pr.Head.Sha,
Labels: getGiteaPRLabelNames(pr.Labels),
Author: pr.Poster.UserName,
})
}
return list, nil

View File

@@ -1,6 +1,7 @@
package pull_request
import (
"context"
"fmt"
"io"
"net/http"
@@ -9,11 +10,9 @@ import (
"code.gitea.io/sdk/gitea"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func giteaMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
t.Helper()
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Println(r.RequestURI)
@@ -250,17 +249,15 @@ func TestGiteaList(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
giteaMockHandler(t)(w, r)
}))
host, err := NewGiteaService("", ts.URL, "test-argocd", "pr-test", false)
require.NoError(t, err)
prs, err := host.List(t.Context())
require.NoError(t, err)
assert.Len(t, prs, 1)
assert.Equal(t, 1, prs[0].Number)
assert.Equal(t, "add an empty file", prs[0].Title)
assert.Equal(t, "test", prs[0].Branch)
assert.Equal(t, "main", prs[0].TargetBranch)
assert.Equal(t, "7bbaf62d92ddfafd9cc8b340c619abaec32bc09f", prs[0].HeadSHA)
assert.Equal(t, "graytshirt", prs[0].Author)
host, err := NewGiteaService(context.Background(), "", ts.URL, "test-argocd", "pr-test", false)
assert.Nil(t, err)
prs, err := host.List(context.Background())
assert.Nil(t, err)
assert.Equal(t, len(prs), 1)
assert.Equal(t, prs[0].Number, 1)
assert.Equal(t, prs[0].Branch, "test")
assert.Equal(t, prs[0].TargetBranch, "main")
assert.Equal(t, prs[0].HeadSHA, "7bbaf62d92ddfafd9cc8b340c619abaec32bc09f")
}
func TestGetGiteaPRLabelNames(t *testing.T) {

View File

@@ -3,10 +3,10 @@ package pull_request
import (
"context"
"fmt"
"net/http"
"os"
"github.com/google/go-github/v66/github"
"github.com/google/go-github/v35/github"
"golang.org/x/oauth2"
)
type GithubService struct {
@@ -18,18 +18,24 @@ type GithubService struct {
var _ PullRequestService = (*GithubService)(nil)
func NewGithubService(token, url, owner, repo string, labels []string) (PullRequestService, error) {
func NewGithubService(ctx context.Context, token, url, owner, repo string, labels []string) (PullRequestService, error) {
var ts oauth2.TokenSource
// Undocumented environment variable to set a default token, to be used in testing to dodge anonymous rate limits.
if token == "" {
token = os.Getenv("GITHUB_TOKEN")
}
httpClient := &http.Client{}
if token != "" {
ts = oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
)
}
httpClient := oauth2.NewClient(ctx, ts)
var client *github.Client
if url == "" {
client = github.NewClient(httpClient).WithAuthToken(token)
client = github.NewClient(httpClient)
} else {
var err error
client, err = github.NewClient(httpClient).WithEnterpriseURLs(url, url)
client, err = github.NewEnterpriseClient(url, url, httpClient)
if err != nil {
return nil, err
}
@@ -60,12 +66,10 @@ func (g *GithubService) List(ctx context.Context) ([]*PullRequest, error) {
}
pullRequests = append(pullRequests, &PullRequest{
Number: *pull.Number,
Title: *pull.Title,
Branch: *pull.Head.Ref,
TargetBranch: *pull.Base.Ref,
HeadSHA: *pull.Head.SHA,
Labels: getGithubPRLabelNames(pull.Labels),
Author: *pull.User.Login,
})
}
if resp.NextPage == 0 {

View File

@@ -1,8 +1,8 @@
package pull_request
import (
"github.com/argoproj/argo-cd/v3/applicationset/services/github_app_auth"
"github.com/argoproj/argo-cd/v3/applicationset/services/internal/github_app"
"github.com/argoproj/argo-cd/v2/applicationset/services/github_app_auth"
"github.com/argoproj/argo-cd/v2/applicationset/services/internal/github_app"
)
func NewGithubAppService(g github_app_auth.Authentication, url, owner, repo string, labels []string) (PullRequestService, error) {

View File

@@ -3,8 +3,8 @@ package pull_request
import (
"testing"
"github.com/google/go-github/v66/github"
"github.com/stretchr/testify/require"
"github.com/google/go-github/v35/github"
"github.com/stretchr/testify/assert"
)
func toPtr(s string) *string {
@@ -52,8 +52,9 @@ func TestContainLabels(t *testing.T) {
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
got := containLabels(c.Labels, c.PullLabels)
require.Equal(t, got, c.Expect)
if got := containLabels(c.Labels, c.PullLabels); got != c.Expect {
t.Errorf("expect: %v, got: %v", c.Expect, got)
}
})
}
}
@@ -82,7 +83,7 @@ func TestGetGitHubPRLabelNames(t *testing.T) {
for _, test := range Tests {
t.Run(test.Name, func(t *testing.T) {
labels := getGithubPRLabelNames(test.PullLabels)
require.Equal(t, test.ExpectedResult, labels)
assert.Equal(t, test.ExpectedResult, labels)
})
}
}

View File

@@ -6,10 +6,9 @@ import (
"net/http"
"os"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
"github.com/hashicorp/go-retryablehttp"
gitlab "gitlab.com/gitlab-org/api/client-go"
"github.com/argoproj/argo-cd/v3/applicationset/utils"
gitlab "github.com/xanzy/go-gitlab"
)
type GitLabService struct {
@@ -21,7 +20,7 @@ type GitLabService struct {
var _ PullRequestService = (*GitLabService)(nil)
func NewGitLabService(token, url, project string, labels []string, pullRequestState string, scmRootCAPath string, insecure bool, caCerts []byte) (PullRequestService, error) {
func NewGitLabService(ctx context.Context, token, url, project string, labels []string, pullRequestState string, scmRootCAPath string, insecure bool) (PullRequestService, error) {
var clientOptionFns []gitlab.ClientOptionFunc
// Set a custom Gitlab base URL if one is provided
@@ -34,7 +33,7 @@ func NewGitLabService(token, url, project string, labels []string, pullRequestSt
}
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.TLSClientConfig = utils.GetTlsConfig(scmRootCAPath, insecure, caCerts)
tr.TLSClientConfig = utils.GetTlsConfig(scmRootCAPath, insecure)
retryClient := retryablehttp.NewClient()
retryClient.HTTPClient.Transport = tr
@@ -43,7 +42,7 @@ func NewGitLabService(token, url, project string, labels []string, pullRequestSt
client, err := gitlab.NewClient(token, clientOptionFns...)
if err != nil {
return nil, fmt.Errorf("error creating Gitlab client: %w", err)
return nil, fmt.Errorf("error creating Gitlab client: %v", err)
}
return &GitLabService{
@@ -55,12 +54,13 @@ func NewGitLabService(token, url, project string, labels []string, pullRequestSt
}
func (g *GitLabService) List(ctx context.Context) ([]*PullRequest, error) {
// Filter the merge requests on labels, if they are specified.
var labels *gitlab.LabelOptions
var labels *gitlab.Labels
if len(g.labels) > 0 {
var labelsList gitlab.LabelOptions = g.labels
labels = &labelsList
labels = (*gitlab.Labels)(&g.labels)
}
opts := &gitlab.ListProjectMergeRequestsOptions{
ListOptions: gitlab.ListOptions{
PerPage: 100,
@@ -74,19 +74,17 @@ func (g *GitLabService) List(ctx context.Context) ([]*PullRequest, error) {
pullRequests := []*PullRequest{}
for {
mrs, resp, err := g.client.MergeRequests.ListProjectMergeRequests(g.project, opts, gitlab.WithContext(ctx))
mrs, resp, err := g.client.MergeRequests.ListProjectMergeRequests(g.project, opts)
if err != nil {
return nil, fmt.Errorf("error listing merge requests for project '%s': %w", g.project, err)
return nil, fmt.Errorf("error listing merge requests for project '%s': %v", g.project, err)
}
for _, mr := range mrs {
pullRequests = append(pullRequests, &PullRequest{
Number: mr.IID,
Title: mr.Title,
Branch: mr.SourceBranch,
TargetBranch: mr.TargetBranch,
HeadSHA: mr.SHA,
Labels: mr.Labels,
Author: mr.Author.Username,
})
}
if resp.NextPage == 0 {

View File

@@ -1,8 +1,7 @@
package pull_request
import (
"crypto/x509"
"encoding/pem"
"context"
"io"
"net/http"
"net/http/httptest"
@@ -10,16 +9,17 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func writeMRListResponse(t *testing.T, w io.Writer) {
t.Helper()
f, err := os.Open("fixtures/gitlab_mr_list_response.json")
require.NoErrorf(t, err, "error opening fixture file: %v", err)
if err != nil {
t.Fatalf("error opening fixture file: %v", err)
}
_, err = io.Copy(w, f)
require.NoErrorf(t, err, "error writing response: %v", err)
if _, err = io.Copy(w, f); err != nil {
t.Fatalf("error writing response: %v", err)
}
}
func TestGitLabServiceCustomBaseURL(t *testing.T) {
@@ -34,11 +34,11 @@ func TestGitLabServiceCustomBaseURL(t *testing.T) {
writeMRListResponse(t, w)
})
svc, err := NewGitLabService("", server.URL, "278964", nil, "", "", false, nil)
require.NoError(t, err)
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", nil, "", "", false)
assert.NoError(t, err)
_, err = svc.List(t.Context())
require.NoError(t, err)
_, err = svc.List(context.Background())
assert.NoError(t, err)
}
func TestGitLabServiceToken(t *testing.T) {
@@ -53,11 +53,11 @@ func TestGitLabServiceToken(t *testing.T) {
writeMRListResponse(t, w)
})
svc, err := NewGitLabService("token-123", server.URL, "278964", nil, "", "", false, nil)
require.NoError(t, err)
svc, err := NewGitLabService(context.Background(), "token-123", server.URL, "278964", nil, "", "", false)
assert.NoError(t, err)
_, err = svc.List(t.Context())
require.NoError(t, err)
_, err = svc.List(context.Background())
assert.NoError(t, err)
}
func TestList(t *testing.T) {
@@ -72,18 +72,16 @@ func TestList(t *testing.T) {
writeMRListResponse(t, w)
})
svc, err := NewGitLabService("", server.URL, "278964", []string{}, "", "", false, nil)
require.NoError(t, err)
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{}, "", "", false)
assert.NoError(t, err)
prs, err := svc.List(t.Context())
require.NoError(t, err)
prs, err := svc.List(context.Background())
assert.NoError(t, err)
assert.Len(t, prs, 1)
assert.Equal(t, 15442, prs[0].Number)
assert.Equal(t, "Draft: Use structured logging for DB load balancer", prs[0].Title)
assert.Equal(t, "use-structured-logging-for-db-load-balancer", prs[0].Branch)
assert.Equal(t, "master", prs[0].TargetBranch)
assert.Equal(t, "2fc4e8b972ff3208ec63b6143e34ad67ff343ad7", prs[0].HeadSHA)
assert.Equal(t, "hfyngvason", prs[0].Author)
assert.Equal(t, prs[0].Number, 15442)
assert.Equal(t, prs[0].Branch, "use-structured-logging-for-db-load-balancer")
assert.Equal(t, prs[0].TargetBranch, "master")
assert.Equal(t, prs[0].HeadSHA, "2fc4e8b972ff3208ec63b6143e34ad67ff343ad7")
}
func TestListWithLabels(t *testing.T) {
@@ -98,11 +96,11 @@ func TestListWithLabels(t *testing.T) {
writeMRListResponse(t, w)
})
svc, err := NewGitLabService("", server.URL, "278964", []string{"feature", "ready"}, "", "", false, nil)
require.NoError(t, err)
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{"feature", "ready"}, "", "", false)
assert.NoError(t, err)
_, err = svc.List(t.Context())
require.NoError(t, err)
_, err = svc.List(context.Background())
assert.NoError(t, err)
}
func TestListWithState(t *testing.T) {
@@ -117,77 +115,9 @@ func TestListWithState(t *testing.T) {
writeMRListResponse(t, w)
})
svc, err := NewGitLabService("", server.URL, "278964", []string{}, "opened", "", false, nil)
require.NoError(t, err)
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{}, "opened", "", false)
assert.NoError(t, err)
_, err = svc.List(t.Context())
require.NoError(t, err)
}
func TestListWithStateTLS(t *testing.T) {
tests := []struct {
name string
tlsInsecure bool
passCerts bool
requireErr bool
}{
{
name: "TLS Insecure: true, No Certs",
tlsInsecure: true,
passCerts: false,
requireErr: false,
},
{
name: "TLS Insecure: true, With Certs",
tlsInsecure: true,
passCerts: true,
requireErr: false,
},
{
name: "TLS Insecure: false, With Certs",
tlsInsecure: false,
passCerts: true,
requireErr: false,
},
{
name: "TLS Insecure: false, No Certs",
tlsInsecure: false,
passCerts: false,
requireErr: true,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
writeMRListResponse(t, w)
}))
defer ts.Close()
var certs []byte
if test.passCerts {
for _, cert := range ts.TLS.Certificates {
for _, c := range cert.Certificate {
parsedCert, err := x509.ParseCertificate(c)
require.NoError(t, err, "Failed to parse certificate")
certs = append(certs, pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: parsedCert.Raw,
})...)
}
}
}
svc, err := NewGitLabService("", ts.URL, "278964", []string{}, "opened", "", test.tlsInsecure, certs)
require.NoError(t, err)
_, err = svc.List(t.Context())
if test.requireErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
_, err = svc.List(context.Background())
assert.NoError(t, err)
}

View File

@@ -8,8 +8,6 @@ import (
type PullRequest struct {
// Number is a number that will be the ID of the pull request.
Number int
// Title of the pull request.
Title string
// Branch is the name of the branch from which the pull request originated.
Branch string
// TargetBranch is the name of the target branch of the pull request.
@@ -18,8 +16,6 @@ type PullRequest struct {
HeadSHA string
// Labels of the pull request.
Labels []string
// Author is the author of the pull request.
Author string
}
type PullRequestService interface {

View File

@@ -5,7 +5,7 @@ import (
"fmt"
"regexp"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func compileFilters(filters []argoprojiov1alpha1.PullRequestGeneratorFilter) ([]*Filter, error) {
@@ -16,13 +16,13 @@ func compileFilters(filters []argoprojiov1alpha1.PullRequestGeneratorFilter) ([]
if filter.BranchMatch != nil {
outFilter.BranchMatch, err = regexp.Compile(*filter.BranchMatch)
if err != nil {
return nil, fmt.Errorf("error compiling BranchMatch regexp %q: %w", *filter.BranchMatch, err)
return nil, fmt.Errorf("error compiling BranchMatch regexp %q: %v", *filter.BranchMatch, err)
}
}
if filter.TargetBranchMatch != nil {
outFilter.TargetBranchMatch, err = regexp.Compile(*filter.TargetBranchMatch)
if err != nil {
return nil, fmt.Errorf("error compiling TargetBranchMatch regexp %q: %w", *filter.TargetBranchMatch, err)
return nil, fmt.Errorf("error compiling TargetBranchMatch regexp %q: %v", *filter.TargetBranchMatch, err)
}
}
outFilters = append(outFilters, outFilter)

View File

@@ -1,29 +1,25 @@
package pull_request
import (
"context"
"testing"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
)
func strp(s string) *string {
return &s
}
func TestFilterBranchMatchBadRegexp(t *testing.T) {
provider, _ := NewFakeService(
t.Context(),
context.Background(),
[]*PullRequest{
{
Number: 1,
Title: "PR branch1",
Branch: "branch1",
TargetBranch: "master",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name1",
},
},
nil,
@@ -33,45 +29,37 @@ func TestFilterBranchMatchBadRegexp(t *testing.T) {
BranchMatch: strp("("),
},
}
_, err := ListPullRequests(t.Context(), provider, filters)
require.Error(t, err)
_, err := ListPullRequests(context.Background(), provider, filters)
assert.Error(t, err)
}
func TestFilterBranchMatch(t *testing.T) {
provider, _ := NewFakeService(
t.Context(),
context.Background(),
[]*PullRequest{
{
Number: 1,
Title: "PR one",
Branch: "one",
TargetBranch: "master",
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name1",
},
{
Number: 2,
Title: "PR two",
Branch: "two",
TargetBranch: "master",
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name2",
},
{
Number: 3,
Title: "PR three",
Branch: "three",
TargetBranch: "master",
HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name3",
},
{
Number: 4,
Title: "PR four",
Branch: "four",
TargetBranch: "master",
HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name4",
},
},
nil,
@@ -81,47 +69,39 @@ func TestFilterBranchMatch(t *testing.T) {
BranchMatch: strp("w"),
},
}
pullRequests, err := ListPullRequests(t.Context(), provider, filters)
require.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), provider, filters)
assert.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.Equal(t, "two", pullRequests[0].Branch)
}
func TestFilterTargetBranchMatch(t *testing.T) {
provider, _ := NewFakeService(
t.Context(),
context.Background(),
[]*PullRequest{
{
Number: 1,
Title: "PR one",
Branch: "one",
TargetBranch: "master",
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name1",
},
{
Number: 2,
Title: "PR two",
Branch: "two",
TargetBranch: "branch1",
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name2",
},
{
Number: 3,
Title: "PR three",
Branch: "three",
TargetBranch: "branch2",
HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name3",
},
{
Number: 4,
Title: "PR four",
Branch: "four",
TargetBranch: "branch3",
HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name4",
},
},
nil,
@@ -131,47 +111,39 @@ func TestFilterTargetBranchMatch(t *testing.T) {
TargetBranchMatch: strp("1"),
},
}
pullRequests, err := ListPullRequests(t.Context(), provider, filters)
require.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), provider, filters)
assert.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.Equal(t, "two", pullRequests[0].Branch)
}
func TestMultiFilterOr(t *testing.T) {
provider, _ := NewFakeService(
t.Context(),
context.Background(),
[]*PullRequest{
{
Number: 1,
Title: "PR one",
Branch: "one",
TargetBranch: "master",
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name1",
},
{
Number: 2,
Title: "PR two",
Branch: "two",
TargetBranch: "master",
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name2",
},
{
Number: 3,
Title: "PR three",
Branch: "three",
TargetBranch: "master",
HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name3",
},
{
Number: 4,
Title: "PR four",
Branch: "four",
TargetBranch: "master",
HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name4",
},
},
nil,
@@ -184,8 +156,8 @@ func TestMultiFilterOr(t *testing.T) {
BranchMatch: strp("r"),
},
}
pullRequests, err := ListPullRequests(t.Context(), provider, filters)
require.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), provider, filters)
assert.NoError(t, err)
assert.Len(t, pullRequests, 3)
assert.Equal(t, "two", pullRequests[0].Branch)
assert.Equal(t, "three", pullRequests[1].Branch)
@@ -194,39 +166,31 @@ func TestMultiFilterOr(t *testing.T) {
func TestMultiFilterOrWithTargetBranchFilter(t *testing.T) {
provider, _ := NewFakeService(
t.Context(),
context.Background(),
[]*PullRequest{
{
Number: 1,
Title: "PR one",
Branch: "one",
TargetBranch: "master",
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name1",
},
{
Number: 2,
Title: "PR two",
Branch: "two",
TargetBranch: "branch1",
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name2",
},
{
Number: 3,
Title: "PR three",
Branch: "three",
TargetBranch: "branch2",
HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name3",
},
{
Number: 4,
Title: "PR four",
Branch: "four",
TargetBranch: "branch3",
HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name4",
},
},
nil,
@@ -241,8 +205,8 @@ func TestMultiFilterOrWithTargetBranchFilter(t *testing.T) {
TargetBranchMatch: strp("3"),
},
}
pullRequests, err := ListPullRequests(t.Context(), provider, filters)
require.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), provider, filters)
assert.NoError(t, err)
assert.Len(t, pullRequests, 2)
assert.Equal(t, "two", pullRequests[0].Branch)
assert.Equal(t, "four", pullRequests[1].Branch)
@@ -250,30 +214,26 @@ func TestMultiFilterOrWithTargetBranchFilter(t *testing.T) {
func TestNoFilters(t *testing.T) {
provider, _ := NewFakeService(
t.Context(),
context.Background(),
[]*PullRequest{
{
Number: 1,
Title: "PR one",
Branch: "one",
TargetBranch: "master",
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name1",
},
{
Number: 2,
Title: "PR two",
Branch: "two",
TargetBranch: "master",
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "name2",
},
},
nil,
)
filters := []argoprojiov1alpha1.PullRequestGeneratorFilter{}
repos, err := ListPullRequests(t.Context(), provider, filters)
require.NoError(t, err)
repos, err := ListPullRequests(context.Background(), provider, filters)
assert.NoError(t, err)
assert.Len(t, repos, 2)
assert.Equal(t, "one", repos[0].Branch)
assert.Equal(t, "two", repos[1].Branch)

View File

@@ -4,54 +4,51 @@ import (
"context"
"fmt"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v3/reposerver/apiclient"
"github.com/argoproj/argo-cd/v3/util/db"
"github.com/argoproj/argo-cd/v3/util/io"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
"github.com/argoproj/argo-cd/v2/util/db"
"github.com/argoproj/argo-cd/v2/util/git"
"github.com/argoproj/argo-cd/v2/util/io"
)
type argoCDService struct {
getRepository func(ctx context.Context, url, project string) (*v1alpha1.Repository, error)
submoduleEnabled bool
newFileGlobbingEnabled bool
getGitFilesFromRepoServer func(ctx context.Context, req *apiclient.GitFilesRequest) (*apiclient.GitFilesResponse, error)
getGitDirectoriesFromRepoServer func(ctx context.Context, req *apiclient.GitDirectoriesRequest) (*apiclient.GitDirectoriesResponse, error)
//go:generate go run github.com/vektra/mockery/v2@v2.25.1 --name=RepositoryDB
// RepositoryDB Is a lean facade for ArgoDB,
// Using a lean interface makes it easier to test the functionality of the git generator
type RepositoryDB interface {
GetRepository(ctx context.Context, url string) (*v1alpha1.Repository, error)
}
type argoCDService struct {
repositoriesDB RepositoryDB
storecreds git.CredsStore
submoduleEnabled bool
repoServerClientSet apiclient.Clientset
newFileGlobbingEnabled bool
}
//go:generate go run github.com/vektra/mockery/v2@v2.25.1 --name=Repos
type Repos interface {
// GetFiles returns content of files (not directories) within the target repo
GetFiles(ctx context.Context, repoURL, revision, project, pattern string, noRevisionCache, verifyCommit bool) (map[string][]byte, error)
GetFiles(ctx context.Context, repoURL string, revision string, pattern string, noRevisionCache bool) (map[string][]byte, error)
// GetDirectories returns a list of directories (not files) within the target repo
GetDirectories(ctx context.Context, repoURL, revision, project string, noRevisionCache, verifyCommit bool) ([]string, error)
GetDirectories(ctx context.Context, repoURL string, revision string, noRevisionCache bool) ([]string, error)
}
func NewArgoCDService(db db.ArgoDB, submoduleEnabled bool, repoClientset apiclient.Clientset, newFileGlobbingEnabled bool) Repos {
func NewArgoCDService(db db.ArgoDB, submoduleEnabled bool, repoClientset apiclient.Clientset, newFileGlobbingEnabled bool) (Repos, error) {
return &argoCDService{
getRepository: db.GetRepository,
repositoriesDB: db.(RepositoryDB),
submoduleEnabled: submoduleEnabled,
repoServerClientSet: repoClientset,
newFileGlobbingEnabled: newFileGlobbingEnabled,
getGitFilesFromRepoServer: func(ctx context.Context, fileRequest *apiclient.GitFilesRequest) (*apiclient.GitFilesResponse, error) {
closer, client, err := repoClientset.NewRepoServerClient()
if err != nil {
return nil, fmt.Errorf("error initializing new repo server client: %w", err)
}
defer io.Close(closer)
return client.GetGitFiles(ctx, fileRequest)
},
getGitDirectoriesFromRepoServer: func(ctx context.Context, dirRequest *apiclient.GitDirectoriesRequest) (*apiclient.GitDirectoriesResponse, error) {
closer, client, err := repoClientset.NewRepoServerClient()
if err != nil {
return nil, fmt.Errorf("error initialising new repo server client: %w", err)
}
defer io.Close(closer)
return client.GetGitDirectories(ctx, dirRequest)
},
}
}, nil
}
func (a *argoCDService) GetFiles(ctx context.Context, repoURL, revision, project, pattern string, noRevisionCache, verifyCommit bool) (map[string][]byte, error) {
repo, err := a.getRepository(ctx, repoURL, project)
func (a *argoCDService) GetFiles(ctx context.Context, repoURL string, revision string, pattern string, noRevisionCache bool) (map[string][]byte, error) {
repo, err := a.repositoriesDB.GetRepository(ctx, repoURL)
if err != nil {
return nil, fmt.Errorf("error in GetRepository: %w", err)
}
@@ -63,17 +60,22 @@ func (a *argoCDService) GetFiles(ctx context.Context, repoURL, revision, project
Path: pattern,
NewGitFileGlobbingEnabled: a.newFileGlobbingEnabled,
NoRevisionCache: noRevisionCache,
VerifyCommit: verifyCommit,
}
fileResponse, err := a.getGitFilesFromRepoServer(ctx, fileRequest)
closer, client, err := a.repoServerClientSet.NewRepoServerClient()
if err != nil {
return nil, fmt.Errorf("error initialising new repo server client: %w", err)
}
defer io.Close(closer)
fileResponse, err := client.GetGitFiles(ctx, fileRequest)
if err != nil {
return nil, fmt.Errorf("error retrieving Git files: %w", err)
}
return fileResponse.GetMap(), nil
}
func (a *argoCDService) GetDirectories(ctx context.Context, repoURL, revision, project string, noRevisionCache, verifyCommit bool) ([]string, error) {
repo, err := a.getRepository(ctx, repoURL, project)
func (a *argoCDService) GetDirectories(ctx context.Context, repoURL string, revision string, noRevisionCache bool) ([]string, error) {
repo, err := a.repositoriesDB.GetRepository(ctx, repoURL)
if err != nil {
return nil, fmt.Errorf("error in GetRepository: %w", err)
}
@@ -83,12 +85,18 @@ func (a *argoCDService) GetDirectories(ctx context.Context, repoURL, revision, p
SubmoduleEnabled: a.submoduleEnabled,
Revision: revision,
NoRevisionCache: noRevisionCache,
VerifyCommit: verifyCommit,
}
dirResponse, err := a.getGitDirectoriesFromRepoServer(ctx, dirRequest)
closer, client, err := a.repoServerClientSet.NewRepoServerClient()
if err != nil {
return nil, fmt.Errorf("error initialising new repo server client: %w", err)
}
defer io.Close(closer)
dirResponse, err := client.GetGitDirectories(ctx, dirRequest)
if err != nil {
return nil, fmt.Errorf("error retrieving Git Directories: %w", err)
}
return dirResponse.GetPaths(), nil
}

View File

@@ -2,33 +2,33 @@ package services
import (
"context"
"errors"
"fmt"
"testing"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
repo_mocks "github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
db_mocks "github.com/argoproj/argo-cd/v2/util/db/mocks"
"github.com/argoproj/argo-cd/v2/util/git"
"github.com/stretchr/testify/assert"
"k8s.io/client-go/kubernetes/fake"
"github.com/stretchr/testify/mock"
"github.com/argoproj/argo-cd/v3/reposerver/apiclient"
repo_mocks "github.com/argoproj/argo-cd/v3/reposerver/apiclient/mocks"
"github.com/argoproj/argo-cd/v3/util/db"
"github.com/argoproj/argo-cd/v3/util/settings"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func TestGetDirectories(t *testing.T) {
type fields struct {
submoduleEnabled bool
getRepository func(ctx context.Context, url, project string) (*v1alpha1.Repository, error)
getGitDirectories func(ctx context.Context, req *apiclient.GitDirectoriesRequest) (*apiclient.GitDirectoriesResponse, error)
repositoriesDBFuncs []func(*mocks.RepositoryDB)
storecreds git.CredsStore
submoduleEnabled bool
repoServerClientFuncs []func(*repo_mocks.RepoServerServiceClient)
}
type args struct {
ctx context.Context
repoURL string
revision string
noRevisionCache bool
verifyCommit bool
}
tests := []struct {
name string
@@ -38,49 +38,58 @@ func TestGetDirectories(t *testing.T) {
wantErr assert.ErrorAssertionFunc
}{
{name: "ErrorGettingRepos", fields: fields{
getRepository: func(_ context.Context, _, _ string) (*v1alpha1.Repository, error) {
return nil, errors.New("unable to get repos")
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("unable to get repos"))
},
},
}, args: args{}, want: nil, wantErr: assert.Error},
{name: "ErrorGettingDirs", fields: fields{
getRepository: func(_ context.Context, _, _ string) (*v1alpha1.Repository, error) {
return &v1alpha1.Repository{}, nil
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(&v1alpha1.Repository{}, nil)
},
},
getGitDirectories: func(_ context.Context, _ *apiclient.GitDirectoriesRequest) (*apiclient.GitDirectoriesResponse, error) {
return nil, errors.New("unable to get dirs")
repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){
func(client *repo_mocks.RepoServerServiceClient) {
client.On("GetGitDirectories", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("unable to get dirs"))
},
},
}, args: args{}, want: nil, wantErr: assert.Error},
{name: "HappyCase", fields: fields{
getRepository: func(_ context.Context, _, _ string) (*v1alpha1.Repository, error) {
return &v1alpha1.Repository{
Repo: "foo",
}, nil
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(&v1alpha1.Repository{}, nil)
},
},
getGitDirectories: func(_ context.Context, _ *apiclient.GitDirectoriesRequest) (*apiclient.GitDirectoriesResponse, error) {
return &apiclient.GitDirectoriesResponse{
Paths: []string{"foo", "foo/bar", "bar/foo"},
}, nil
repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){
func(client *repo_mocks.RepoServerServiceClient) {
client.On("GetGitDirectories", mock.Anything, mock.Anything).Return(&apiclient.GitDirectoriesResponse{
Paths: []string{"foo", "foo/bar", "bar/foo"},
}, nil)
},
},
}, args: args{
repoURL: "foo",
}, want: []string{"foo", "foo/bar", "bar/foo"}, wantErr: assert.NoError},
{name: "ErrorVerifyingCommit", fields: fields{
getRepository: func(_ context.Context, _, _ string) (*v1alpha1.Repository, error) {
return &v1alpha1.Repository{}, nil
},
getGitDirectories: func(_ context.Context, _ *apiclient.GitDirectoriesRequest) (*apiclient.GitDirectoriesResponse, error) {
return nil, errors.New("revision HEAD is not signed")
},
}, args: args{}, want: nil, wantErr: assert.Error},
}, args: args{}, want: []string{"foo", "foo/bar", "bar/foo"}, wantErr: assert.NoError},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &argoCDService{
getRepository: tt.fields.getRepository,
submoduleEnabled: tt.fields.submoduleEnabled,
getGitDirectoriesFromRepoServer: tt.fields.getGitDirectories,
mockDb := &mocks.RepositoryDB{}
mockRepoClient := &repo_mocks.RepoServerServiceClient{}
// decorate the mocks
for i := range tt.fields.repositoriesDBFuncs {
tt.fields.repositoriesDBFuncs[i](mockDb)
}
got, err := a.GetDirectories(tt.args.ctx, tt.args.repoURL, tt.args.revision, "", tt.args.noRevisionCache, tt.args.verifyCommit)
for i := range tt.fields.repoServerClientFuncs {
tt.fields.repoServerClientFuncs[i](mockRepoClient)
}
a := &argoCDService{
repositoriesDB: mockDb,
storecreds: tt.fields.storecreds,
submoduleEnabled: tt.fields.submoduleEnabled,
repoServerClientSet: &repo_mocks.Clientset{RepoServerServiceClient: mockRepoClient},
}
got, err := a.GetDirectories(tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.noRevisionCache)
if !tt.wantErr(t, err, fmt.Sprintf("GetDirectories(%v, %v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.noRevisionCache)) {
return
}
@@ -91,9 +100,10 @@ func TestGetDirectories(t *testing.T) {
func TestGetFiles(t *testing.T) {
type fields struct {
submoduleEnabled bool
getRepository func(ctx context.Context, url, project string) (*v1alpha1.Repository, error)
getGitFiles func(ctx context.Context, req *apiclient.GitFilesRequest) (*apiclient.GitFilesResponse, error)
repositoriesDBFuncs []func(*mocks.RepositoryDB)
storecreds git.CredsStore
submoduleEnabled bool
repoServerClientFuncs []func(*repo_mocks.RepoServerServiceClient)
}
type args struct {
ctx context.Context
@@ -101,7 +111,6 @@ func TestGetFiles(t *testing.T) {
revision string
pattern string
noRevisionCache bool
verifyCommit bool
}
tests := []struct {
name string
@@ -111,55 +120,64 @@ func TestGetFiles(t *testing.T) {
wantErr assert.ErrorAssertionFunc
}{
{name: "ErrorGettingRepos", fields: fields{
getRepository: func(_ context.Context, _, _ string) (*v1alpha1.Repository, error) {
return nil, errors.New("unable to get repos")
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("unable to get repos"))
},
},
}, args: args{}, want: nil, wantErr: assert.Error},
{name: "ErrorGettingFiles", fields: fields{
getRepository: func(_ context.Context, _, _ string) (*v1alpha1.Repository, error) {
return &v1alpha1.Repository{}, nil
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(&v1alpha1.Repository{}, nil)
},
},
getGitFiles: func(_ context.Context, _ *apiclient.GitFilesRequest) (*apiclient.GitFilesResponse, error) {
return nil, errors.New("unable to get files")
repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){
func(client *repo_mocks.RepoServerServiceClient) {
client.On("GetGitFiles", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("unable to get files"))
},
},
}, args: args{}, want: nil, wantErr: assert.Error},
{name: "HappyCase", fields: fields{
getRepository: func(_ context.Context, _, _ string) (*v1alpha1.Repository, error) {
return &v1alpha1.Repository{
Repo: "foo",
}, nil
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(&v1alpha1.Repository{}, nil)
},
},
getGitFiles: func(_ context.Context, _ *apiclient.GitFilesRequest) (*apiclient.GitFilesResponse, error) {
return &apiclient.GitFilesResponse{
Map: map[string][]byte{
"foo.json": []byte("hello: world!"),
"bar.yaml": []byte("yay: appsets"),
},
}, nil
repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){
func(client *repo_mocks.RepoServerServiceClient) {
client.On("GetGitFiles", mock.Anything, mock.Anything).Return(&apiclient.GitFilesResponse{
Map: map[string][]byte{
"foo.json": []byte("hello: world!"),
"bar.yaml": []byte("yay: appsets"),
},
}, nil)
},
},
}, args: args{
repoURL: "foo",
}, want: map[string][]byte{
}, args: args{}, want: map[string][]byte{
"foo.json": []byte("hello: world!"),
"bar.yaml": []byte("yay: appsets"),
}, wantErr: assert.NoError},
{name: "ErrorVerifyingCommit", fields: fields{
getRepository: func(_ context.Context, _, _ string) (*v1alpha1.Repository, error) {
return &v1alpha1.Repository{}, nil
},
getGitFiles: func(_ context.Context, _ *apiclient.GitFilesRequest) (*apiclient.GitFilesResponse, error) {
return nil, errors.New("revision HEAD is not signed")
},
}, args: args{}, want: nil, wantErr: assert.Error},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &argoCDService{
getRepository: tt.fields.getRepository,
submoduleEnabled: tt.fields.submoduleEnabled,
getGitFilesFromRepoServer: tt.fields.getGitFiles,
mockDb := &mocks.RepositoryDB{}
mockRepoClient := &repo_mocks.RepoServerServiceClient{}
// decorate the mocks
for i := range tt.fields.repositoriesDBFuncs {
tt.fields.repositoriesDBFuncs[i](mockDb)
}
got, err := a.GetFiles(tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern, "", tt.args.noRevisionCache, tt.args.verifyCommit)
for i := range tt.fields.repoServerClientFuncs {
tt.fields.repoServerClientFuncs[i](mockRepoClient)
}
a := &argoCDService{
repositoriesDB: mockDb,
storecreds: tt.fields.storecreds,
submoduleEnabled: tt.fields.submoduleEnabled,
repoServerClientSet: &repo_mocks.Clientset{RepoServerServiceClient: mockRepoClient},
}
got, err := a.GetFiles(tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern, tt.args.noRevisionCache)
if !tt.wantErr(t, err, fmt.Sprintf("GetFiles(%v, %v, %v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern, tt.args.noRevisionCache)) {
return
}
@@ -169,9 +187,7 @@ func TestGetFiles(t *testing.T) {
}
func TestNewArgoCDService(t *testing.T) {
testNamespace := "test"
clientset := fake.NewClientset()
testDB := db.NewDB(testNamespace, settings.NewSettingsManager(t.Context(), clientset, testNamespace), clientset)
service := NewArgoCDService(testDB, false, &repo_mocks.Clientset{}, false)
service, err := NewArgoCDService(&db_mocks.ArgoDB{}, false, &repo_mocks.Clientset{}, false)
assert.NoError(t, err, err)
assert.NotNil(t, service)
}

View File

@@ -2,14 +2,13 @@ package scm_provider
import (
"context"
"errors"
"fmt"
"github.com/aws/aws-sdk-go/aws/request"
pathpkg "path"
"path/filepath"
"strings"
"github.com/aws/aws-sdk-go/aws/request"
application "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/aws/awserr"
@@ -20,14 +19,12 @@ import (
log "github.com/sirupsen/logrus"
"golang.org/x/exp/maps"
"k8s.io/utils/strings/slices"
application "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
)
const (
resourceTypeCodeCommitRepository = "codecommit:repository"
prefixGitURLHTTPS = "https://git-codecommit."
prefixGitURLHTTPSFIPS = "https://git-codecommit-fips."
prefixGitUrlHttps = "https://git-codecommit."
prefixGitUrlHttpsFIPS = "https://git-codecommit-fips."
)
// AWSCodeCommitClient is a lean facade to the codecommitiface.CodeCommitAPI
@@ -315,21 +312,20 @@ func getCodeCommitRepoName(repoArn string) (string, error) {
// getCodeCommitFIPSEndpoint transforms provided https:// codecommit URL to a FIPS-compliant endpoint.
// note that the specified region must support FIPS, otherwise the returned URL won't be reachable
// see: https://docs.aws.amazon.com/codecommit/latest/userguide/regions.html#regions-git
func getCodeCommitFIPSEndpoint(repoURL string) (string, error) {
if strings.HasPrefix(repoURL, prefixGitURLHTTPSFIPS) {
log.Debugf("provided repoUrl %s is already a fips endpoint", repoURL)
return repoURL, nil
func getCodeCommitFIPSEndpoint(repoUrl string) (string, error) {
if strings.HasPrefix(repoUrl, prefixGitUrlHttpsFIPS) {
log.Debugf("provided repoUrl %s is already a fips endpoint", repoUrl)
return repoUrl, nil
}
if !strings.HasPrefix(repoURL, prefixGitURLHTTPS) {
return "", fmt.Errorf("the provided https endpoint isn't recognized, cannot be transformed to FIPS endpoint: %s", repoURL)
if !strings.HasPrefix(repoUrl, prefixGitUrlHttps) {
return "", fmt.Errorf("the provided https endpoint isn't recognized, cannot be transformed to FIPS endpoint: %s", repoUrl)
}
// we already have the prefix, so we guarantee to replace exactly the prefix only.
return strings.Replace(repoURL, prefixGitURLHTTPS, prefixGitURLHTTPSFIPS, 1), nil
return strings.Replace(repoUrl, prefixGitUrlHttps, prefixGitUrlHttpsFIPS, 1), nil
}
func hasAwsError(err error, codes ...string) bool {
var awsErr awserr.Error
if errors.As(err, &awsErr) {
if awsErr, ok := err.(awserr.Error); ok {
return slices.Contains(codes, awsErr.Code())
}
return false
@@ -358,7 +354,7 @@ func createAWSDiscoveryClients(_ context.Context, role string, region string) (*
Credentials: assumeRoleCreds,
})
if err != nil {
return nil, nil, fmt.Errorf("error creating new AWS discovery session: %w", err)
return nil, nil, fmt.Errorf("error creating new AWS discovery session: %s", err)
}
} else {
log.Debugf("role is not provided for AWS CodeCommit discovery, using pod role")

View File

@@ -1,4 +1,4 @@
// Code generated by mockery v2.52.4. DO NOT EDIT.
// Code generated by mockery v2.26.1. DO NOT EDIT.
package mocks
@@ -17,6 +17,14 @@ type AWSCodeCommitClient struct {
mock.Mock
}
type AWSCodeCommitClient_Expecter struct {
mock *mock.Mock
}
func (_m *AWSCodeCommitClient) EXPECT() *AWSCodeCommitClient_Expecter {
return &AWSCodeCommitClient_Expecter{mock: &_m.Mock}
}
// GetFolderWithContext provides a mock function with given fields: _a0, _a1, _a2
func (_m *AWSCodeCommitClient) GetFolderWithContext(_a0 context.Context, _a1 *codecommit.GetFolderInput, _a2 ...request.Option) (*codecommit.GetFolderOutput, error) {
_va := make([]interface{}, len(_a2))
@@ -28,10 +36,6 @@ func (_m *AWSCodeCommitClient) GetFolderWithContext(_a0 context.Context, _a1 *co
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for GetFolderWithContext")
}
var r0 *codecommit.GetFolderOutput
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.GetFolderInput, ...request.Option) (*codecommit.GetFolderOutput, error)); ok {
@@ -54,6 +58,43 @@ func (_m *AWSCodeCommitClient) GetFolderWithContext(_a0 context.Context, _a1 *co
return r0, r1
}
// AWSCodeCommitClient_GetFolderWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFolderWithContext'
type AWSCodeCommitClient_GetFolderWithContext_Call struct {
*mock.Call
}
// GetFolderWithContext is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *codecommit.GetFolderInput
// - _a2 ...request.Option
func (_e *AWSCodeCommitClient_Expecter) GetFolderWithContext(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *AWSCodeCommitClient_GetFolderWithContext_Call {
return &AWSCodeCommitClient_GetFolderWithContext_Call{Call: _e.mock.On("GetFolderWithContext",
append([]interface{}{_a0, _a1}, _a2...)...)}
}
func (_c *AWSCodeCommitClient_GetFolderWithContext_Call) Run(run func(_a0 context.Context, _a1 *codecommit.GetFolderInput, _a2 ...request.Option)) *AWSCodeCommitClient_GetFolderWithContext_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]request.Option, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(request.Option)
}
}
run(args[0].(context.Context), args[1].(*codecommit.GetFolderInput), variadicArgs...)
})
return _c
}
func (_c *AWSCodeCommitClient_GetFolderWithContext_Call) Return(_a0 *codecommit.GetFolderOutput, _a1 error) *AWSCodeCommitClient_GetFolderWithContext_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AWSCodeCommitClient_GetFolderWithContext_Call) RunAndReturn(run func(context.Context, *codecommit.GetFolderInput, ...request.Option) (*codecommit.GetFolderOutput, error)) *AWSCodeCommitClient_GetFolderWithContext_Call {
_c.Call.Return(run)
return _c
}
// GetRepositoryWithContext provides a mock function with given fields: _a0, _a1, _a2
func (_m *AWSCodeCommitClient) GetRepositoryWithContext(_a0 context.Context, _a1 *codecommit.GetRepositoryInput, _a2 ...request.Option) (*codecommit.GetRepositoryOutput, error) {
_va := make([]interface{}, len(_a2))
@@ -65,10 +106,6 @@ func (_m *AWSCodeCommitClient) GetRepositoryWithContext(_a0 context.Context, _a1
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for GetRepositoryWithContext")
}
var r0 *codecommit.GetRepositoryOutput
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.GetRepositoryInput, ...request.Option) (*codecommit.GetRepositoryOutput, error)); ok {
@@ -91,6 +128,43 @@ func (_m *AWSCodeCommitClient) GetRepositoryWithContext(_a0 context.Context, _a1
return r0, r1
}
// AWSCodeCommitClient_GetRepositoryWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRepositoryWithContext'
type AWSCodeCommitClient_GetRepositoryWithContext_Call struct {
*mock.Call
}
// GetRepositoryWithContext is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *codecommit.GetRepositoryInput
// - _a2 ...request.Option
func (_e *AWSCodeCommitClient_Expecter) GetRepositoryWithContext(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
return &AWSCodeCommitClient_GetRepositoryWithContext_Call{Call: _e.mock.On("GetRepositoryWithContext",
append([]interface{}{_a0, _a1}, _a2...)...)}
}
func (_c *AWSCodeCommitClient_GetRepositoryWithContext_Call) Run(run func(_a0 context.Context, _a1 *codecommit.GetRepositoryInput, _a2 ...request.Option)) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]request.Option, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(request.Option)
}
}
run(args[0].(context.Context), args[1].(*codecommit.GetRepositoryInput), variadicArgs...)
})
return _c
}
func (_c *AWSCodeCommitClient_GetRepositoryWithContext_Call) Return(_a0 *codecommit.GetRepositoryOutput, _a1 error) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AWSCodeCommitClient_GetRepositoryWithContext_Call) RunAndReturn(run func(context.Context, *codecommit.GetRepositoryInput, ...request.Option) (*codecommit.GetRepositoryOutput, error)) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
_c.Call.Return(run)
return _c
}
// ListBranchesWithContext provides a mock function with given fields: _a0, _a1, _a2
func (_m *AWSCodeCommitClient) ListBranchesWithContext(_a0 context.Context, _a1 *codecommit.ListBranchesInput, _a2 ...request.Option) (*codecommit.ListBranchesOutput, error) {
_va := make([]interface{}, len(_a2))
@@ -102,10 +176,6 @@ func (_m *AWSCodeCommitClient) ListBranchesWithContext(_a0 context.Context, _a1
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for ListBranchesWithContext")
}
var r0 *codecommit.ListBranchesOutput
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.ListBranchesInput, ...request.Option) (*codecommit.ListBranchesOutput, error)); ok {
@@ -128,6 +198,43 @@ func (_m *AWSCodeCommitClient) ListBranchesWithContext(_a0 context.Context, _a1
return r0, r1
}
// AWSCodeCommitClient_ListBranchesWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListBranchesWithContext'
type AWSCodeCommitClient_ListBranchesWithContext_Call struct {
*mock.Call
}
// ListBranchesWithContext is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *codecommit.ListBranchesInput
// - _a2 ...request.Option
func (_e *AWSCodeCommitClient_Expecter) ListBranchesWithContext(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *AWSCodeCommitClient_ListBranchesWithContext_Call {
return &AWSCodeCommitClient_ListBranchesWithContext_Call{Call: _e.mock.On("ListBranchesWithContext",
append([]interface{}{_a0, _a1}, _a2...)...)}
}
func (_c *AWSCodeCommitClient_ListBranchesWithContext_Call) Run(run func(_a0 context.Context, _a1 *codecommit.ListBranchesInput, _a2 ...request.Option)) *AWSCodeCommitClient_ListBranchesWithContext_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]request.Option, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(request.Option)
}
}
run(args[0].(context.Context), args[1].(*codecommit.ListBranchesInput), variadicArgs...)
})
return _c
}
func (_c *AWSCodeCommitClient_ListBranchesWithContext_Call) Return(_a0 *codecommit.ListBranchesOutput, _a1 error) *AWSCodeCommitClient_ListBranchesWithContext_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AWSCodeCommitClient_ListBranchesWithContext_Call) RunAndReturn(run func(context.Context, *codecommit.ListBranchesInput, ...request.Option) (*codecommit.ListBranchesOutput, error)) *AWSCodeCommitClient_ListBranchesWithContext_Call {
_c.Call.Return(run)
return _c
}
// ListRepositoriesWithContext provides a mock function with given fields: _a0, _a1, _a2
func (_m *AWSCodeCommitClient) ListRepositoriesWithContext(_a0 context.Context, _a1 *codecommit.ListRepositoriesInput, _a2 ...request.Option) (*codecommit.ListRepositoriesOutput, error) {
_va := make([]interface{}, len(_a2))
@@ -139,10 +246,6 @@ func (_m *AWSCodeCommitClient) ListRepositoriesWithContext(_a0 context.Context,
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for ListRepositoriesWithContext")
}
var r0 *codecommit.ListRepositoriesOutput
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.ListRepositoriesInput, ...request.Option) (*codecommit.ListRepositoriesOutput, error)); ok {
@@ -165,12 +268,50 @@ func (_m *AWSCodeCommitClient) ListRepositoriesWithContext(_a0 context.Context,
return r0, r1
}
// NewAWSCodeCommitClient creates a new instance of AWSCodeCommitClient. 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 NewAWSCodeCommitClient(t interface {
// AWSCodeCommitClient_ListRepositoriesWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListRepositoriesWithContext'
type AWSCodeCommitClient_ListRepositoriesWithContext_Call struct {
*mock.Call
}
// ListRepositoriesWithContext is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *codecommit.ListRepositoriesInput
// - _a2 ...request.Option
func (_e *AWSCodeCommitClient_Expecter) ListRepositoriesWithContext(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
return &AWSCodeCommitClient_ListRepositoriesWithContext_Call{Call: _e.mock.On("ListRepositoriesWithContext",
append([]interface{}{_a0, _a1}, _a2...)...)}
}
func (_c *AWSCodeCommitClient_ListRepositoriesWithContext_Call) Run(run func(_a0 context.Context, _a1 *codecommit.ListRepositoriesInput, _a2 ...request.Option)) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]request.Option, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(request.Option)
}
}
run(args[0].(context.Context), args[1].(*codecommit.ListRepositoriesInput), variadicArgs...)
})
return _c
}
func (_c *AWSCodeCommitClient_ListRepositoriesWithContext_Call) Return(_a0 *codecommit.ListRepositoriesOutput, _a1 error) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AWSCodeCommitClient_ListRepositoriesWithContext_Call) RunAndReturn(run func(context.Context, *codecommit.ListRepositoriesInput, ...request.Option) (*codecommit.ListRepositoriesOutput, error)) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
_c.Call.Return(run)
return _c
}
type mockConstructorTestingTNewAWSCodeCommitClient interface {
mock.TestingT
Cleanup(func())
}) *AWSCodeCommitClient {
}
// NewAWSCodeCommitClient creates a new instance of AWSCodeCommitClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewAWSCodeCommitClient(t mockConstructorTestingTNewAWSCodeCommitClient) *AWSCodeCommitClient {
mock := &AWSCodeCommitClient{}
mock.Mock.Test(t)

View File

@@ -1,4 +1,4 @@
// Code generated by mockery v2.52.4. DO NOT EDIT.
// Code generated by mockery v2.26.1. DO NOT EDIT.
package mocks
@@ -16,6 +16,14 @@ type AWSTaggingClient struct {
mock.Mock
}
type AWSTaggingClient_Expecter struct {
mock *mock.Mock
}
func (_m *AWSTaggingClient) EXPECT() *AWSTaggingClient_Expecter {
return &AWSTaggingClient_Expecter{mock: &_m.Mock}
}
// GetResourcesWithContext provides a mock function with given fields: _a0, _a1, _a2
func (_m *AWSTaggingClient) GetResourcesWithContext(_a0 context.Context, _a1 *resourcegroupstaggingapi.GetResourcesInput, _a2 ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error) {
_va := make([]interface{}, len(_a2))
@@ -27,10 +35,6 @@ func (_m *AWSTaggingClient) GetResourcesWithContext(_a0 context.Context, _a1 *re
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for GetResourcesWithContext")
}
var r0 *resourcegroupstaggingapi.GetResourcesOutput
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error)); ok {
@@ -53,12 +57,50 @@ func (_m *AWSTaggingClient) GetResourcesWithContext(_a0 context.Context, _a1 *re
return r0, r1
}
// NewAWSTaggingClient creates a new instance of AWSTaggingClient. 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 NewAWSTaggingClient(t interface {
// AWSTaggingClient_GetResourcesWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetResourcesWithContext'
type AWSTaggingClient_GetResourcesWithContext_Call struct {
*mock.Call
}
// GetResourcesWithContext is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *resourcegroupstaggingapi.GetResourcesInput
// - _a2 ...request.Option
func (_e *AWSTaggingClient_Expecter) GetResourcesWithContext(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *AWSTaggingClient_GetResourcesWithContext_Call {
return &AWSTaggingClient_GetResourcesWithContext_Call{Call: _e.mock.On("GetResourcesWithContext",
append([]interface{}{_a0, _a1}, _a2...)...)}
}
func (_c *AWSTaggingClient_GetResourcesWithContext_Call) Run(run func(_a0 context.Context, _a1 *resourcegroupstaggingapi.GetResourcesInput, _a2 ...request.Option)) *AWSTaggingClient_GetResourcesWithContext_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]request.Option, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(request.Option)
}
}
run(args[0].(context.Context), args[1].(*resourcegroupstaggingapi.GetResourcesInput), variadicArgs...)
})
return _c
}
func (_c *AWSTaggingClient_GetResourcesWithContext_Call) Return(_a0 *resourcegroupstaggingapi.GetResourcesOutput, _a1 error) *AWSTaggingClient_GetResourcesWithContext_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AWSTaggingClient_GetResourcesWithContext_Call) RunAndReturn(run func(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error)) *AWSTaggingClient_GetResourcesWithContext_Call {
_c.Call.Return(run)
return _c
}
type mockConstructorTestingTNewAWSTaggingClient interface {
mock.TestingT
Cleanup(func())
}) *AWSTaggingClient {
}
// NewAWSTaggingClient creates a new instance of AWSTaggingClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewAWSTaggingClient(t mockConstructorTestingTNewAWSTaggingClient) *AWSTaggingClient {
mock := &AWSTaggingClient{}
mock.Mock.Test(t)

View File

@@ -1,19 +1,19 @@
package scm_provider
import (
"context"
"errors"
"sort"
"testing"
"github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider/aws_codecommit/mocks"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/codecommit"
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/argoproj/argo-cd/v3/applicationset/services/scm_provider/aws_codecommit/mocks"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
)
type awsCodeCommitTestRepository struct {
@@ -22,7 +22,7 @@ type awsCodeCommitTestRepository struct {
arn string
accountId string
defaultBranch string
expectedCloneURL string
expectedCloneUrl string
getRepositoryError error
getRepositoryNilMetadata bool
valid bool
@@ -48,7 +48,7 @@ func TestAWSCodeCommitListRepos(t *testing.T) {
id: "8235624d-d248-4df9-a983-2558b01dbe83",
arn: "arn:aws:codecommit:us-east-1:111111111111:repo1",
defaultBranch: "main",
expectedCloneURL: "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/repo1",
expectedCloneUrl: "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/repo1",
valid: true,
},
},
@@ -73,7 +73,7 @@ func TestAWSCodeCommitListRepos(t *testing.T) {
id: "8235624d-d248-4df9-a983-2558b01dbe83",
arn: "arn:aws:codecommit:us-east-1:111111111111:repo1",
defaultBranch: "main",
expectedCloneURL: "https://git-codecommit-fips.us-east-1.amazonaws.com/v1/repos/repo1",
expectedCloneUrl: "https://git-codecommit-fips.us-east-1.amazonaws.com/v1/repos/repo1",
valid: true,
},
},
@@ -95,7 +95,7 @@ func TestAWSCodeCommitListRepos(t *testing.T) {
id: "8235624d-d248-4df9-a983-2558b01dbe83",
arn: "arn:aws:codecommit:us-east-1:111111111111:repo1",
defaultBranch: "main",
expectedCloneURL: "ssh://git-codecommit.us-east-1.amazonaws.com/v1/repos/repo1",
expectedCloneUrl: "ssh://git-codecommit.us-east-1.amazonaws.com/v1/repos/repo1",
valid: true,
},
{
@@ -159,7 +159,7 @@ func TestAWSCodeCommitListRepos(t *testing.T) {
t.Run(testCase.name, func(t *testing.T) {
codeCommitClient := mocks.NewAWSCodeCommitClient(t)
taggingClient := mocks.NewAWSTaggingClient(t)
ctx := t.Context()
ctx := context.Background()
codecommitRepoNameIdPairs := make([]*codecommit.RepositoryNameIdPair, 0)
resourceTaggings := make([]*resourcegroupstaggingapi.ResourceTagMapping, 0)
validRepositories := make([]*awsCodeCommitTestRepository, 0)
@@ -177,8 +177,8 @@ func TestAWSCodeCommitListRepos(t *testing.T) {
if repo.getRepositoryNilMetadata {
repoMetadata = nil
}
codeCommitClient.
On("GetRepositoryWithContext", ctx, &codecommit.GetRepositoryInput{RepositoryName: aws.String(repo.name)}).
codeCommitClient.EXPECT().
GetRepositoryWithContext(ctx, &codecommit.GetRepositoryInput{RepositoryName: aws.String(repo.name)}).
Return(&codecommit.GetRepositoryOutput{RepositoryMetadata: repoMetadata}, repo.getRepositoryError)
codecommitRepoNameIdPairs = append(codecommitRepoNameIdPairs, &codecommit.RepositoryNameIdPair{
RepositoryId: aws.String(repo.id),
@@ -193,14 +193,14 @@ func TestAWSCodeCommitListRepos(t *testing.T) {
}
if testCase.expectListAtCodeCommit {
codeCommitClient.
On("ListRepositoriesWithContext", ctx, &codecommit.ListRepositoriesInput{}).
codeCommitClient.EXPECT().
ListRepositoriesWithContext(ctx, &codecommit.ListRepositoriesInput{}).
Return(&codecommit.ListRepositoriesOutput{
Repositories: codecommitRepoNameIdPairs,
}, testCase.listRepositoryError)
} else {
taggingClient.
On("GetResourcesWithContext", ctx, mock.MatchedBy(equalIgnoringTagFilterOrder(&resourcegroupstaggingapi.GetResourcesInput{
taggingClient.EXPECT().
GetResourcesWithContext(ctx, mock.MatchedBy(equalIgnoringTagFilterOrder(&resourcegroupstaggingapi.GetResourcesInput{
TagFilters: testCase.expectTagFilters,
ResourceTypeFilters: aws.StringSlice([]string{resourceTypeCodeCommitRepository}),
}))).
@@ -225,7 +225,7 @@ func TestAWSCodeCommitListRepos(t *testing.T) {
assert.Equal(t, originRepo.name, repo.Repository)
assert.Equal(t, originRepo.id, repo.RepositoryId)
assert.Equal(t, originRepo.defaultBranch, repo.Branch)
assert.Equal(t, originRepo.expectedCloneURL, repo.URL)
assert.Equal(t, originRepo.expectedCloneUrl, repo.URL)
assert.Empty(t, repo.SHA, "SHA is always empty")
}
}
@@ -348,10 +348,10 @@ func TestAWSCodeCommitRepoHasPath(t *testing.T) {
t.Run(testCase.name, func(t *testing.T) {
codeCommitClient := mocks.NewAWSCodeCommitClient(t)
taggingClient := mocks.NewAWSTaggingClient(t)
ctx := t.Context()
ctx := context.Background()
if testCase.expectedGetFolderPath != "" {
codeCommitClient.
On("GetFolderWithContext", ctx, &codecommit.GetFolderInput{
codeCommitClient.EXPECT().
GetFolderWithContext(ctx, &codecommit.GetFolderInput{
CommitSpecifier: aws.String(branch),
FolderPath: aws.String(testCase.expectedGetFolderPath),
RepositoryName: aws.String(repoName),
@@ -381,7 +381,7 @@ func TestAWSCodeCommitGetBranches(t *testing.T) {
id := "1a64adc4-2fb5-4abd-afe7-127984ba83c0"
defaultBranch := "main"
organization := "111111111111"
cloneURL := "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/repo1"
cloneUrl := "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/repo1"
testCases := []struct {
name string
@@ -421,16 +421,16 @@ func TestAWSCodeCommitGetBranches(t *testing.T) {
t.Run(testCase.name, func(t *testing.T) {
codeCommitClient := mocks.NewAWSCodeCommitClient(t)
taggingClient := mocks.NewAWSTaggingClient(t)
ctx := t.Context()
ctx := context.Background()
if testCase.allBranches {
codeCommitClient.
On("ListBranchesWithContext", ctx, &codecommit.ListBranchesInput{
codeCommitClient.EXPECT().
ListBranchesWithContext(ctx, &codecommit.ListBranchesInput{
RepositoryName: aws.String(name),
}).
Return(&codecommit.ListBranchesOutput{Branches: aws.StringSlice(testCase.branches)}, testCase.apiError)
} else {
codeCommitClient.
On("GetRepositoryWithContext", ctx, &codecommit.GetRepositoryInput{RepositoryName: aws.String(name)}).
codeCommitClient.EXPECT().
GetRepositoryWithContext(ctx, &codecommit.GetRepositoryInput{RepositoryName: aws.String(name)}).
Return(&codecommit.GetRepositoryOutput{RepositoryMetadata: &codecommit.RepositoryMetadata{
AccountId: aws.String(organization),
DefaultBranch: aws.String(defaultBranch),
@@ -444,7 +444,7 @@ func TestAWSCodeCommitGetBranches(t *testing.T) {
actual, err := provider.GetBranches(ctx, &Repository{
Organization: organization,
Repository: name,
URL: cloneURL,
URL: cloneUrl,
RepositoryId: id,
})
if testCase.expectOverallError {
@@ -453,7 +453,7 @@ func TestAWSCodeCommitGetBranches(t *testing.T) {
assertCopiedProperties := func(repo *Repository) {
assert.Equal(t, id, repo.RepositoryId)
assert.Equal(t, name, repo.Repository)
assert.Equal(t, cloneURL, repo.URL)
assert.Equal(t, cloneUrl, repo.URL)
assert.Equal(t, organization, repo.Organization)
assert.Empty(t, repo.SHA)
}

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