Compare commits

..

244 Commits

Author SHA1 Message Date
argo-bot
84fbc93016 Bump version to 2.5.16 2023-03-23 14:45:01 +00:00
argo-bot
f80ae69050 Bump version to 2.5.16 2023-03-23 14:44:54 +00:00
Michael Crenshaw
06b9a25f6d Merge pull request from GHSA-2q5c-qw9c-fmvq
* fix: prevent app enumeration

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

* more tests, fix incorrect param use

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

similar requests

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

* fix merge issue

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

* fix CLI to understand permission denied is not a fatal error

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

* fix test to expect permission denied instead of validation error

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

* upgrade notes

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

---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-03-23 09:22:05 -04:00
argo-bot
64a7bb8f02 Bump version to 2.5.15 2023-03-16 22:13:43 +00:00
argo-bot
bc1908cdea Bump version to 2.5.15 2023-03-16 22:13:35 +00:00
Michael Crenshaw
4bacb3a381 fix codegen
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

fix codegen

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

fix codegen

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

fix codegen

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-03-16 16:11:00 -04:00
dependabot[bot]
9e06a83c2f chore(deps): bump actions/setup-go from 3.5.0 to 4.0.0 (#12888)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3.5.0 to 4.0.0.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](6edd4406fa...4d34df0c23)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-16 12:53:32 -04:00
Michael Crenshaw
4a02cf1639 docs: fix version numbers in upgrade notes (#12896)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-03-16 12:51:04 -04:00
dependabot[bot]
7a5cf79984 chore(deps): bump actions/checkout from 3.3.0 to 3.4.0 (#12889)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.3.0 to 3.4.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](ac59398561...24cb908017)

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

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-16 10:49:23 -04:00
jannfis
798642dc19 build: Enable CI checks on PRs to release branches (#12887)
Signed-off-by: jannfis <jann@mistrust.net>
2023-03-16 09:50:40 -04:00
gcp-cherry-pick-bot[bot]
aa60207496 test: wait longer after repo server restarted to avoid errors on s390x (#12839) (#12885)
Signed-off-by: Sam Ding <samding@ca.ibm.com>
Co-authored-by: Sam Ding <samding@ca.ibm.com>
2023-03-16 09:33:09 -04:00
Michael Crenshaw
5611eea361 fix(appset): git files generator in matrix generator produces no params (#12881)
* fix(appset): git files generator in matrix generator produces no params

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

* upgrade notes

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

* fix lint

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

---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-03-16 09:24:52 -04:00
Michael Crenshaw
4376093af0 fix: log plugin commands in a better format (#12260)
* fix: log plugin commands in a better format

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

* comments

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

---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-03-15 09:43:19 -04:00
gcp-cherry-pick-bot[bot]
435691827a docs: cleanup HA operator manual (#10409) (#12866)
Signed-off-by: Prasad Katti <prasadmkatti@gmail.com>
Co-authored-by: Prasad Katti <prasadmkatti@gmail.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-03-15 09:27:21 -04:00
gcp-cherry-pick-bot[bot]
3bcdec30f3 docs: fix list formatting in keycloak.md (#11061) (#12863)
Signed-off-by: Jack Henschel <jackdev@mailbox.org>
Co-authored-by: Jack Henschel <jackdev@mailbox.org>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-03-15 09:26:41 -04:00
Michael Crenshaw
ccd7f76768 fix: support 'project' filter field for backwards-compatibility (#12594)
* fix: support 'project' filter field for backwards-compatibility

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

* fix codegen

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

* add upgrade notes

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

* fix upgrade notes

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

* tests

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

---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-03-14 17:37:13 -04:00
gcp-cherry-pick-bot[bot]
c2cb6691dd docs: Post Selector moved to Generators section (#11109) (#12857)
Co-authored-by: Guðmundur Kristinn Ögmundsson <gummikr@icelandair.is>
2023-03-14 14:41:39 -04:00
dependabot[bot]
eb06207959 chore(deps): bump actions/cache from 3.2.6 to 3.3.1 (#12845)
Bumps [actions/cache](https://github.com/actions/cache) from 3.2.6 to 3.3.1.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](69d9d449ac...88522ab9f3)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-14 13:56:55 -04:00
argo-bot
23c950728a Bump version to 2.5.14 2023-03-14 14:09:54 +00:00
argo-bot
1c0a505140 Bump version to 2.5.14 2023-03-14 14:09:44 +00:00
Michael Crenshaw
b1986bb6a5 chore: upgrade https lib to avoid CVE-2022-41723
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-03-13 09:58:14 -04:00
gcp-cherry-pick-bot[bot]
36a134fa4a fix: ignore gitlab projects w/o repository (#12819) (#12820) (#12826)
* fix: ignore gitlab projects w/o repository (#12819)



* chore: Add Redpill Linpro to USERS.md



---------

Signed-off-by: Pip Oomen <pepijn@redpill-linpro.com>
Co-authored-by: Pip Oomen <oomen@piprograms.com>
2023-03-11 20:02:35 -05:00
gcp-cherry-pick-bot[bot]
b4df63495a docs: Fix Jenkins guide link in understand_the_basics.md (#12814) (#12817)
Signed-off-by: Arkadiusz Podkowa <55452766+czuhajster@users.noreply.github.com>
Co-authored-by: Arkadiusz Podkowa <55452766+czuhajster@users.noreply.github.com>
2023-03-10 16:36:37 -05:00
Michael Crenshaw
1a5bcf858a fix: use field-wise templating for child matrix generators (#11661) (#12287)
* fix: use field-wise templating for child matrix generators (#11661)

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

* test shouldn't use go template

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

* Update applicationset/utils/utils.go

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

---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-03-08 13:12:57 -05:00
gcp-cherry-pick-bot[bot]
c9ca3ff5f6 fix: Fix the applicationset kind typo (#12690) (#12766)
Signed-off-by: Shaw Ho <tossmilestone@gmail.com>
Co-authored-by: Shaw Ho <tossmilestone@gmail.com>
2023-03-08 09:43:41 -05:00
argo-bot
64a09a1fb6 Bump version to 2.5.13 2023-03-07 22:35:04 +00:00
argo-bot
bb24b5c38a Bump version to 2.5.13 2023-03-07 22:35:00 +00:00
gcp-cherry-pick-bot[bot]
1125042e5b fix: typo in doc link (#12744) (#12761)
Signed-off-by: Noah Krause <krausenoah@gmail.com>
Co-authored-by: Noah Krause <krausenoah@gmail.com>
2023-03-07 16:40:36 -05:00
gcp-cherry-pick-bot[bot]
471e386ff0 fix: Validate chat button url only when chatUrl is set (#12655) (#12749) (#12758)
* Validate chat button url only when chatUrl is set



* Add Info Support to argocd USERS.md



* Fix linter error



* Fix linter error



---------

Signed-off-by: Rouke Broersma <rouke.broersma@infosupport.com>
Co-authored-by: Rouke Broersma <rouke.broersma@infosupport.com>
2023-03-07 14:38:13 -05:00
Tsubasa Nagasawa
ae3f5402ab fix: suppress Kubernetes API deprecation warnings from application controller (#12067)
Completely suppress warning logs only for log levels that are less than Debug.

Signed-off-by: toVersus <toversus2357@gmail.com>
2023-03-06 16:50:16 -05:00
gcp-cherry-pick-bot[bot]
1dfb61ec60 docs: Update kustomization example (#12555) (#12739)
...to align with documented usage of kustomize.

As it was, this example stops working with Kustomize v5

Signed-off-by: Jonas Bergler <jonas@bergler.name>
Co-authored-by: Jonas Bergler <jonas@bergler.name>
2023-03-06 16:40:41 -05:00
dependabot[bot]
4cb8de4501 chore(deps): bump actions/cache from 3.2.5 to 3.2.6 (#12567)
Bumps [actions/cache](https://github.com/actions/cache) from 3.2.5 to 3.2.6.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](6998d139dd...69d9d449ac)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-06 16:36:46 -05:00
dependabot[bot]
403ec2786b chore(deps): bump sigstore/cosign-installer from 2.8.1 to 3.0.1 (#12689)
Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 2.8.1 to 3.0.1.
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](9becc61764...c3667d9942)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: sigstore/cosign-installer
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-06 16:34:51 -05:00
gcp-cherry-pick-bot[bot]
9075a56650 docs: unset finalizer before deleting an app non-cascadingly (#10949) (#12734)
Signed-off-by: Bo Huang <beyondbill@users.noreply.github.com>
Co-authored-by: Bo Huang <beyondbill@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-03-06 14:53:13 -05:00
gcp-cherry-pick-bot[bot]
5ccaafc504 fix: ensure certificate gets updated on reload (#12076) (#12695)
* fix: ensure certificate gets updated on reload

Fixes #10707. `GetCertificate` ensures that the most current version of
 `a.settings.Certificate` is used. It's still a bit of a mystery to me
 as to why the reloading of the server does not work for this, since it
 should fulfill the same function.



* fix: remove break from cert changes

With 3553ef8, there's no longer any need to break out of the loop. The
webhook reloading logic needs another look (since it likely no longer
works), but can be handled in another PR.



---------

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
2023-03-02 09:27:34 -05:00
gcp-cherry-pick-bot[bot]
58bb6adc79 docs: link directly to HA manifests (#11970) (#12683)
This updates the manifest link directly to the High Availability header in the manifest readme. I chose this over linking to the `ha` folder since it explains the options and links to them.

Signed-off-by: Nicholas Morey <nicholas@morey.tech>
Co-authored-by: Nicholas Morey <nicholas@morey.tech>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-03-01 16:59:21 -05:00
Justin Marquis
7aa54a5e17 chore: upgrade redis to 7.0.8 to avoid several CVEs (#12627)
Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
2023-02-28 09:26:46 -05:00
gcp-cherry-pick-bot[bot]
6ade5f2bad chore: upgrade haproxy to 2.6.9 to avoid multiple CVEs (#12628) (#12659)
Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
Co-authored-by: Justin Marquis <34fathombelow@protonmail.com>
2023-02-28 09:25:44 -05:00
argo-bot
9cd67b16f3 Bump version to 2.5.12 2023-02-27 14:28:40 +00:00
argo-bot
33d756e8d9 Bump version to 2.5.12 2023-02-27 14:28:36 +00:00
gcp-cherry-pick-bot[bot]
48edd4d998 fix: traverse generator tree when getting requeue time (#12407) (#12409) (#12611)
* add unit test reproducing




* feat: Begin polishing top bar design (#12327)



* chore: add dist to path to use our kustomize version (#12352)

* chore: add dist to path to use our kustomize version



* correct path



* missed a spot



---------




* fix: when resource does not exist node menu and resource details shou… (#12360)

* fix: when resource does not exist node menu and resource details should still render



* Retrigger CI pipeline



---------




* fix: traverse generator tree when getting requeue time



* fix: traverse generator tree when getting requeue time



* remove duplicate code



* Retrigger CI pipeline



* revert gitignore



* update from code review



---------

Signed-off-by: rumstead <rjumstead@gmail.com>
Signed-off-by: rumstead <37445536+rumstead@users.noreply.github.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: Joshua Helton <jdoghelton@gmail.com>
Co-authored-by: rumstead <37445536+rumstead@users.noreply.github.com>
Co-authored-by: Remington Breeze <remington@breeze.software>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: jphelton <jdoghelton@gmail.com>
2023-02-24 16:29:14 -05:00
Justin Marquis
3e96e915dd chore: use registry.k8s.io instead of k8s.gcr.io (#12362)
Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-02-17 15:13:45 -05:00
dependabot[bot]
7530d3ff8e chore(deps): bump imjasonh/setup-crane from 0.2 to 0.3 (#12504)
Bumps [imjasonh/setup-crane](https://github.com/imjasonh/setup-crane) from 0.2 to 0.3.
- [Release notes](https://github.com/imjasonh/setup-crane/releases)
- [Commits](e82f1b9a80...00c9e93efa)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: imjasonh/setup-crane
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-17 14:40:03 -05:00
Josh Soref
6c3b07f56d docs: FAQ improvements (#12146)
* docs: use more backticks in FAQ

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

* docs: add FAQ entry

The order in patch list … doesn't match $setElementOrder list: …

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>

---------

Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-02-17 14:14:54 -05:00
Dmitriy Mann
8833e41511 fix: valid username in webhook URL matching regex (#9055) (#12203)
This commit fixes incorrect regular expression used for URL matching.

Expected behavior: valid user info part is matched => webhook is sent.
Actual behavior: some valid user info is not matched, example: `ssh://user-name@example.com/org/repo` => webhook is not sent.

Context:
 - [RFC 3986 3.2.1 - User Information](https://www.rfc-editor.org/rfc/rfc3986#section-3.2.1)
 - [Username validation regex in shadow Linux package](https://github.com/shadow-maint/shadow/blob/master/libmisc/chkname.c#L36)

Signed-off-by: mdsjip <2284562+mdsjip@users.noreply.github.com>
2023-02-17 14:11:24 -05:00
dependabot[bot]
d3d03868d8 chore(deps): bump docker/setup-buildx-action from 2.4.0 to 2.4.1 (#12308)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.4.0 to 2.4.1.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](15c905b16b...f03ac48505)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-17 14:00:31 -05:00
atusy
7d853494ec docs: fix typo (#12389)
Signed-off-by: atusy <30277794+atusy@users.noreply.github.com>
2023-02-17 13:57:34 -05:00
dependabot[bot]
602f8d07f9 chore(deps): bump actions/cache from 3.2.4 to 3.2.5 (#12433)
Bumps [actions/cache](https://github.com/actions/cache) from 3.2.4 to 3.2.5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](627f0f41f6...6998d139dd)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-17 13:52:41 -05:00
Vaibhav Bhembre
2dfcb49b34 fix: setting spec.SyncPolicy crashes 'argocd appset get' output (#12424) (#12425)
Signed-off-by: Vaibhav Bhembre <vaibhav@digitalocean.com>
2023-02-17 13:51:08 -05:00
Wojtek Cichoń
5eb4e0fee5 docs: Updated link to Jenkins and added GitHub Actions link (#12465)
Signed-off-by: Wojtek Cichoń <wojtek.cichon@protonmail.com>
2023-02-17 13:36:34 -05:00
Zadkiel Aharonian
af9a51603b docs: fix typo in health documentation (#12497)
Signed-off-by: Zadkiel Aharonian <hello@zadkiel.fr>
2023-02-17 13:34:31 -05:00
Saumeya Katyal
62e23e2e80 fix(security): add url validation for help chat (#9956) (#10417)
* fix: add url validation for help chat

Signed-off-by: saumeya <saumeyakatyal@gmail.com>

* lint check

Signed-off-by: saumeya <saumeyakatyal@gmail.com>

* lint fix

Signed-off-by: saumeya <saumeyakatyal@gmail.com>

* review comments

Signed-off-by: saumeya <saumeyakatyal@gmail.com>

---------

Signed-off-by: saumeya <saumeyakatyal@gmail.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-02-17 13:19:41 -05:00
Michael Chen
e5fc4f5a9c docs: Clarify cascade delete of resource and finalizer (#11064)
* Clarify cascade delete of resource and finalizer.

The wording of this warning was confusing.

Signed-off-by: Michael Chen <4326639+mcgitty@users.noreply.github.com>

* Update docs/operator-manual/declarative-setup.md

Co-authored-by: Nicholas Morey <nicholas@morey.tech>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

---------

Signed-off-by: Michael Chen <4326639+mcgitty@users.noreply.github.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Nicholas Morey <nicholas@morey.tech>
2023-02-17 13:17:59 -05:00
schakrad
8ca2b1de52 fix: show full event message in pod event view (#12104) (#12267)
* #11602 fix : Object options menu truncated when selected in ApplicationListView.

Signed-off-by: schakradari <saisindhu_chakradari@intuit.com>

* fix for the message to be fully shown under the events section.

Signed-off-by: schakradari <saisindhu_chakradari@intuit.com>

* fixing lint

Signed-off-by: schakradari <saisindhu_chakradari@intuit.com>

* Update application-resource-list.tsx

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

* fix for  lint error

Signed-off-by: schakradari <saisindhu_chakradari@intuit.com>

---------

Signed-off-by: schakradari <saisindhu_chakradari@intuit.com>
Signed-off-by: schakrad <58915923+schakrad@users.noreply.github.com>
2023-02-17 11:58:03 -05:00
argo-bot
6dd79895e2 Bump version to 2.5.11 2023-02-16 14:52:40 +00:00
argo-bot
333f0d72a1 Bump version to 2.5.11 2023-02-16 14:52:32 +00:00
Michael Crenshaw
5a21561d6e Merge pull request from GHSA-3jfq-742w-xg8j
fix test name

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-02-16 09:07:57 -05:00
Michael Crenshaw
ee8016b3d9 chore: add dist to path to use our kustomize version (#12352)
* chore: add dist to path to use our kustomize version

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

* correct path

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

* missed a spot

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

---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-02-09 12:18:15 -05:00
Jake
2a584836b8 fix: don't dump SSG to server logs, change deprecation notice to v2.7 (#12285)
* fix: don't dump SSG to server logs, change deprecation notice to v2.7

Signed-off-by: notfromstatefarm <86763948+notfromstatefarm@users.noreply.github.com>

* Update server/server.go

Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: Jake <86763948+notfromstatefarm@users.noreply.github.com>

---------

Signed-off-by: notfromstatefarm <86763948+notfromstatefarm@users.noreply.github.com>
Signed-off-by: Jake <86763948+notfromstatefarm@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-02-03 13:32:57 -05:00
Josh Soref
3d328268f0 docs: Fix heading to not include a v for the second version (#12218)
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-02-03 10:29:21 -05:00
Ishita Sequeira
26f116af70 fix: return nil if reading application set was successful (#12261)
Signed-off-by: ishitasequeira <ishiseq29@gmail.com>
2023-02-03 10:05:53 -05:00
Thomas Decaux
a08a6fc7d9 docs: add destination.name example (#12242)
I had trouble finding the documentation to use the cluster name for destination, instead of the full URL. This is really useful.

Use case: we manage multiple clusters, destination.name is a better way to set destination.

Signed-off-by: Thomas Decaux <ebuildy@gmail.com>
Signed-off-by: ebuildy <ebuildy@gmail.com>
2023-02-02 12:56:19 -05:00
argo-bot
d311fad538 Bump version to 2.5.10 2023-02-02 14:57:45 +00:00
argo-bot
a18be650b8 Bump version to 2.5.10 2023-02-02 14:57:39 +00:00
Panagiotis Georgiadis
72013390bd fix: Upgrade gopkg.in/yaml.v2 to v2.2.4 (#12246)
Signed-off-by: Panagiotis Georgiadis <pgeorgia@redhat.com>
2023-02-01 16:06:54 -05:00
Michael Crenshaw
93547ce7ea chore: remove unnecessary whitespace
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-02-01 13:39:50 -05:00
Jaideep Rao
dbf5ea5935 fix: Upgrade goutils to v1.1.1 [release-2.5] (#12219) (#12220)
Signed-off-by: Jaideep Rao <jaideep.r97@gmail.com>
2023-02-01 13:13:53 -05:00
dependabot[bot]
fa1fd6965e chore(deps): bump docker/setup-buildx-action from 2.2.1 to 2.4.0 (#12227)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.2.1 to 2.4.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](8c0edbc76e...15c905b16b)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-31 16:55:19 -05:00
dependabot[bot]
1f7e47f747 chore(deps): bump actions/cache from 3.2.3 to 3.2.4 (#12228)
Bumps [actions/cache](https://github.com/actions/cache) from 3.2.3 to 3.2.4.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](58c146cc91...627f0f41f6)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-31 16:53:42 -05:00
Adam Jensen
2d9dc7f75a docs: Fix copy that refers to a different CLI flag (#12236)
Signed-off-by: Adam Jensen <adam@acj.sh>
2023-01-31 16:52:02 -05:00
Kostis (Codefresh)
f93dc55a55 docs: Clarify directory recursion (#12037)
Signed-off-by: Kostis Kapelonis <kostis@codefresh.io>
2023-01-31 16:29:40 -05:00
James Brady
46ec2d1b46 docs: Fix list formatting in "Resource Actions" docs page (#12061)
Signed-off-by: James Brady <goodgravy@users.noreply.github.com>
2023-01-31 16:26:41 -05:00
Alex Eftimie
b413afd522 fix: backport values should always be visible on 2.5 (#11681) (#12139)
Signed-off-by: Alex Eftimie <alex.eftimie@getyourguide.com>
2023-01-31 15:59:47 -05:00
Nobuo Takizawa
702082e4c9 chore: Update dex's image tag that is forgotten to be updated (#12234)
Signed-off-by: nobuyo <longzechangsheng@gmail.com>
2023-01-31 15:43:30 -05:00
argo-bot
e5f1194a6d Bump version to 2.5.9 2023-01-27 23:14:41 +00:00
argo-bot
ef4f103ee8 Bump version to 2.5.9 2023-01-27 23:14:35 +00:00
Eugen Friedland
1925612f1b fix(health): Handling SparkApplication CRD health status if dynamic allocation is enabled (#7557) (#11522)
Signed-off-by: Yevgeniy Fridland <yevg.mord@gmail.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-01-27 15:17:20 -05:00
Michael Crenshaw
86f75b05ea fix: add CLI client IDs to default OIDC allowed audiences (#12170) (#12179)
* fix(settings): add CLI client ID in default OAuth2 allowed audiences

Signed-off-by: Yann Soubeyrand <yann.soubeyrand@camptocamp.com>

* fix: add CLI client IDs to default OIDC allowed audiences (#12170)

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

* docs

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

* test

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

* handle expired token properly

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

---------

Signed-off-by: Yann Soubeyrand <yann.soubeyrand@camptocamp.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Yann Soubeyrand <yann.soubeyrand@camptocamp.com>
2023-01-27 14:43:09 -05:00
Leonardo Luz Almeida
35546fd856 chore: Refactor terminal handler to use auth-middleware (#12052)
* chore: Refactor terminal handler to use auth-middleware

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

* remove context key for now

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

* implement unit-tests

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

* remove claim valid check for now

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

* remove unnecessary test

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

* fix lint

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

* don't too much details in http response

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

* Fix error

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

* Fix lint

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

* trigger build

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

* builder pattern in terminal feature-flag middleware

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

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>
2023-01-27 13:25:34 -05:00
Ian Delahorne
9ce407fec0 fix: Add namespace to sub-application link URLs (#11946)
Signed-off-by: Ian Delahorne <ian@patreon.com>
Co-authored-by: Remington Breeze <remington@breeze.software>
2023-01-27 13:19:36 -05:00
argo-bot
bbe870ff59 Bump version to 2.5.8 2023-01-25 16:01:15 +00:00
argo-bot
af321b8ff3 Bump version to 2.5.8 2023-01-25 16:01:01 +00:00
Dan Garfield
50b9f19d3c Merge pull request from GHSA-q9hr-j4rf-8fjc
* fix: verify audience claim

Co-Authored-By: Vladimir Pouzanov <farcaller@gmail.com>
Signed-off-by: CI <350466+crenshaw-dev@users.noreply.github.com>

* fix lint

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

* go mod tidy

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

* handle single aud claim marshaled as a string

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

Signed-off-by: CI <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: CI <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Vladimir Pouzanov <farcaller@gmail.com>
2023-01-25 09:15:04 -05:00
Dan Garfield
1f82078e74 Merge pull request from GHSA-6p4m-hw2h-6gmw
Signed-off-by: ChangZhuo Chen (陳昌倬) <czchen@czchen.org>

add test

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

better comment

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

Signed-off-by: CI <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: ChangZhuo Chen (陳昌倬) <czchen@czchen.org>
2023-01-25 09:14:29 -05:00
Mike Bryant
37c08332d5 fix: Support resource actions for apps in different Namespace (#12115)
Signed-off-by: Mike Bryant <mike.bryant@mettle.co.uk>

Signed-off-by: Mike Bryant <mike.bryant@mettle.co.uk>
2023-01-24 17:32:54 -05:00
Justin Marquis
a94ff15ebd chore: disable docker sbom and attestations (#12059)
Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
2023-01-20 10:03:22 -05:00
Nicholas Morey
4bd9f36182 docs: clarify value for disabling tools (#11395)
* docs: clarify value for disabling tools

Although it is implied to set the value for the key to `false`, this explicitly states it to add clarity. Along with some wording changes.

Signed-off-by: Nicholas Morey <nicholas@morey.tech>

* docs: add use-case for disabling tools

Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: Nicholas Morey <nicholas@morey.tech>

Signed-off-by: Nicholas Morey <nicholas@morey.tech>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-01-18 16:46:48 -05:00
argo-bot
e0ee3458d0 Bump version to 2.5.7 2023-01-18 02:11:47 +00:00
argo-bot
7390335b4a Bump version to 2.5.7 2023-01-18 02:11:43 +00:00
Aymen Ben Tanfous
8d05e6d2df fix: Fixed matrix requeueAfterSeconds for PR (#10914) (#10915)
* Fixed matrix requeueAfterSeconds for PR

Signed-off-by: Aymen Ben Tanfous <aymen.bentanfous@gmail.com>

* A try to make some tests

Signed-off-by: Aymen Ben Tanfous <aymen.bentanfous@cimpress.com>

* Fixed default test returns the default time

Signed-off-by: Aymen Ben Tanfous <aymenbentanfous@gmail.com>

* Fixed default test returns the default time

Signed-off-by: Aymen Ben Tanfous <aymenbentanfous@gmail.com>

Signed-off-by: Aymen Ben Tanfous <aymen.bentanfous@gmail.com>
Signed-off-by: Aymen Ben Tanfous <aymen.bentanfous@cimpress.com>
Signed-off-by: Aymen Ben Tanfous <aymenbentanfous@gmail.com>
Co-authored-by: Aymen Ben Tanfous <aymen.bentanfous@cimpress.com>
Co-authored-by: Aymen Ben Tanfous <aymenbentanfous@gmail.com>
2023-01-17 16:54:15 -05:00
dependabot[bot]
00421bfc9f chore(deps): bump actions/cache from 3.2.2 to 3.2.3 (#11928)
Bumps [actions/cache](https://github.com/actions/cache) from 3.2.2 to 3.2.3.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](4723a57e26...58c146cc91)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-13 09:52:04 -05:00
Alexander Matyushentsev
2949994fbd fix: Argo CD doesn't detect the repo type when repository is scoped (#11959)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2023-01-12 09:22:57 -08:00
dependabot[bot]
037dcb0f1a chore(deps): bump actions/checkout from 3.2.0 to 3.3.0 (#11895)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.2.0 to 3.3.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](755da8c3cf...ac59398561)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-11 14:06:20 -05:00
dependabot[bot]
bd948bcbba chore(deps): bump actions/setup-node from 3.5.1 to 3.6.0 (#11896)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3.5.1 to 3.6.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](8c91899e58...64ed1c7eab)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-10 15:25:17 -05:00
dependabot[bot]
3c7afadab3 chore(deps): bump actions/upload-artifact from 3.1.1 to 3.1.2 (#11929)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3.1.1 to 3.1.2.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](83fd05a356...0b7f8abb15)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-10 15:21:53 -05:00
argo-bot
9db2c9471f Bump version to 2.5.6 2023-01-10 19:17:05 +00:00
argo-bot
4161360cca Bump version to 2.5.6 2023-01-10 19:17:00 +00:00
Ryan Umstead
7b7fa87a53 fix(redis): explicit bind to redis and sentinel for IPv4 clusters (#11388) (#11862)
* fix(redis): explicit bind to redis and sentinel for IPv4 clusters #11388

Signed-off-by: rumstead <rjumstead@gmail.com>

* fix(redis): run manifests generate

Signed-off-by: rumstead <rjumstead@gmail.com>

* fix(redis): run manifests generate

Signed-off-by: rumstead <rjumstead@gmail.com>

* Retrigger CI pipeline

Signed-off-by: rumstead <rjumstead@gmail.com>

Signed-off-by: rumstead <rjumstead@gmail.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-01-10 14:02:23 -05:00
Michael Crenshaw
170f62ca89 chore: upgrade redis to 7.0.7 to avoid CVE-2022-3996 (#11925)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-01-10 10:36:44 -05:00
Michael Crenshaw
c273f66392 fix: upgrade qs to avoid CVE-2022-24999
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-01-10 09:39:41 -05:00
dependabot[bot]
95e7ca284d chore(deps): bump actions/cache from 3.2.0 to 3.2.2 (#11839)
Bumps [actions/cache](https://github.com/actions/cache) from 3.2.0 to 3.2.2.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](c17f4bf466...4723a57e26)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-09 17:12:01 -05:00
dependabot[bot]
fe3f617afc chore(deps): bump actions/download-artifact from 3.0.1 to 3.0.2 (#11894)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](9782bd6a98...9bc31d5ccc)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-09 16:52:20 -05:00
Michael Crenshaw
2d68476867 docs: note risks of secret-injection plugins (#11617)
* docs: note risks of secret-injection plugins

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

* grammar tweaks

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

* grammar tweaks

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

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-01-09 16:29:39 -05:00
Leonardo Luz Almeida
4ebdc0ea49 fix: web terminal namespace handler (#11891)
Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>
2023-01-09 16:28:19 -05:00
dependabot[bot]
174ce2f65c chore(deps): bump actions/cache from 3.0.11 to 3.2.0 (#11809)
Bumps [actions/cache](https://github.com/actions/cache) from 3.0.11 to 3.2.0.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](9b0c1fce7a...c17f4bf466)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-22 13:38:53 -05:00
Michael Crenshaw
3de96ca51c fix: web terminal outside argocd namespace (#11166) (#11400)
* fix: web terminal outside argocd namespace (#11166)

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

* reorganize

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

* fix reference

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

* move things around, fix stuff maybe

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

* tests

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

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-12-22 13:29:33 -05:00
Justin Marquis
0e506a936d chore: fix lint error (#11788)
Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
2022-12-21 08:14:07 -05:00
Justin Marquis
d276f739f2 chore: get image digest in seperate step (#11778)
* chore: get image digest in seperate step

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

* Retrigger CI pipeline

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
2022-12-20 17:22:29 -05:00
Gaël Jourdan-Weil
7d635329c3 docs: clarify project destination possibilities (#11706)
Clarify that it's possible to reference clusters by `cluster` or by `name`.

Signed-off-by: Gaël Jourdan-Weil <gjourdanweil@gmail.com>

Signed-off-by: Gaël Jourdan-Weil <gjourdanweil@gmail.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-12-20 12:52:00 -05:00
Matt Clegg
c105f31fd7 docs: correct SSO configuration URL in example configmap (#11720)
Signed-off-by: Matt Clegg <m@cle.gg>

Signed-off-by: Matt Clegg <m@cle.gg>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-12-20 08:58:05 -05:00
Phil Wright- Christie
0fce9519e7 docs: Update example dockerfile (#11721)
The latest tag hasn't been updated in almost a year, and as a result, the ubuntu repositories are out of date and are throwing errors. This updates the example to use a fixed version, which are updated much more frequently.

Signed-off-by: Phil Wright- Christie <philwc@gmail.com>

Signed-off-by: Phil Wright- Christie <philwc@gmail.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-12-20 08:55:20 -05:00
Leonardo Luz Almeida
6db9bc31b4 fix: ssa e2e tests failing after updating to kubectl 1.26 (#11753)
* fix: ssa e2e test failing after updating to kubectl 1.26

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

* Remove pinned kubectl version

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

* Cleaner approach to fix e2e test

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

* Fix

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

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>
2022-12-20 08:49:23 -05:00
Nicholas Morey
03b52391bd docs: clarify that all labels must exist (#11693)
It's unclear if all or any of the labels need to exist. This clarifies that all of the labels must exist.

Signed-off-by: Nicholas Morey <nicholas@morey.tech>

Signed-off-by: Nicholas Morey <nicholas@morey.tech>
2022-12-20 08:46:25 -05:00
dependabot[bot]
094ae26c7e chore(deps): bump actions/setup-go from 3.4.0 to 3.5.0 (#11697)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3.4.0 to 3.5.0.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](d0a58c1c4d...6edd4406fa)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-20 08:41:10 -05:00
yanyx
eb54e2293e doc: correct kustomize demo path (#11762)
Signed-off-by: Yixing Yan <yixingyan@gmail.com>

Signed-off-by: Yixing Yan <yixingyan@gmail.com>
2022-12-20 08:38:07 -05:00
Justin Marquis
acdf544694 fix: sign container images by digest (#11151)
* chore: sign container images by digest

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

* use sha hash

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
2022-12-19 20:45:11 -05:00
Justin Marquis
1b81f4c2ef docs: update cosign docs (#11749)
Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
2022-12-19 11:18:44 -05:00
jannfis
8a7b7b51e2 docs: Document applications in any namespace (#10678)
* docs: Document applications in any namespace

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

* Fix some code blocks

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

* Fix link

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

* docs: Document applications in any namespace

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

* Fix some code blocks

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

* Fix link

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

* Apply reviewer comments

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

Signed-off-by: jannfis <jann@mistrust.net>
2022-12-16 14:42:41 -05:00
argo-bot
fc3eaec6f4 Bump version to 2.5.5 2022-12-16 15:56:33 +00:00
argo-bot
09e026d43f Bump version to 2.5.5 2022-12-16 15:56:29 +00:00
jannfis
c0271bdccf fix: Unbreak termination of operation with apps in other namespaces (#11239) (#11724)
* fix: Unbreak operation termination

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

* Revert change to Dockerfile

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

Signed-off-by: jannfis <jann@mistrust.net>
2022-12-16 09:26:32 -05:00
Justin Marquis
9b27f353a9 chore: upgrade helm to most recent version (v3.10.3) (#11725)
* chore: upgrade helm to most recent version (v3.10.3)

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

* Retrigger CI pipeline

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-12-16 09:23:02 -05:00
Leonardo Luz Almeida
aaeef297a9 docs: Clarification of the create namespace feature (#11723)
* docs: Clarification of the create namespace feature

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

* Address review suggestion

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

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>
2022-12-16 08:33:04 -05:00
Michael Crenshaw
5fc009edbd fix: pin kubectl version (#11726)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-12-16 02:13:12 +01:00
Michael Crenshaw
12d0fa4c39 chore: fix flaky e2e test for immutable fields (#11685)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-12-13 15:24:11 -05:00
schakrad
cff4614c69 fix: add pod age icon details in tooltip (#10290) (#11170)
* pod-age-icon details added in tooltip

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

* Tooltip change

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

Signed-off-by: schakradari <58915923+schakrad@users.noreply.github.com>
2022-12-13 09:46:14 -05:00
asingh
711e544581 fix: appname in searchbar (#11493)
* fix: appname in searchbar

Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>

* fix: appname in searchbar

Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>

Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>
2022-12-13 09:35:34 -05:00
dependabot[bot]
c7441251a7 chore(deps): bump actions/checkout from 3.1.0 to 3.2.0 (#11679)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.1.0 to 3.2.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](93ea575cb5...755da8c3cf)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-13 08:25:08 -05:00
Michael Crenshaw
a79e43bb1b chore: fix flaky e2e test (#11670)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-12-12 17:36:32 -05:00
Michael Crenshaw
997223d0a5 chore: fix flaky e2e test (#11509) (#11654)
* chore: fix flaky e2e test (#11509)

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

* don't centralize mock response - tests should be independent

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

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-12-12 12:14:38 -05:00
Nicholas Morey
8b070b5a31 docs: kustomize has access to build environment (#11643)
Current docs reflect that the build environment is not available to kustomize. Since https://github.com/argoproj/argo-cd/pull/8096 it is now exposed for kustomize. This updates the kustomize section of the docs to reflect that.

Signed-off-by: Nicholas Morey <nicholas@morey.tech>

Signed-off-by: Nicholas Morey <nicholas@morey.tech>
2022-12-11 15:58:42 -05:00
asingh
10acf7b09c docs: add warning for user when using replace sync option (#11566)
* docs: adding warning to 'replace' sync option

Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>

* Update sync-options.md

Signed-off-by: asingh <11219262+ashutosh16@users.noreply.github.com>

Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>
Signed-off-by: asingh <11219262+ashutosh16@users.noreply.github.com>
2022-12-09 14:22:09 -05:00
Alex Eftimie
1c7d767977 fix(helm): login OCI Helm dependencies correctly (#8563) (#11327)
Signed-off-by: Alex Eftimie <alex.eftimie@getyourguide.com>

Signed-off-by: Alex Eftimie <alex.eftimie@getyourguide.com>
2022-12-07 12:44:41 -05:00
Alex Eftimie
c867f0675e fix(helm): helm v3 doesn't have these flags (#11100) (#11540)
* fix: helm v3 doesn't have these flags

Signed-off-by: Alex Eftimie <alex.eftimie@getyourguide.com>

* Revert repoAdd change. Was to greedy, ca-file is needed there

Signed-off-by: Alex Eftimie <alex.eftimie@getyourguide.com>

Signed-off-by: Alex Eftimie <alex.eftimie@getyourguide.com>
2022-12-07 12:43:09 -05:00
argo-bot
86b2dde8e4 Bump version to 2.5.4 2022-12-06 19:33:24 +00:00
argo-bot
d3d228f19d Bump version to 2.5.4 2022-12-06 19:33:18 +00:00
Michael Vittrup Larsen
e8f37d79aa docs: Add skipCrds and ignoreMissingValueFiles to application.yaml example (#11565)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-12-06 09:51:05 -05:00
asingh
93d3fe60ae fix: sidebar css (#11531)
Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>
Co-authored-by: Remington Breeze <remington@breeze.software>
2022-12-05 20:16:49 -05:00
Nathanael Liechti
fba15018bd fix: use repository GithubAppCreds proxy if set (#11422)
Signed-off-by: Nathanael Liechti <technat@technat.ch>

Signed-off-by: Nathanael Liechti <technat@technat.ch>
2022-12-05 12:27:06 -05:00
Dieter Bocklandt
57562de259 docs: update how to access arrays in Go templates (#11562)
Signed-off-by: Dieter Bocklandt <dieterbocklandt@gmail.com>

Signed-off-by: Dieter Bocklandt <dieterbocklandt@gmail.com>
2022-12-05 08:52:24 -05:00
dependabot[bot]
3b8b4c16dd chore(deps): bump decode-uri-component from 0.2.0 to 0.2.2 in /ui (#11533)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-03 21:58:12 -05:00
Philip Haberkern
ff20becb13 docs: Added ARGOCD_ENV_ prefix to FOO (#11545)
Signed-off-by: Philip Haberkern <59010269+thedatabaseme@users.noreply.github.com>

Signed-off-by: Philip Haberkern <59010269+thedatabaseme@users.noreply.github.com>
2022-12-03 21:55:13 -05:00
Artur Rodrigues
67a13bbe6e chore: add debug logs around CMP manifest generation (#11185)
* docs: note one single CMP per app

Signed-off-by: Artur Rodrigues <artur.rodrigues@lacework.net>

* cmp: debug logs around manifest handling

Signed-off-by: Artur Rodrigues <artur.rodrigues@lacework.net>

Signed-off-by: Artur Rodrigues <artur.rodrigues@lacework.net>
2022-12-03 15:43:52 -05:00
Alex Eftimie
140fbccd09 fix(ui): fix sorting of parameters. Make the Remove override button clickable again (#11316)
Signed-off-by: Alex Eftimie <alex.eftimie@getyourguide.com>
Co-authored-by: Remington Breeze <remington@breeze.software>
2022-12-02 09:56:40 -05:00
dependabot[bot]
b9cbfadf15 chore(deps): bump actions/setup-go from 3.3.1 to 3.4.0 (#11535)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3.3.1 to 3.4.0.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](c4a742cab1...d0a58c1c4d)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-02 08:54:36 -05:00
dependabot[bot]
7ac6a6b500 chore(deps): bump softprops/action-gh-release from 0.1.14 to 0.1.15 (#11534)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 0.1.14 to 0.1.15.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](1e07f43987...de2c0eb89a)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-02 08:53:33 -05:00
Duncan
afe1eaad6a docs: Add CSI secret driver to the secret management options (#10900)
Signed-off-by: Duncan <62943186+duncan485@users.noreply.github.com>

Signed-off-by: Duncan <62943186+duncan485@users.noreply.github.com>
2022-12-02 08:51:22 -05:00
Antoine Pultier
bb21fcb3db docs: Improve Keycloak documentation for command line sign-in (#8758)
Documenting what is discussed in #2932

Signed-off-by: Antoine Pultier <antoine.pultier@sintef.no>

Signed-off-by: Antoine Pultier <antoine.pultier@sintef.no>
Co-authored-by: pasha-codefresh <pavel@codefresh.io>
2022-12-02 08:47:25 -05:00
Cedar
73be2c9912 docs: Update operator manual installation helm available url (#11120)
Signed-off-by: cedarkuo <cedarkuo@gmail.com>

Signed-off-by: cedarkuo <cedarkuo@gmail.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-12-02 08:45:59 -05:00
Gerald Spencer
158e2883f6 docs: Update Pull Request generator documentation to include application lifecycle (#11274)
* Update Pull Request generator documentation

The lifecycle of the generated applications was not explained

Signed-off-by: Gerald Spencer <Geethree@users.noreply.github.com>

* Update docs/operator-manual/applicationset/Generators-Pull-Request.md

Co-authored-by: Alex Eftimie <alex.eftimie@getyourguide.com>
Signed-off-by: Gerald Spencer <Geethree@users.noreply.github.com>

Signed-off-by: Gerald Spencer <Geethree@users.noreply.github.com>
Co-authored-by: Alex Eftimie <alex.eftimie@getyourguide.com>
2022-12-02 08:44:56 -05:00
Michael Merrill
0137050ef5 fix: add missing changes for bitbucket cloud SCM provider (#10143) (#11150)
Signed-off-by: mmerrill3 <jjpaacks@gmail.com>

Signed-off-by: mmerrill3 <jjpaacks@gmail.com>
2022-11-30 21:02:58 -05:00
Alexander Matyushentsev
21ea86a827 fix: expose missing ReactDOM to enable extensions implementation (#11495)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2022-11-30 11:13:24 -08:00
Michael Crenshaw
524032ee39 chore: pin actions (#11360)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-11-29 13:03:00 -05:00
Michael Crenshaw
5bb97700c4 chore: use set-output environment file (#10999)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-11-29 09:48:24 -05:00
Adrian Moisey
0a170ca457 docs: fix markdown formatting (#11460)
The exiting table is a bit off

Signed-off-by: Adrian Moisey <adrian@changeover.za.net>

Signed-off-by: Adrian Moisey <adrian@changeover.za.net>
2022-11-29 09:19:31 -05:00
asingh
1523d89df8 fix: sonarlint issue (#11472)
Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>

Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>
2022-11-29 08:30:36 -05:00
argo-bot
0c7de210ae Bump version to 2.5.3 2022-11-28 16:39:10 +00:00
argo-bot
2c7d99b9ae Bump version to 2.5.3 2022-11-28 16:39:04 +00:00
asingh
4b53a60b11 fix: hide app namespace on the ui (#11111) (#11247)
* fix: hide app namespace when irrelevant (#11111)

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

* wire up setting

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

* fix: hide app namespace

Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>

* fix: hide app namespace

Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>

* add null check

Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>

* Update ui/src/app/applications/components/utils.tsx

Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
Signed-off-by: asingh <11219262+ashutosh16@users.noreply.github.com>

* lint

Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>

* fix name generation

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

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>
Signed-off-by: asingh <11219262+ashutosh16@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
2022-11-28 11:08:38 -05:00
Saumeya Katyal
114a4bf140 fix: ui banner covering sidebar (#11101)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-11-28 10:47:56 -05:00
Nicholas Morey
b20dbf5cf5 docs: improve build env variable list formatting (#11429)
* docs: normalize build env list

Signed-off-by: Nicholas Morey <nicholas@morey.tech>

* docs: use table instead of list

Signed-off-by: Nicholas Morey <nicholas@morey.tech>

* docs: remove separator from description

Signed-off-by: Nicholas Morey <nicholas@morey.tech>

Signed-off-by: Nicholas Morey <nicholas@morey.tech>
2022-11-25 15:31:23 -05:00
Nick Mohoric
02bba2397b fix: Add support for /api/v1/applicationsets* via HTTP (#11409)
Signed-off-by: Nick Mohoric <nmohoric@hearst.com>

Signed-off-by: Nick Mohoric <nmohoric@hearst.com>
2022-11-25 15:14:47 -05:00
dependabot[bot]
cb21483053 chore(deps): bump actions/upload-artifact from 2 to 3 (#11365)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2 to 3.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v2...v3)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-18 22:07:53 -05:00
dependabot[bot]
279a58b05b chore(deps): bump actions/setup-node from 1 to 3 (#11364)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 1 to 3.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v1...v3)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-18 22:06:25 -05:00
dependabot[bot]
e01509a31f chore(deps): bump actions/cache from 1 to 3 (#11363)
Bumps [actions/cache](https://github.com/actions/cache) from 1 to 3.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v1...v3)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-18 22:04:21 -05:00
dependabot[bot]
b40b62f1b2 chore(deps): bump codecov/codecov-action from 1 to 3 (#11362)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 1 to 3.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v1...v3)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-18 22:02:42 -05:00
dependabot[bot]
ad49186498 chore(deps): bump actions/download-artifact from 2 to 3 (#11361)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 2 to 3.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v2...v3)

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-18 21:06:22 -05:00
Alex Eftimie
f000a73a3a docs: fix references to cli tools moved under argocd admin (#11181)
* docs: fix references to cli tools moved under argocd admin

Signed-off-by: Alex Eftimie <alex.eftimie@getyourguide.com>

* attempt to fix the build by fiddling the generated docs

Signed-off-by: Alex Eftimie <alex.eftimie@getyourguide.com>

* Update hack/gen-catalog/main.go

Signed-off-by: Alex Eftimie <alex.eftimie@getyourguide.com>

* docs: fix doc generator for argocd admin notifications

Signed-off-by: Alex Eftimie <alex.eftimie@getyourguide.com>

* docs: fix doc generator diff

Signed-off-by: Alex Eftimie <alex.eftimie@getyourguide.com>

Signed-off-by: Alex Eftimie <alex.eftimie@getyourguide.com>
2022-11-18 21:03:19 -05:00
Alexander Matyushentsev
2a6f07aea2 fix: application stuck in infinite reconciliation loop if using wrong project (#11246)
* fix: application stuck in infinite reconciliation loop if using wrong project

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

* add missing unit test

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

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2022-11-18 20:57:59 -05:00
F1ko
16042390d7 fix: remove 0.0.0.0/0 ipblock from network policies (#11321) (#11322)
* fix: remove 0.0.0.0/0 ipblock from network policies

https://github.com/argoproj/argo-cd/issues/11321
Signed-off-by: Filip Nikolic <oss.filipn@gmail.com>

* chore: add postfinance to the list of users

Signed-off-by: Filip Nikolic <oss.filipn@gmail.com>

Signed-off-by: Filip Nikolic <oss.filipn@gmail.com>
2022-11-18 20:56:35 -05:00
Patrice Chalin
70a9f9047e docs: Enable Google Analytics 4 for 2.5 (stable) (#11323)
* chore(docs): fix build, prepare for google analytics v4

Signed-off-by: Alex Eftimie <alex.eftimie@getyourguide.com>
Signed-off-by: Patrice Chalin <chalin@cncf.io>

* docs: Use new Google Analytics 4 ID

Signed-off-by: Patrice Chalin <chalin@cncf.io>

Signed-off-by: Alex Eftimie <alex.eftimie@getyourguide.com>
Signed-off-by: Patrice Chalin <chalin@cncf.io>
2022-11-18 20:55:27 -05:00
Alex Eftimie
0366e0153d fix: set HELM_CONFIG_HOME dir for oci registry authentication; fixes: #11284 (#11285)
Signed-off-by: Alex Eftimie <alex.eftimie@getyourguide.com>
2022-11-17 14:24:00 -08:00
Michael Crenshaw
0a34eb18e8 chore: use --password-stdin for docker login (#11331)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-11-17 10:46:55 -05:00
Edmund Rhudy
787dccd85a fix: UI can now get clusters with slashes in name (#9812) (#9813)
* fix: #9812 UI can now get clusters with slashes in name

Fixes #9812

If a cluster name has a slash in it, the API would not be able
to fetch that cluster and would display "in-cluster (undefined)"
for that application. This fixes that issue by URI-encoding
the cluster name on the UI side and URI-decoding the cluster name
on the API side.

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

* Retrigger CI pipeline

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

Signed-off-by: Edmund Rhudy <erhudy@users.noreply.github.com>
2022-11-17 09:56:03 -05:00
Shuai Zhang
16b2fd3cc9 fix: allow resolving repo root as jsonnet lib path (#11119)
Signed-off-by: shuai-zh <shuaiz8023@gmail.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-11-15 18:07:18 -05:00
Jesse Suen
b72f6df58a fix: retry token creation/deletion upon project conflict errors (#11199)
Signed-off-by: Jesse Suen <jesse@akuity.io>
2022-11-09 16:13:23 -08:00
Jesse Suen
91fcd86bf1 fix: Retry IsConflict for settings update. Map kube API errors to retryable HTTP status codes (#10817)
Signed-off-by: Jesse Suen <jesse@akuity.io>
2022-11-09 16:13:07 -08:00
ChangZhuo Chen (陳昌倬)
2c1a8a9a22 docs: add example for config management plugins exclusion (#11187)
Signed-off-by: ChangZhuo Chen (陳昌倬) <czchen@czchen.org>

Signed-off-by: ChangZhuo Chen (陳昌倬) <czchen@czchen.org>
2022-11-09 08:30:05 -05:00
Justin Marquis
64b29fee1b fix: use non distroless image for dex (#11219)
* fix: use non distroless image for dex

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

* change image in ci workflow

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
2022-11-09 08:22:22 -05:00
argo-bot
148d8da7a9 Bump version to 2.5.2 2022-11-07 16:29:42 +00:00
argo-bot
f13bb9e2e9 Bump version to 2.5.2 2022-11-07 16:29:35 +00:00
Chris Lewis
1e6a4c6128 docs: Add Contributor's Quickstart Page (#11108)
* First Draft

* Update docs/contributors_quickstart.md

Co-authored-by: ChanJong Na <cjna@umich.edu>
Signed-off-by: ctlewis <lewisengineer@gmail.com>

* Update docs/contributors_quickstart.md

Co-authored-by: Dan Garfield <dan@codefresh.io>
Signed-off-by: ctlewis <lewisengineer@gmail.com>

* Update docs/contributors_quickstart.md

Co-authored-by: Moshe Shitrit <moshe@s5t.dev>
Signed-off-by: ctlewis <lewisengineer@gmail.com>

* Update docs/contributors_quickstart.md

Co-authored-by: Jason Poley <jason.poley@gmail.com>
Signed-off-by: ctlewis <lewisengineer@gmail.com>

* Update docs/contributors_quickstart.md

Co-authored-by: Moshe Shitrit <moshe@s5t.dev>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* Update docs/contributors_quickstart.md

Co-authored-by: Garima Negi <garima.negy@gmail.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* tweaks

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

* undo temporary change

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

* Add sign off

Signed-off-by: Chris Lewis <clewis@powercosts.com>

* Update docs/developer-guide/contributors-quickstart.md

Co-authored-by: Angela Wilson <84730053+awilson-payit@users.noreply.github.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* Update docs/developer-guide/contributors-quickstart.md

Co-authored-by: Andre Marcelo-Tanner <andre@enthropia.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

Signed-off-by: ctlewis <lewisengineer@gmail.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: Chris Lewis <clewis@powercosts.com>
Co-authored-by: Chris Lewis <clewis@powercosts.com>
Co-authored-by: ChanJong Na <cjna@umich.edu>
Co-authored-by: Dan Garfield <dan@codefresh.io>
Co-authored-by: Moshe Shitrit <moshe@s5t.dev>
Co-authored-by: Jason Poley <jason.poley@gmail.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Garima Negi <garima.negy@gmail.com>
Co-authored-by: Angela Wilson <84730053+awilson-payit@users.noreply.github.com>
Co-authored-by: Andre Marcelo-Tanner <andre@enthropia.com>
2022-11-04 20:22:09 -04:00
Michael Crenshaw
2873aa43f4 docs: debugging CMPs (#11142)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-11-04 10:46:33 -04:00
Michael Crenshaw
e7b4256474 docs: add Dockerfile example for plugin (#11130)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-11-04 09:04:34 -04:00
Michael Crenshaw
b0b8353e26 docs: document metadata access for go-templated cluster generator (#10929)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-11-04 09:03:59 -04:00
balajisa
2848ca2607 docs(user-guide): Add doc for import argocd packages (#11041) (#11096)
* Add doc for argocd pkg import

Signed-off-by: balajisa09 <balajisa09@gmail.com>

* Update docs/user-guide/import.md

Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: balajisa <50614674+balajisa09@users.noreply.github.com>

* Add detailed solution

Signed-off-by: balajisa09 <balajisa09@gmail.com>

* Update suggestions

Signed-off-by: balajisa09 <balajisa09@gmail.com>

* Update docs/user-guide/import.md

Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: balajisa <50614674+balajisa09@users.noreply.github.com>

* Update docs/user-guide/import.md

Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: balajisa <50614674+balajisa09@users.noreply.github.com>

* Update docs/user-guide/import.md

Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: balajisa <50614674+balajisa09@users.noreply.github.com>

* Update docs/user-guide/import.md

Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: balajisa <50614674+balajisa09@users.noreply.github.com>

* Update docs/user-guide/import.md

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

* fix code block

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

Signed-off-by: balajisa09 <balajisa09@gmail.com>
Signed-off-by: balajisa <50614674+balajisa09@users.noreply.github.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: balajisa09 <balajisa09@gmail.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-11-04 08:43:54 -04:00
Michael Crenshaw
9b7445cb18 fix: templating keys in ApplicationSet (#11076) (#11163)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-11-03 19:51:02 -04:00
Leonardo Luz Almeida
9b2cdc2ccf fix: handle apiGroup updates in resource-tracking (#11012)
* fix: handle apiGroup updates in resource-tracking

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

* Fix test

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

* change the fix approach by inspecting tracking id from the config

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

* add unit-test to validate the scenario

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

* fix test lint

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

* review fixes

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

* Reword godocs for clarity

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

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>
2022-11-03 15:02:33 -04:00
Justin Marquis
301b80b512 fix: upgrade redis-ha chart to 4.22.3, redis regression (#11176)
* chore: upgrade redis-ha chart to 4.22.3, redis regression

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

* fix manifest

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

* fix missing cidr

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

* fix typo

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

* fix typo

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
2022-11-03 15:01:18 -04:00
argo-bot
504da424c2 Bump version to 2.5.1 2022-11-01 21:01:33 +00:00
argo-bot
24cc8578fd Bump version to 2.5.1 2022-11-01 21:01:24 +00:00
argo-bot
b895da4577 Bump version to 2.5.0 2022-10-25 14:23:02 +00:00
argo-bot
5a62dbd336 Bump version to 2.5.0 2022-10-25 14:22:54 +00:00
Michael Crenshaw
c7a0978271 chore: don't generate release notes
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-10-25 10:22:08 -04:00
Chromo-residuum-opec
872e1f2f5a docs: fix 'bellow' typos (#11038)
Signed-off-by: backfire-monism-net <development.0extl@simplelogin.com>

Signed-off-by: backfire-monism-net <development.0extl@simplelogin.com>
2022-10-22 20:11:53 -04:00
Michael Crenshaw
fe565dc2d9 chore: fix CI (#11022)
* chore: fix CI

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

* no more set global

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

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-10-21 11:21:13 -04:00
Michael Crenshaw
1b65c3f330 chore: fix e2e (#11005)
* chore: fix e2e

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

* more config

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

* global

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

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-10-21 11:20:32 -04:00
Alex Eftimie
fdd2e982c5 docs: mention that OCI helm does not support version ranges (#11026)
* docs: mention that OCI helm does not support version ranges

Signed-off-by: Alex Eftimie <alex.eftimie@getyourguide.com>

* Apply suggestions from code review

Signed-off-by: Alex Eftimie <alex.eftimie@getyourguide.com>

Signed-off-by: Alex Eftimie <alex.eftimie@getyourguide.com>
2022-10-21 11:18:35 -04:00
Allex
49235d5525 fix: Update custom health check for kiali.io/Kiali (#10995)
With Kiali v1.57.1 an additional status condition was added:
```
    - lastTransitionTime: '2022-10-14T11:56:24Z'
      message: ''
      reason: ''
      status: 'False'
      type: Failure
```

Based on the discussion in https://github.com/kiali/kiali/issues/5560 this should not lead to a degraded health state.

This will no longer return Degraded as a catch-all and use the `type` and `status` fields of the condition to determine the CR health.

Signed-off-by: Allex Veldman <allexveldman+github@gmail.com>

Signed-off-by: Allex Veldman <allexveldman+github@gmail.com>
2022-10-19 12:17:24 -04:00
Michael Crenshaw
0cf224c958 chore: upgrade actions/checkout to v3, i.e. Node.js 16 (#10947)
* chore: updgrade actions/checkout to v3, i.e. Node.js 16

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

* more node 12

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

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-10-19 10:32:33 -04:00
34FathomBelow
664224fe14 chore: release signature of sbom (#10969)
Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
2022-10-18 15:39:41 -04:00
Jessie Teng
351cab4c2a fix: Resource list in sync page msg style#10887 (#10970)
* fix: Resource list in sync page msg style#10887

Signed-off-by: Teng, Jessie <yilin.teng@fmr.com>

* fix: Resource list in sync page msg style#10887

Signed-off-by: Teng, Jessie <yilin.teng@fmr.com>

* fix: Resource list in sync page msg style#10887

Signed-off-by: Teng, Jessie <yilin.teng@fmr.com>

* fix: Resource list in sync page msg style#10887

Signed-off-by: Teng, Jessie <yilin.teng@fmr.com>

Signed-off-by: Teng, Jessie <yilin.teng@fmr.com>
2022-10-18 14:01:44 -04:00
argo-bot
c615c8a56d Bump version to 2.5.0-rc3 2022-10-17 17:34:56 +00:00
argo-bot
048275cb93 Bump version to 2.5.0-rc3 2022-10-17 17:34:48 +00:00
34FathomBelow
df8f70aac8 chore: sign checksums file for release binaries (#10963)
Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
2022-10-17 13:03:50 -04:00
Michael Crenshaw
40ce041aa7 fix: upgrade Helm to avoid disk use issue (#8773) (#10937)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-10-17 12:44:36 -04:00
34FathomBelow
68df7d8dda chore: implement signed images (#10925)
* consolidate checksums into one file

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

* sign container images

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

* sign container images

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

* remove id-token permissions

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
2022-10-17 12:37:44 -04:00
Michael Crenshaw
d9004fc748 chore: upgrade dex to v2.35.3 to avoid CVE-2022-27665 (#10939)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-10-17 12:30:27 -04:00
34FathomBelow
84680ba0ff docs: release signature verification (#10967)
* chore: release signature documentation

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

* fixed typos

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

* fixed requested changes

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>

Signed-off-by: Justin Marquis <34fathombelow@protonmail.com>
2022-10-17 11:49:00 -04:00
Aiman Fatima
9f5111d474 fix: Display pointer on labels for resource names in sync panel (#10959)
Signed-off-by: Aiman Fatima <aimanfatimadl@gmail.com>

Signed-off-by: Aiman Fatima <aimanfatimadl@gmail.com>
2022-10-17 09:30:24 -04:00
Chris Davis
cd3ce58df4 fix: Use os.PathSeparator instead of hard-coded string to resolve local file paths (#10945) (#10946)
fix: Use os.PathSeparator instead of hard-coded string to resolve local file paths (#10945) (#10946)
2022-10-14 13:53:54 -07:00
Mayursinh Sarvaiya
39f9565e34 feat(ui): notification subscriptions edit field #10310 (#10839)
* feat(ui): notification subscriptions edit field

> this new field is just an abstraction of relevant annotations

Signed-off-by: Mayursinh Sarvaiya <marvinduff97@gmail.com>

* fix: codeql regex issue

Signed-off-by: Mayursinh Sarvaiya <marvinduff97@gmail.com>

Signed-off-by: Mayursinh Sarvaiya <marvinduff97@gmail.com>
2022-10-14 13:07:33 -07:00
Alexander Matyushentsev
2d9f13d0bb fix: Resource list loading slowly due to Sync Wave sorting (#10932)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2022-10-13 17:49:15 -07:00
Michael Crenshaw
b735c00761 docs: appset PR generator docs fixes (#10567)
* docs: appset PR generator docs fixes

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* oh, that field is actually a thing

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-10-12 11:30:41 -04:00
Michael Crenshaw
aae9a24cbd docs: add link to 2.4-2.5 upgrade guide (#10808)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-10-12 11:29:41 -04:00
Michael Crenshaw
dde489db1c docs: more docs for directory apps (#10879)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-10-12 11:19:28 -04:00
Michael Crenshaw
d0b20b06dc docs: clarify how default RBAC policy works (#10896)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-10-12 11:17:00 -04:00
karengineering
2ccb96086f fix: add applicationsets to RBAC policy (#10810) (#10891)
Signed-off-by: Karengineering <49111213+karengineering@users.noreply.github.com>

Signed-off-by: Karengineering <49111213+karengineering@users.noreply.github.com>
2022-10-11 17:01:13 -04:00
argo-bot
ba4c562508 Bump version to 2.5.0-rc2 2022-10-11 19:01:45 +00:00
argo-bot
701b3403c5 Bump version to 2.5.0-rc2 2022-10-11 19:01:38 +00:00
Michael Crenshaw
f927aaeddd chore: add script to generate release notes (#10806)
* chore: add script to generate release notes

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

* newlines look bad in the release markdown rendering on GitHub

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

* use diff instead of comp

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

* use auto-generated docs

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

* pre-pended, not appended

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

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-10-11 14:54:49 -04:00
Mayursinh Sarvaiya
e0ffec8a63 fix(ui): sync option label doesn't check corresponding box (#10863) (#10876)
* fix(ui): sync option label doesn't check corresponding box

Signed-off-by: Mayursinh Sarvaiya <marvinduff97@gmail.com>

* fix: lint

Signed-off-by: Mayursinh Sarvaiya <marvinduff97@gmail.com>

Signed-off-by: Mayursinh Sarvaiya <marvinduff97@gmail.com>
2022-10-10 14:52:29 -04:00
Nir Shtein
a04d634fba fix: clicking HEAD in bitbucket leads to a 404 page (#10862)
* Wrap error objects to include context

Signed-off-by: Nir Shtein <89006520+nirsht@users.noreply.github.com>

* fix: duplicate source namespace validation (#10853)

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

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

* Fix CR

Signed-off-by: Nir Shtein <89006520+nirsht@users.noreply.github.com>

* Change 'branch' to 'src' when building url path

Signed-off-by: Nir Shtein <89006520+nirsht@users.noreply.github.com>

* Revert "Fix CR"

This reverts commit 4b92408412.

Signed-off-by: Nir Shtein <89006520+nirsht@users.noreply.github.com>

* Revert "Wrap error objects to include context"

This reverts commit d1789bd271.

Signed-off-by: Nir Shtein <89006520+nirsht@users.noreply.github.com>

Signed-off-by: Nir Shtein <89006520+nirsht@users.noreply.github.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-10-09 12:11:03 -04:00
Sakshi Jain
0b076c13a0 fix: added css to change cursor to pointer on hover (#10864) (#10867)
* added css to change cursor to pointer on hover

Signed-off-by: Sakshi <sakshi.jain7597@gmail.com>

* moved cursor change to only label and input

Signed-off-by: Sakshi <sakshi.jain7597@gmail.com>

Signed-off-by: Sakshi <sakshi.jain7597@gmail.com>
2022-10-09 12:05:23 -04:00
Michael Crenshaw
d33981ffa2 docs: more versioned docs fixes (#10342)
* docs: remove more version notes - rely on docs versioning

Signed-off-by: CI <michael@crenshaw.dev>

* missed some things

Signed-off-by: CI <michael@crenshaw.dev>

Signed-off-by: CI <michael@crenshaw.dev>
2022-10-08 15:09:07 -04:00
toyamagu
2b62aa7d92 docs: fix examples for ArgoCD ApplicationSet Git Generator (#10857)
* Doc: ArgoCD ApplicationSet Git directory

Signed-off-by: toyamagu <toyamagu2021@gmail.com>

* Docs: use "my-project" rather than default project

Signed-off-by: toyamagu <toyamagu2021@gmail.com>

Signed-off-by: toyamagu <toyamagu2021@gmail.com>
2022-10-08 14:44:55 -04:00
Michael Crenshaw
dfbcf757b9 fix: duplicate source namespace validation (#10853)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-10-08 14:31:47 -04:00
Michael Crenshaw
d5feafc654 docs: remove unused plugin config fields (#10304)
* docs: remove unused plugin config fields

Signed-off-by: CI <michael@crenshaw.dev>

* fix codegen

Signed-off-by: CI <michael@crenshaw.dev>

Signed-off-by: CI <michael@crenshaw.dev>
2022-10-07 19:44:15 -04:00
Tsubasa Nagasawa
f47a5f90c2 fix: applicationset controller should respect logging flags (#10513)
* Align logging setup with other controllers

Signed-off-by: toVersus <toversus2357@gmail.com>

Signed-off-by: toVersus <toversus2357@gmail.com>
2022-10-07 17:34:35 -04:00
Thijs van Tol
faa01bb6f9 fix: show revision in badge when param is true (#10545)
* fix: show revision in badge when param is true

Signed-off-by: Thijs van Tol <43065692+thijsvtol@users.noreply.github.com>

* Update badge.go

Signed-off-by: Thijs van Tol <43065692+thijsvtol@users.noreply.github.com>

* Update badge.go

Signed-off-by: Thijs van Tol <43065692+thijsvtol@users.noreply.github.com>

* pr feedback

Signed-off-by: Thijs van Tol <43065692+thijsvtol@users.noreply.github.com>

Signed-off-by: Thijs van Tol <43065692+thijsvtol@users.noreply.github.com>
2022-10-07 17:13:38 -04:00
Matt Morrison
1e1a744604 fix: consider destination cluster name when validating destinations (#10594)
Signed-off-by: Matt Morrison <matt.morrison@zapier.com>

Signed-off-by: Matt Morrison <matt.morrison@zapier.com>
2022-10-07 16:37:37 -04:00
Minchao
aca9ed2030 docs: fix advice about preferred version in high availability (#10619)
* docs: fix advice about preferred version in high availability

Signed-off-by: Minchao <minchao.220@gmail.com>

* Update docs/operator-manual/high_availability.md

Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: Minchao <minchao.220@gmail.com>

Signed-off-by: Minchao <minchao.220@gmail.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2022-10-07 16:11:33 -04:00
Lars Kellogg-Stedman
b83554fca8 docs: Correct grammar issues in docs on manifest path annotations (#10776)
The "Webhook and Manifest Paths Annotation" section of the documentation
contained a number of grammar and spelling issues as well as what appeared
to be some unintentionally pasted text.

This commit attempts to address these issues.

Signed-off-by: Lars Kellogg-Stedman <lars@oddbit.com>

Signed-off-by: Lars Kellogg-Stedman <lars@oddbit.com>
2022-10-07 15:33:50 -04:00
Denis Krivenko
3275337a26 fix: Add missing statuses to MinIO Tenant health check (#10815)
Signed-off-by: dnskr <dnskrv88@gmail.com>

Signed-off-by: dnskr <dnskrv88@gmail.com>
2022-10-07 15:03:59 -04:00
Eddie Knight
89b6fe01a6 chore: Added recommended permissions to github actions workflows (#10812)
* Added recommended permissions to 4 of 5 workflows

Signed-off-by: Eddie Knight <iv.eddieknight@gmail.com>

* Added release.yaml permissions... might need to add pagages:write

Signed-off-by: Eddie Knight <iv.eddieknight@gmail.com>

* Updated inline comments

Signed-off-by: Eddie Knight <iv.eddieknight@gmail.com>

Signed-off-by: Eddie Knight <iv.eddieknight@gmail.com>
2022-10-07 14:41:59 -04:00
jannfis
1fe1a0060b fix: Unbreak app refresh from panel list (#10825)
Signed-off-by: jannfis <jann@mistrust.net>

Signed-off-by: jannfis <jann@mistrust.net>
2022-10-07 11:53:07 -04:00
Richard Jennings
de72cb1686 fix: add applicationset to crds generated by gen-crd-spec (#10833)
* add applicationset to crds generated

Signed-off-by: Richard Jennings <richardjennings@gmail.com>

* update applicationset crd

Signed-off-by: Richard Jennings <richardjennings@gmail.com>

* remove description from applicationset crd

Signed-off-by: Richard Jennings <richardjennings@gmail.com>

Signed-off-by: Richard Jennings <richardjennings@gmail.com>
2022-10-07 10:52:38 -04:00
Leonardo Luz Almeida
2e24cdc7ea docs: Add example about how to patch with SSA syncs (#10829)
* docs: Add example about how to patch with SSA syncs

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

* docs: minor fixes

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

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>
2022-10-06 15:43:49 -04:00
jannfis
ebf367497b fix(ui): Don't jump back to tiles view on app deletion (#8764) (#10826)
Signed-off-by: jannfis <jann@mistrust.net>

Signed-off-by: jannfis <jann@mistrust.net>
2022-10-06 14:07:18 -04:00
rumstead
277c6adaf1 docs: Update link to resource customizations (#10827) (#10828)
Signed-off-by: rumstead <37445536+rumstead@users.noreply.github.com>

Signed-off-by: rumstead <37445536+rumstead@users.noreply.github.com>
2022-10-06 12:04:21 -04:00
Abhishek Veeramalla
20ce840a2d chore: update Server-Side Apply docs for patching of existing rresources (#10822)
Signed-off-by: iam-veeramalla <abhishek.veeramalla@gmail.com>

Signed-off-by: iam-veeramalla <abhishek.veeramalla@gmail.com>
2022-10-06 11:17:28 -04:00
Remington Breeze
20776419d8 fix: Add filter icon to help users find filters (#10809)
* fix: add clear indicator that filters are now in sidebar

Signed-off-by: Remington Breeze <remington@breeze.software>

* address code review

Signed-off-by: Remington Breeze <remington@breeze.software>

Signed-off-by: Remington Breeze <remington@breeze.software>
2022-10-05 16:52:17 -07:00
argo-bot
2bf51f401d Bump version to 2.5.0-rc1 2022-10-05 16:59:55 +00:00
argo-bot
6a4468ce6b Bump version to 2.5.0-rc1 2022-10-05 16:59:47 +00:00
2812 changed files with 61912 additions and 315494 deletions

View File

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

View File

@@ -11,19 +11,3 @@ cmd/**/debug
debug.test debug.test
coverage.out coverage.out
ui/node_modules/ ui/node_modules/
test-results/
test/
manifests/
hack/
docs/
examples/
.github/
!test/container
!test/e2e/testdata
!test/fixture
!test/remote
!hack/installers
!hack/gpg-wrapper.sh
!hack/git-verify-wrapper.sh
!hack/tool-versions.sh
!hack/install.sh

13
.gitattributes vendored
View File

@@ -1,13 +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/crds/*-crd.yaml linguist-generated=true
manifests/ha/install.yaml linguist-generated=true
manifests/ha/namespace-install.yaml linguist-generated=true
manifests/install.yaml linguist-generated=true
manifests/namespace-install.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

@@ -1,26 +0,0 @@
---
name: Argo CD Release
about: Used by our Release Champion to track progress of a minor release
title: 'Argo CD Release vX.X'
labels: 'release'
assignees: ''
---
Target RC1 date: ___. __, ____
Target GA date: ___. __, ____
- [ ] 1wk before feature freeze post in #argo-contributors that PRs must be merged by DD-MM-YYYY to be included in the release - ask approvers to drop items from milestone they cant merge
- [ ] At least two days before RC1 date, draft RC blog post and submit it for review (or delegate this task)
- [ ] Cut RC1 (or delegate this task to an Approver and coordinate timing)
- [ ] Create new release branch
- [ ] Add the release branch to ReadTheDocs
- [ ] Confirm that tweet and blog post are ready
- [ ] Trigger the release
- [ ] After the release is finished, publish tweet and blog post
- [ ] Post in #argo-cd and #argo-announcements with lots of emojis announcing the release and requesting help testing
- [ ] Monitor support channels for issues, cherry-picking bugfixes and docs fixes as appropriate (or delegate this task to an Approver and coordinate timing)
- [ ] At release date, evaluate if any bugs justify delaying the release. If not, cut the release (or delegate this task to an Approver and coordinate timing)
- [ ] If unreleased changes are on the release branch for {current minor version minus 3}, cut a final patch release for that series (or delegate this task to an Approver and coordinate timing)
- [ ] After the release, post in #argo-cd that the {current minor version minus 3} has reached EOL (example: https://cloud-native.slack.com/archives/C01TSERG0KZ/p1667336234059729)
- [ ] (For the next release champion) Review the [items scheduled for the next release](https://github.com/orgs/argoproj/projects/25). If any item does not have an assignee who can commit to finish the feature, move it to the next release.
- [ ] (For the next release champion) Schedule a time mid-way through the release cycle to review items again.

View File

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

View File

@@ -1,58 +0,0 @@
version: 2
updates:
- package-ecosystem: "gomod"
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: "/"
schedule:
interval: "daily"
- package-ecosystem: "npm"
directory: "/ui/"
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/"
schedule:
interval: "daily"
- package-ecosystem: "docker"
directory: "/test/e2e/multiarch-container/"
schedule:
interval: "daily"
- package-ecosystem: "docker"
directory: "/test/remote/"
schedule:
interval: "daily"
- package-ecosystem: "docker"
directory: "/ui-test/"
schedule:
interval: "daily"

View File

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

View File

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

View File

@@ -1,39 +0,0 @@
# Workflows
| Workflow | Description |
|--------------------|----------------------------------------------------------------|
| ci-build.yaml | Build, lint, test, codegen, build-ui, analyze, e2e-test |
| 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 |
| release.yaml | Build images, cli-binaries, provenances, and post actions |
| scorecard.yaml | Generate scorecard for supply-chain security |
| update-snyk.yaml | Scheduled snyk reports |
# Reusable workflows
## image-reuse.yaml
- The resuable workflow can be used to publish or build images with multiple container registries(Quay,GHCR, dockerhub), and then sign them with cosign when an image is published.
- A GO version `must` be specified e.g. 1.21
- The image name for each registry *must* contain the tag. Note: multiple tags are allowed for each registry using a CSV type.
- Multiple platforms can be specified e.g. linux/amd64,linux/arm64
- Images are not published by default. A boolean value must be set to `true` to push images.
- An optional target can be specified.
| Inputs | Description | Type | Required | Defaults |
|-------------------|-------------------------------------|-------------|----------|-----------------|
| go-version | Version of Go to be used | string | true | none |
| quay_image_name | Full image name and tag | CSV, string | false | none |
| ghcr_image_name | Full image name and tag | CSV, string | false | none |
| docker_image_name | Full image name and tag | CSV, string | false | none |
| platforms | Platforms to build (linux/amd64) | CSV, string | false | linux/amd64 |
| push | Whether to push image/s to registry | boolean | false | false |
| target | Target build stage | string | false | none |
| Outputs | Description | Type |
|-------------|------------------------------------------|-------|
|image-digest | Image digest of image container created | string|

View File

@@ -1,5 +1,5 @@
name: Integration tests name: Integration tests
on: on:
push: push:
branches: branches:
- 'master' - 'master'
@@ -13,8 +13,7 @@ on:
env: env:
# Golang version to use across CI steps # Golang version to use across CI steps
# renovate: datasource=golang-version packageName=golang GOLANG_VERSION: '1.18'
GOLANG_VERSION: '1.23.3'
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
@@ -24,65 +23,36 @@ permissions:
contents: read contents: read
jobs: 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: check-go:
name: Ensure Go modules synchronicity name: Ensure Go modules synchronicity
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
needs:
- changes
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Download all Go modules - name: Download all Go modules
run: | run: |
go mod download go mod download
- name: Check for tidiness of go.mod and go.sum - name: Check for tidyness of go.mod and go.sum
run: | run: |
go mod tidy go mod tidy
git diff --exit-code -- . git diff --exit-code -- .
build-go: build-go:
name: Build & cache Go code name: Build & cache Go code
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
needs:
- changes
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Restore go build cache - name: Restore go build cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with: with:
path: ~/.cache/go-build path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }} key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -97,43 +67,37 @@ jobs:
contents: read # for actions/checkout to fetch code contents: read # for actions/checkout to fetch code
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
name: Lint Go code name: Lint Go code
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
needs:
- changes
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Run golangci-lint - name: Run golangci-lint
uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1 uses: golangci/golangci-lint-action@0ad9a0988b3973e851ab0a07adf248ec2e100376 # v3.3.1
with: with:
# renovate: datasource=go packageName=github.com/golangci/golangci-lint versioning=regex:^v(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)?$ version: v1.46.2
version: v1.62.2 args: --timeout 10m --exclude SA5011 --verbose
args: --verbose
test-go: test-go:
name: Run unit tests for Go packages name: Run unit tests for Go packages
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
needs: needs:
- build-go - build-go
- changes
env: env:
GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} 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: steps:
- name: Create checkout directory - name: Create checkout directory
run: mkdir -p ~/go/src/github.com/argoproj run: mkdir -p ~/go/src/github.com/argoproj
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Create symlink in GOPATH - name: Create symlink in GOPATH
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Install required packages - name: Install required packages
@@ -153,7 +117,7 @@ jobs:
run: | run: |
echo "/usr/local/bin" >> $GITHUB_PATH echo "/usr/local/bin" >> $GITHUB_PATH
- name: Restore go build cache - name: Restore go build cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with: with:
path: ~/.cache/go-build path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }} key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -173,31 +137,34 @@ jobs:
go mod download go mod download
- name: Run all unit tests - name: Run all unit tests
run: make test-local run: make test-local
- name: Generate code coverage artifacts
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
name: code-coverage
path: coverage.out
- name: Generate test results artifacts - name: Generate test results artifacts
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with: with:
name: test-results name: test-results
path: test-results path: test-results/
test-go-race: test-go-race:
name: Run unit tests with -race for Go packages name: Run unit tests with -race, for Go packages
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
needs: needs:
- build-go - build-go
- changes
env: env:
GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} 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: steps:
- name: Create checkout directory - name: Create checkout directory
run: mkdir -p ~/go/src/github.com/argoproj run: mkdir -p ~/go/src/github.com/argoproj
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Create symlink in GOPATH - name: Create symlink in GOPATH
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Install required packages - name: Install required packages
@@ -217,7 +184,7 @@ jobs:
run: | run: |
echo "/usr/local/bin" >> $GITHUB_PATH echo "/usr/local/bin" >> $GITHUB_PATH
- name: Restore go build cache - name: Restore go build cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with: with:
path: ~/.cache/go-build path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }} key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -238,22 +205,19 @@ jobs:
- name: Run all unit tests - name: Run all unit tests
run: make test-race-local run: make test-race-local
- name: Generate test results artifacts - name: Generate test results artifacts
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with: with:
name: race-results name: race-results
path: test-results/ path: test-results/
codegen: codegen:
name: Check changes to generated code name: Check changes to generated code
if: ${{ needs.changes.outputs.backend == 'true' || needs.changes.outputs.docs == 'true'}}
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
needs:
- changes
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Create symlink in GOPATH - name: Create symlink in GOPATH
@@ -296,22 +260,17 @@ jobs:
build-ui: build-ui:
name: Build, test & lint UI code 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 runs-on: ubuntu-22.04
needs:
- changes
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Setup NodeJS - name: Setup NodeJS
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
with: with:
# renovate: datasource=node-version packageName=node versioning=node node-version: '12.18.4'
node-version: '22.9.0'
- name: Restore node dependency cache - name: Restore node dependency cache
id: cache-dependencies id: cache-dependencies
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with: with:
path: ui/node_modules path: ui/node_modules
key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }} key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }}
@@ -326,8 +285,6 @@ jobs:
NODE_ENV: production NODE_ENV: production
NODE_ONLINE_ENV: online NODE_ONLINE_ENV: online
HOST_ARCH: amd64 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/ working-directory: ui/
- name: Run ESLint - name: Run ESLint
run: yarn lint run: yarn lint
@@ -335,86 +292,78 @@ jobs:
analyze: analyze:
name: Process & analyze test artifacts name: Process & analyze test artifacts
if: ${{ needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true' }}
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
needs: needs:
- test-go - test-go
- build-ui - build-ui
- changes
- test-e2e
env: env:
sonar_secret: ${{ secrets.SONAR_TOKEN }} sonar_secret: ${{ secrets.SONAR_TOKEN }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Restore node dependency cache - name: Restore node dependency cache
id: cache-dependencies id: cache-dependencies
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with: with:
path: ui/node_modules path: ui/node_modules
key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }} key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }}
- name: Remove other node_modules directory - name: Remove other node_modules directory
run: | run: |
rm -rf ui/node_modules/argo-ui/node_modules rm -rf ui/node_modules/argo-ui/node_modules
- name: Get e2e code coverage - name: Create test-results directory
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 run: |
mkdir -p test-results
- name: Get code coverage artifiact
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
with: with:
name: e2e-code-coverage name: code-coverage
path: e2e-code-coverage - name: Get test result artifact
- name: Get unit test code coverage uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with: with:
name: test-results name: test-results
path: 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 - name: Upload code coverage information to codecov.io
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1
with: with:
file: test-results/full-coverage.out file: 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 }}
- name: Perform static code analysis using SonarCloud - name: Perform static code analysis using SonarCloud
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
uses: SonarSource/sonarqube-scan-action@1b442ee39ac3fa7c2acdd410208dcb2bcfaae6c4 # v4.1.0 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 != '' if: env.sonar_secret != ''
test-e2e: test-e2e:
name: Run end-to-end tests name: Run end-to-end tests
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
strategy: strategy:
fail-fast: false
matrix: matrix:
k3s: k3s-version: [v1.24.3, v1.23.3, v1.22.6]
- version: v1.31.0 needs:
# We designate the latest version because we only collect code coverage for that version.
latest: true
- version: v1.30.4
latest: false
- version: v1.29.8
latest: false
- version: v1.28.13
latest: false
needs:
- build-go - build-go
- changes
env: env:
GOPATH: /home/runner/go GOPATH: /home/runner/go
ARGOCD_FAKE_IN_CLUSTER: "true" ARGOCD_FAKE_IN_CLUSTER: "true"
@@ -424,15 +373,15 @@ jobs:
ARGOCD_E2E_K3S: "true" ARGOCD_E2E_K3S: "true"
ARGOCD_IN_CI: "true" ARGOCD_IN_CI: "true"
ARGOCD_E2E_APISERVER_PORT: "8088" ARGOCD_E2E_APISERVER_PORT: "8088"
ARGOCD_APPLICATION_NAMESPACES: "argocd-e2e-external,argocd-e2e-external-2" ARGOCD_APPLICATION_NAMESPACES: "argocd-e2e-external"
ARGOCD_SERVER: "127.0.0.1:8088" ARGOCD_SERVER: "127.0.0.1:8088"
GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} 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: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: GH actions workaround - Kill XSP4 process - name: GH actions workaround - Kill XSP4 process
@@ -440,7 +389,7 @@ jobs:
sudo pkill mono || true sudo pkill mono || true
- name: Install K3S - name: Install K3S
env: env:
INSTALL_K3S_VERSION: ${{ matrix.k3s.version }}+k3s1 INSTALL_K3S_VERSION: ${{ matrix.k3s-version }}+k3s1
run: | run: |
set -x set -x
curl -sfL https://get.k3s.io | sh - curl -sfL https://get.k3s.io | sh -
@@ -448,10 +397,9 @@ jobs:
sudo mkdir -p $HOME/.kube && sudo chown -R runner $HOME/.kube sudo mkdir -p $HOME/.kube && sudo chown -R runner $HOME/.kube
sudo k3s kubectl config view --raw > $HOME/.kube/config sudo k3s kubectl config view --raw > $HOME/.kube/config
sudo chown runner $HOME/.kube/config sudo chown runner $HOME/.kube/config
sudo chmod go-r $HOME/.kube/config
kubectl version kubectl version
- name: Restore go build cache - name: Restore go build cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with: with:
path: ~/.cache/go-build path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }} key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -477,9 +425,9 @@ jobs:
git config --global user.email "john.doe@example.com" git config --global user.email "john.doe@example.com"
- name: Pull Docker image required for tests - name: Pull Docker image required for tests
run: | run: |
docker pull ghcr.io/dexidp/dex:v2.41.1 docker pull ghcr.io/dexidp/dex:v2.35.3
docker pull argoproj/argo-cd-ci-builder:v1.0.0 docker pull argoproj/argo-cd-ci-builder:v1.0.0
docker pull redis:7.0.15-alpine docker pull redis:7.0.8-alpine
- name: Create target directory for binaries in the build-process - name: Create target directory for binaries in the build-process
run: | run: |
mkdir -p dist mkdir -p dist
@@ -492,7 +440,7 @@ jobs:
# port 8080 which is not visible in netstat -tulpen, but still there # port 8080 which is not visible in netstat -tulpen, but still there
# with a HTTP listener. We have API server listening on port 8088 # with a HTTP listener. We have API server listening on port 8088
# instead. # 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 count=1
until curl -f http://127.0.0.1:8088/healthz; do until curl -f http://127.0.0.1:8088/healthz; do
sleep 10; sleep 10;
@@ -506,40 +454,9 @@ jobs:
run: | run: |
set -x set -x
make test-e2e-local 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 - name: Upload e2e-server logs
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with: with:
name: e2e-server-k8s${{ matrix.k3s.version }}.log name: e2e-server-k8s${{ matrix.k3s-version }}.log
path: /tmp/e2e-server.log path: /tmp/e2e-server.log
if: ${{ failure() }} 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

@@ -5,7 +5,6 @@ on:
# Secrets aren't available for dependabot on push. https://docs.github.com/en/enterprise-cloud@latest/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/troubleshooting-the-codeql-workflow#error-403-resource-not-accessible-by-integration-when-using-dependabot # Secrets aren't available for dependabot on push. https://docs.github.com/en/enterprise-cloud@latest/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/troubleshooting-the-codeql-workflow#error-403-resource-not-accessible-by-integration-when-using-dependabot
branches-ignore: branches-ignore:
- 'dependabot/**' - 'dependabot/**'
- 'cherry-pick-*'
pull_request: pull_request:
schedule: schedule:
- cron: '0 19 * * 0' - cron: '0 19 * * 0'
@@ -23,23 +22,18 @@ jobs:
actions: read # for github/codeql-action/init to get workflow details actions: read # for github/codeql-action/init to get workflow details
contents: read # for actions/checkout to fetch code contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/autobuild to send a status report 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 # CodeQL runs on ubuntu-latest and windows-latest
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.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
with:
go-version-file: go.mod
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - 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 # Override language selection by uncommenting this and choosing your languages
# with: # with:
# languages: go, javascript, csharp, python, cpp, java # languages: go, javascript, csharp, python, cpp, java
@@ -47,7 +41,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # 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) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - 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. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@@ -61,4 +55,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@8fcfedf57053e09257688fce7a0beeb18b1b9ae3 # v2.17.2 uses: github/codeql-action/analyze@8aff97f12c99086bdb92ff62ae06dbbcdf07941b # v2.1.33

View File

@@ -1,171 +0,0 @@
name: Publish and Sign Container Image
on:
workflow_call:
inputs:
go-version:
required: true
type: string
quay_image_name:
required: false
type: string
ghcr_image_name:
required: false
type: string
docker_image_name:
required: false
type: string
platforms:
required: true
type: string
default: linux/amd64
push:
required: true
type: boolean
default: false
target:
required: false
type: string
secrets:
quay_username:
required: false
quay_password:
required: false
ghcr_username:
required: false
ghcr_password:
required: false
docker_username:
required: false
docker_password:
required: false
outputs:
image-digest:
description: "sha256 digest of container image"
value: ${{ jobs.publish.outputs.image-digest }}
permissions: {}
jobs:
publish:
permissions:
contents: read
packages: write # Used to push images to `ghcr.io` if used.
id-token: write # Needed to create an OIDC token for keyless signing
runs-on: ubuntu-22.04
outputs:
image-digest: ${{ steps.image.outputs.digest }}
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # 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
if: ${{ github.ref_type != 'tag'}}
- name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with:
go-version: ${{ inputs.go-version }}
- name: Install cosign
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
- uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0
- uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1
- name: Setup tags for container image as a CSV type
run: |
IMAGE_TAGS=$(for str in \
${{ inputs.quay_image_name }} \
${{ inputs.ghcr_image_name }} \
${{ inputs.docker_image_name}}; do
echo -n "${str}",;done | sed 's/,$//')
echo $IMAGE_TAGS
echo "TAGS=$IMAGE_TAGS" >> $GITHUB_ENV
- name: Setup image namespace for signing, strip off the tag
run: |
TAGS=$(for tag in \
${{ inputs.quay_image_name }} \
${{ inputs.ghcr_image_name }} \
${{ inputs.docker_image_name}}; do
echo -n "${tag}" | awk -F ":" '{print $1}' -;done)
echo $TAGS
echo 'SIGNING_TAGS<<EOF' >> $GITHUB_ENV
echo $TAGS >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
- name: Login to Quay.io
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: quay.io
username: ${{ secrets.quay_username }}
password: ${{ secrets.quay_password }}
if: ${{ inputs.quay_image_name && inputs.push }}
- name: Login to GitHub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: ghcr.io
username: ${{ secrets.ghcr_username }}
password: ${{ secrets.ghcr_password }}
if: ${{ inputs.ghcr_image_name && inputs.push }}
- name: Login to dockerhub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
username: ${{ secrets.docker_username }}
password: ${{ secrets.docker_password }}
if: ${{ inputs.docker_image_name && inputs.push }}
- name: Set up build args for container image
run: |
echo "GIT_TAG=$(if [ -z "`git status --porcelain`" ]; then git describe --exact-match --tags HEAD 2>/dev/null; fi)" >> $GITHUB_ENV
echo "GIT_COMMIT=$(git rev-parse HEAD)" >> $GITHUB_ENV
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
echo "GIT_TREE_STATE=$(if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)" >> $GITHUB_ENV
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
with:
large-packages: false
docker-images: false
swap-storage: false
tool-cache: false
- name: Build and push container image
id: image
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 #v6.10.0
with:
context: .
platforms: ${{ inputs.platforms }}
push: ${{ inputs.push }}
tags: ${{ env.TAGS }}
target: ${{ inputs.target }}
provenance: false
sbom: false
build-args: |
GIT_TAG=${{env.GIT_TAG}}
GIT_COMMIT=${{env.GIT_COMMIT}}
BUILD_DATE=${{env.BUILD_DATE}}
GIT_TREE_STATE=${{env.GIT_TREE_STATE}}
- name: Sign container images
run: |
for signing_tag in $SIGNING_TAGS; do
cosign sign \
-a "repo=${{ github.repository }}" \
-a "workflow=${{ github.workflow }}" \
-a "sha=${{ github.sha }}" \
-y \
"$signing_tag"@${{ steps.image.outputs.digest }}
done
if: ${{ inputs.push }}

View File

@@ -9,111 +9,96 @@ on:
- master - master
types: [ labeled, unlabeled, opened, synchronize, reopened ] types: [ labeled, unlabeled, opened, synchronize, reopened ]
env:
GOLANG_VERSION: '1.18'
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
permissions: {} permissions:
contents: read
jobs: jobs:
set-vars: publish:
permissions: permissions:
contents: read contents: write # for git to push upgrade commit if not already deployed
if: github.repository == 'argoproj/argo-cd' if: github.repository == 'argoproj/argo-cd'
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
outputs: env:
image-tag: ${{ steps.image.outputs.tag}} GOPATH: /home/runner/work/argo-cd/argo-cd
platforms: ${{ steps.platforms.outputs.platforms }}
steps: steps:
- uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 - uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
with:
path: src/github.com/argoproj/argo-cd
- name: Set image tag for ghcr # get image tag
run: echo "tag=$(cat ./VERSION)-${GITHUB_SHA::8}" >> $GITHUB_OUTPUT - run: echo "tag=$(cat ./VERSION)-${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
working-directory: ./src/github.com/argoproj/argo-cd
id: image id: image
- name: Determine image platforms to use # login
id: platforms - run: |
run: | docker login ghcr.io --username $USERNAME --password-stdin <<< "$PASSWORD"
docker login quay.io --username "$DOCKER_USERNAME" --password-stdin <<< "$DOCKER_TOKEN"
if: github.event_name == 'push'
env:
USERNAME: ${{ secrets.USERNAME }}
PASSWORD: ${{ secrets.TOKEN }}
DOCKER_USERNAME: ${{ secrets.RELEASE_QUAY_USERNAME }}
DOCKER_TOKEN: ${{ secrets.RELEASE_QUAY_TOKEN }}
# build
- uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0
- uses: docker/setup-buildx-action@f03ac48505955848960e80bbb68046aa35c7b9e7 # v2.4.1
- run: |
IMAGE_PLATFORMS=linux/amd64 IMAGE_PLATFORMS=linux/amd64
if [[ "${{ github.event_name }}" == "push" || "${{ contains(github.event.pull_request.labels.*.name, 'test-multi-image') }}" == "true" ]] if [[ "${{ github.event_name }}" == "push" || "${{ contains(github.event.pull_request.labels.*.name, 'test-arm-image') }}" == "true" ]]
then then
IMAGE_PLATFORMS=linux/amd64,linux/arm64,linux/s390x,linux/ppc64le IMAGE_PLATFORMS=linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
fi fi
echo "Building image for platforms: $IMAGE_PLATFORMS" echo "Building image for platforms: $IMAGE_PLATFORMS"
echo "platforms=$IMAGE_PLATFORMS" >> $GITHUB_OUTPUT docker buildx build --platform $IMAGE_PLATFORMS --sbom=false --provenance=false --push="${{ github.event_name == 'push' }}" \
-t ghcr.io/argoproj/argocd:${{ steps.image.outputs.tag }} \
-t quay.io/argoproj/argocd:latest .
working-directory: ./src/github.com/argoproj/argo-cd
build-only: # sign container images
needs: [set-vars] - name: Install cosign
permissions: uses: sigstore/cosign-installer@c3667d99424e7e6047999fb6246c0da843953c65 # v3.0.1
contents: read with:
packages: write # for pushing packages to GHCR, which is used by cd.apps.argoproj.io to avoid polluting Quay with tags cosign-release: 'v1.13.1'
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.23.3
platforms: ${{ needs.set-vars.outputs.platforms }}
push: false
build-and-publish: - name: Install crane to get digest of image
needs: [set-vars] uses: imjasonh/setup-crane@00c9e93efa4e1138c9a7a5c594acd6c75a2fbf0c
permissions:
contents: read
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:
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.23.3
platforms: ${{ needs.set-vars.outputs.platforms }}
push: true
secrets:
quay_username: ${{ secrets.RELEASE_QUAY_USERNAME }}
quay_password: ${{ secrets.RELEASE_QUAY_TOKEN }}
ghcr_username: ${{ github.actor }}
ghcr_password: ${{ secrets.GITHUB_TOKEN }}
build-and-publish-provenance: # Push attestations to GHCR, latest image is polluting quay.io - name: Get digest of image
needs: run: |
- build-and-publish echo "IMAGE_DIGEST=$(crane digest quay.io/argoproj/argocd:latest)" >> $GITHUB_ENV
permissions:
actions: read # for detecting the Github Actions environment.
id-token: write # for creating OIDC tokens for signing.
packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
with:
image: ghcr.io/argoproj/argo-cd/argocd
digest: ${{ needs.build-and-publish.outputs.image-digest }}
registry-username: ${{ github.actor }}
secrets:
registry-password: ${{ secrets.GITHUB_TOKEN }}
Deploy: - name: Sign Argo CD latest image
needs: run: |
- build-and-publish cosign sign --key env://COSIGN_PRIVATE_KEY quay.io/argoproj/argocd@${{ env.IMAGE_DIGEST }}
- set-vars # Displays the public key to share.
permissions: cosign public-key --key env://COSIGN_PRIVATE_KEY
contents: write # for git to push upgrade commit if not already deployed env:
packages: write # for pushing packages to GHCR, which is used by cd.apps.argoproj.io to avoid polluting Quay with tags COSIGN_PRIVATE_KEY: ${{secrets.COSIGN_PRIVATE_KEY}}
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }} COSIGN_PASSWORD: ${{secrets.COSIGN_PASSWORD}}
runs-on: ubuntu-22.04 if: ${{ github.event_name == 'push' }}
steps:
- uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 # deploy
- run: git clone "https://$TOKEN@github.com/argoproj/argoproj-deployments" - run: git clone "https://$TOKEN@github.com/argoproj/argoproj-deployments"
if: github.event_name == 'push'
env: env:
TOKEN: ${{ secrets.TOKEN }} TOKEN: ${{ secrets.TOKEN }}
- run: | - run: |
docker run -u $(id -u):$(id -g) -v $(pwd):/src -w /src --rm -t ghcr.io/argoproj/argo-cd/argocd:${{ needs.set-vars.outputs.image-tag }} kustomize edit set image quay.io/argoproj/argocd=ghcr.io/argoproj/argo-cd/argocd:${{ needs.set-vars.outputs.image-tag }} docker run -u $(id -u):$(id -g) -v $(pwd):/src -w /src --rm -t ghcr.io/argoproj/argocd:${{ steps.image.outputs.tag }} kustomize edit set image quay.io/argoproj/argocd=ghcr.io/argoproj/argocd:${{ steps.image.outputs.tag }}
git config --global user.email 'ci@argoproj.com' git config --global user.email 'ci@argoproj.com'
git config --global user.name 'CI' 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) git diff --exit-code && echo 'Already deployed' || (git commit -am 'Upgrade argocd to ${{ steps.image.outputs.tag }}' && git push)
if: github.event_name == 'push'
working-directory: argoproj-deployments/argocd working-directory: argoproj-deployments/argocd
# TODO: clean up old images once github supports it: https://github.community/t5/How-to-use-Git-and-GitHub/Deleting-images-from-GitHub-Package-Registry/m-p/41202/thread-id/9811

View File

@@ -1,77 +0,0 @@
name: Init ArgoCD Release
on:
workflow_dispatch:
inputs:
TARGET_BRANCH:
description: 'TARGET_BRANCH to checkout (e.g. release-2.5)'
required: true
type: string
TARGET_VERSION:
description: 'TARGET_VERSION to build manifests (e.g. 2.5.0-rc1) Note: the `v` prefix is not used'
required: true
type: string
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 generate version and manifests on ${{ inputs.TARGET_BRANCH }}
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
ref: ${{ inputs.TARGET_BRANCH }}
- name: Check if TARGET_VERSION is well formed.
run: |
set -xue
# Target version must not contain 'v' prefix
if echo "${{ inputs.TARGET_VERSION }}" | grep -e '^v'; then
echo "::error::Target version '${{ inputs.TARGET_VERSION }}' should not begin with a 'v' prefix, refusing to continue." >&2
exit 1
fi
- name: Create VERSION information
run: |
set -ue
echo "Bumping version from $(cat VERSION) to ${{ inputs.TARGET_VERSION }}"
echo "${{ inputs.TARGET_VERSION }}" > VERSION
# 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: Generate new set of manifests
run: |
set -ue
make install-codegen-tools-local
make manifests-local VERSION=${{ inputs.TARGET_VERSION }}
git diff
- name: Generate version compatibility table
run: |
git stash
bash hack/update-supported-versions.sh
git add -u .
git stash pop
- name: Create pull request
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with:
commit-message: "Bump version to ${{ inputs.TARGET_VERSION }}"
title: "Bump version to ${{ inputs.TARGET_VERSION }} on ${{ inputs.TARGET_BRANCH }} branch"
body: Updating VERSION and manifests to ${{ inputs.TARGET_VERSION }}
branch: update-version
branch-suffix: random
signoff: true
labels: release

View File

@@ -1,29 +0,0 @@
name: "Lint PR"
on:
pull_request_target:
types: [opened, edited, reopened, synchronize]
# IMPORTANT: No checkout actions, scripts, or builds should be added to this workflow. Permissions should always be used
# with extreme caution. https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target
permissions: {}
# PR updates can happen in quick succession leading to this
# workflow being trigger a number of times. This limits it
# to one run per PR.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
validate:
permissions:
contents: read
pull-requests: read
name: Validate PR Title
runs-on: ubuntu-latest
steps:
- uses: thehanimo/pr-title-checker@7fbfe05602bdd86f926d3fb3bccb6f3aed43bc70 # v1.4.3
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
configuration_path: ".github/pr-title-checker-config.json"

View File

@@ -1,160 +1,271 @@
name: Publish ArgoCD Release name: Create ArgoCD release
on: on:
push: push:
tags: tags:
- 'v*' - "release-v*"
- '!v2.4*' - "!release-v1.5*"
- '!v2.5*' - "!release-v1.4*"
- '!v2.6*' - "!release-v1.3*"
- "!release-v1.2*"
permissions: {} - "!release-v1.1*"
- "!release-v1.0*"
- "!release-v0*"
env: env:
# renovate: datasource=golang-version packageName=golang GOLANG_VERSION: '1.18'
GOLANG_VERSION: '1.23.3' # Note: go-version must also be set in job argocd-image.with.go-version
permissions:
contents: read
jobs: jobs:
argocd-image: prepare-release:
permissions: permissions:
contents: read contents: write # To push changes to release branch
id-token: write # for creating OIDC tokens for signing. name: Perform automatic release on trigger ${{ github.ref }}
packages: write # used to push images to `ghcr.io` if used.
if: github.repository == 'argoproj/argo-cd'
uses: ./.github/workflows/image-reuse.yaml
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.23.3
platforms: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
push: true
secrets:
quay_username: ${{ secrets.RELEASE_QUAY_USERNAME }}
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 }}
goreleaser:
needs:
- argocd-image
- argocd-image-provenance
permissions:
contents: write # used for uploading assets
if: github.repository == 'argoproj/argo-cd' if: github.repository == 'argoproj/argo-cd'
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
outputs: env:
hashes: ${{ steps.hash.outputs.hashes }} # The name of the tag as supplied by the GitHub event
SOURCE_TAG: ${{ github.ref }}
# The image namespace where Docker image will be published to
IMAGE_NAMESPACE: quay.io/argoproj
# Whether to create & push image and release assets
DRY_RUN: false
# Whether a draft release should be created, instead of public one
DRAFT_RELEASE: false
# Whether to update homebrew with this release as well
# Set RELEASE_HOMEBREW_TOKEN secret in repository for this to work - needs
# access to public repositories
UPDATE_HOMEBREW: false
# Name of the GitHub user for Git config
GIT_USERNAME: argo-bot
# E-Mail of the GitHub user for Git config
GIT_EMAIL: argoproj@gmail.com
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Fetch all tags - name: Check if the published tag is well formed and setup vars
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.
run: | run: |
set -xue set -xue
echo "GORELEASER_PREVIOUS_TAG=$(go run hack/get-previous-release/get-previous-version-for-release-notes.go ${{ github.ref_name }})" >> $GITHUB_ENV # Target version must match major.minor.patch and optional -rcX suffix
# where X must be a number.
- name: Set environment variables for ldflags TARGET_VERSION=${SOURCE_TAG#*release-v}
id: set_ldflag if ! echo "${TARGET_VERSION}" | egrep '^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)*$'; then
run: | echo "::error::Target version '${TARGET_VERSION}' is malformed, refusing to continue." >&2
echo "KUBECTL_VERSION=$(go list -m k8s.io/client-go | head -n 1 | rev | cut -d' ' -f1 | rev)" >> $GITHUB_ENV exit 1
echo "GIT_TREE_STATE=$(if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)" >> $GITHUB_ENV
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
with:
large-packages: false
docker-images: false
swap-storage: false
tool-cache: false
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf # v6.1.0
id: run-goreleaser
with:
version: latest
args: release --clean --timeout 55m
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
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 }}"
run: |
set -euo pipefail
hashes=$(echo $ARTIFACTS | jq --raw-output '.[] | {name, "digest": (.extra.Digest // .extra.Checksum)} | select(.digest) | {digest} + {name} | join(" ") | sub("^sha256:";"")' | base64 -w0)
if test "$hashes" = ""; then # goreleaser < v1.13.0
checksum_file=$(echo "$ARTIFACTS" | jq -r '.[] | select (.type=="Checksum") | .path')
hashes=$(cat $checksum_file | base64 -w0)
fi fi
echo "hashes=$hashes" >> $GITHUB_OUTPUT
goreleaser-provenance: # Target branch is the release branch we're going to operate on
needs: [goreleaser] # Its name is 'release-<major>.<minor>'
permissions: TARGET_BRANCH="release-${TARGET_VERSION%\.[0-9]*}"
actions: read # for detecting the Github Actions environment
id-token: write # Needed for provenance signing and ID
contents: write # Needed for release uploads
if: github.repository == 'argoproj/argo-cd'
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
with:
base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
provenance-name: "argocd-cli.intoto.jsonl"
upload-assets: true
generate-sbom: # The release tag is the source tag, minus the release- prefix
name: Create SBOM and generate hash RELEASE_TAG="${SOURCE_TAG#*release-}"
needs:
- argocd-image # Whether this is a pre-release (indicated by -rc suffix)
- goreleaser PRE_RELEASE=false
permissions: if echo "${RELEASE_TAG}" | egrep -- '-rc[0-9]+$'; then
contents: write # Needed for release uploads PRE_RELEASE=true
outputs: fi
hashes: ${{ steps.sbom-hash.outputs.hashes}}
if: github.repository == 'argoproj/argo-cd' # We must not have a release trigger within the same release branch,
runs-on: ubuntu-22.04 # because that means a release for this branch is already running.
steps: if git tag -l | grep "release-v${TARGET_VERSION%\.[0-9]*}" | grep -v "release-v${TARGET_VERSION}"; then
- name: Checkout code echo "::error::Another release for branch ${TARGET_BRANCH} is currently in progress."
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 exit 1
with: fi
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }} # Ensure that release do not yet exist
if git rev-parse ${RELEASE_TAG}; then
echo "::error::Release tag ${RELEASE_TAG} already exists in repository. Refusing to continue."
exit 1
fi
# Make the variables available in follow-up steps
echo "TARGET_VERSION=${TARGET_VERSION}" >> $GITHUB_ENV
echo "TARGET_BRANCH=${TARGET_BRANCH}" >> $GITHUB_ENV
echo "RELEASE_TAG=${RELEASE_TAG}" >> $GITHUB_ENV
echo "PRE_RELEASE=${PRE_RELEASE}" >> $GITHUB_ENV
- name: Check if our release tag has a correct annotation
run: |
set -ue
# Fetch all tag information as well
git fetch --prune --tags --force
echo "=========== BEGIN COMMIT MESSAGE ============="
git show ${SOURCE_TAG}
echo "============ END COMMIT MESSAGE =============="
# Quite dirty hack to get the release notes from the annotated tag
# into a temporary file.
RELEASE_NOTES=$(mktemp -p /tmp release-notes.XXXXXX)
prefix=true
begin=false
git show ${SOURCE_TAG} | while read line; do
# Whatever is in commit history for the tag, we only want that
# annotation from our tag. We discard everything else.
if test "$begin" = "false"; then
if echo "$line" | grep -q "tag ${SOURCE_TAG#refs/tags/}"; then begin="true"; fi
continue
fi
if test "$prefix" = "true"; then
if test -z "$line"; then prefix=false; fi
else
if echo "$line" | egrep -q '^commit [0-9a-f]+'; then
break
fi
echo "$line" >> ${RELEASE_NOTES}
fi
done
# For debug purposes
echo "============BEGIN RELEASE NOTES================="
cat ${RELEASE_NOTES}
echo "=============END RELEASE NOTES=================="
# Too short release notes are suspicious. We need at least 100 bytes.
relNoteLen=$(stat -c '%s' $RELEASE_NOTES)
if test $relNoteLen -lt 100; then
echo "::error::No release notes provided in tag annotation (or tag is not annotated)"
exit 1
fi
# Check for magic string '## Quick Start' in head of release notes
if ! head -2 ${RELEASE_NOTES} | grep -iq '## Quick Start'; then
echo "::error::Release notes seem invalid, quick start section not found."
exit 1
fi
# We store path to temporary release notes file for later reading, we
# need it when creating release.
echo "RELEASE_NOTES=${RELEASE_NOTES}" >> $GITHUB_ENV
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Setup Git author information
run: |
set -ue
git config --global user.email "${GIT_EMAIL}"
git config --global user.name "${GIT_USERNAME}"
- name: Checkout corresponding release branch
run: |
set -ue
echo "Switching to release branch '${TARGET_BRANCH}'"
if ! git checkout ${TARGET_BRANCH}; then
echo "::error::Checking out release branch '${TARGET_BRANCH}' for target version '${TARGET_VERSION}' (tagged '${RELEASE_TAG}') failed. Does it exist in repo?"
exit 1
fi
- name: Create VERSION information
run: |
set -ue
echo "Bumping version from $(cat VERSION) to ${TARGET_VERSION}"
echo "${TARGET_VERSION}" > VERSION
git commit -m "Bump version to ${TARGET_VERSION}" VERSION
- name: Generate new set of manifests
run: |
set -ue
make install-codegen-tools-local
# We install kustomize in the dist directory
echo "/home/runner/work/argo-cd/argo-cd/dist" >> $GITHUB_PATH
make manifests-local VERSION=${TARGET_VERSION}
git diff
git commit manifests/ -m "Bump version to ${TARGET_VERSION}"
- name: Create the release tag
run: |
set -ue
echo "Creating release ${RELEASE_TAG}"
git tag ${RELEASE_TAG}
- name: Login to docker repositories
env:
DOCKER_USERNAME: ${{ secrets.RELEASE_DOCKERHUB_USERNAME }}
DOCKER_TOKEN: ${{ secrets.RELEASE_DOCKERHUB_TOKEN }}
QUAY_USERNAME: ${{ secrets.RELEASE_QUAY_USERNAME }}
QUAY_TOKEN: ${{ secrets.RELEASE_QUAY_TOKEN }}
run: |
set -ue
docker login quay.io --username "${QUAY_USERNAME}" --password-stdin <<< "${QUAY_TOKEN}"
# Remove the following when Docker Hub is gone
docker login --username "${DOCKER_USERNAME}" --password-stdin <<< "${DOCKER_TOKEN}"
if: ${{ env.DRY_RUN != 'true' }}
- uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0
- uses: docker/setup-buildx-action@f03ac48505955848960e80bbb68046aa35c7b9e7 # v2.4.1
- name: Build and push Docker image for release
run: |
set -ue
git clean -fd
mkdir -p dist/
docker buildx build --platform linux/amd64,linux/arm64,linux/s390x,linux/ppc64le --sbom=false --provenance=false --push -t ${IMAGE_NAMESPACE}/argocd:v${TARGET_VERSION} -t argoproj/argocd:v${TARGET_VERSION} .
make release-cli
make checksums
chmod +x ./dist/argocd-linux-amd64
./dist/argocd-linux-amd64 version --client
if: ${{ env.DRY_RUN != 'true' }}
- name: Install cosign
uses: sigstore/cosign-installer@c3667d99424e7e6047999fb6246c0da843953c65 # v3.0.1
with:
cosign-release: 'v1.13.1'
- name: Install crane to get digest of image
uses: imjasonh/setup-crane@00c9e93efa4e1138c9a7a5c594acd6c75a2fbf0c
- name: Get digest of image
run: |
echo "IMAGE_DIGEST=$(crane digest quay.io/argoproj/argocd:v${TARGET_VERSION})" >> $GITHUB_ENV
- name: Sign Argo CD container images and assets
run: |
cosign sign --key env://COSIGN_PRIVATE_KEY ${IMAGE_NAMESPACE}/argocd@${{ env.IMAGE_DIGEST }}
cosign sign-blob --key env://COSIGN_PRIVATE_KEY ./dist/argocd-${TARGET_VERSION}-checksums.txt > ./dist/argocd-${TARGET_VERSION}-checksums.sig
# Retrieves the public key to release as an asset
cosign public-key --key env://COSIGN_PRIVATE_KEY > ./dist/argocd-cosign.pub
env:
COSIGN_PRIVATE_KEY: ${{secrets.COSIGN_PRIVATE_KEY}}
COSIGN_PASSWORD: ${{secrets.COSIGN_PASSWORD}}
if: ${{ env.DRY_RUN != 'true' }}
- name: Read release notes file
id: release-notes
uses: juliangruber/read-file-action@02bbba9876a8f870efd4ad64e3b9088d3fb94d4b # v1.1.6
with:
path: ${{ env.RELEASE_NOTES }}
- name: Push changes to release branch
run: |
set -ue
git push origin ${TARGET_BRANCH}
git push origin ${RELEASE_TAG}
- name: Dry run GitHub release
uses: actions/create-release@0cb9c9b65d5d1901c1f53e5e66eaf4afd303e70e # v1.1.4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
id: create_release
with:
tag_name: ${{ env.RELEASE_TAG }}
release_name: ${{ env.RELEASE_TAG }}
draft: ${{ env.DRAFT_RELEASE }}
prerelease: ${{ env.PRE_RELEASE }}
body: ${{ steps.release-notes.outputs.content }}
if: ${{ env.DRY_RUN == 'true' }}
- name: Generate SBOM (spdx) - name: Generate SBOM (spdx)
id: spdx-builder id: spdx-builder
env: env:
@@ -166,7 +277,7 @@ jobs:
# managers (gomod, yarn, npm). # managers (gomod, yarn, npm).
PROJECT_FOLDERS: ".,./ui" PROJECT_FOLDERS: ".,./ui"
# full qualified name of the docker image to be inspected # full qualified name of the docker image to be inspected
DOCKER_IMAGE: quay.io/argoproj/argocd:${{ github.ref_name }} DOCKER_IMAGE: ${{env.IMAGE_NAMESPACE}}/argocd:v${{env.TARGET_VERSION}}
run: | run: |
yarn install --cwd ./ui yarn install --cwd ./ui
go install github.com/spdx/spdx-sbom-generator/cmd/generator@$SPDX_GEN_VERSION go install github.com/spdx/spdx-sbom-generator/cmd/generator@$SPDX_GEN_VERSION
@@ -184,122 +295,43 @@ jobs:
fi fi
cd /tmp && tar -zcf sbom.tar.gz *.spdx cd /tmp && tar -zcf sbom.tar.gz *.spdx
if: ${{ env.DRY_RUN != 'true' }}
- name: Generate SBOM hash - name: Sign sbom
shell: bash
id: sbom-hash
run: | run: |
# sha256sum generates sha256 hash for sbom. cosign sign-blob --key env://COSIGN_PRIVATE_KEY /tmp/sbom.tar.gz > /tmp/sbom.tar.gz.sig
# base64 -w0 encodes to base64 and outputs on a single line. env:
# sha256sum /tmp/sbom.tar.gz ... | base64 -w0 COSIGN_PRIVATE_KEY: ${{secrets.COSIGN_PRIVATE_KEY}}
echo "hashes=$(sha256sum /tmp/sbom.tar.gz | base64 -w0)" >> "$GITHUB_OUTPUT" COSIGN_PASSWORD: ${{secrets.COSIGN_PASSWORD}}
if: ${{ env.DRY_RUN != 'true' }}
- name: Upload SBOM - name: Create GitHub release
uses: softprops/action-gh-release@7b4da11513bf3f43f9999e90eabced41ab8bb048 # v2.2.0 uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
name: ${{ env.RELEASE_TAG }}
tag_name: ${{ env.RELEASE_TAG }}
draft: ${{ env.DRAFT_RELEASE }}
prerelease: ${{ env.PRE_RELEASE }}
body: ${{ steps.release-notes.outputs.content }} # Pre-pended to the generated notes
files: | files: |
dist/argocd-*
/tmp/sbom.tar.gz /tmp/sbom.tar.gz
/tmp/sbom.tar.gz.sig
if: ${{ env.DRY_RUN != 'true' }}
sbom-provenance: - name: Update homebrew formula
needs: [generate-sbom] env:
permissions: HOMEBREW_TOKEN: ${{ secrets.RELEASE_HOMEBREW_TOKEN }}
actions: read # for detecting the Github Actions environment uses: dawidd6/action-homebrew-bump-formula@02e79d9da43d79efa846d73695b6052cbbdbf48a # v3.8.3
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
with:
base64-subjects: "${{ needs.generate-sbom.outputs.hashes }}"
provenance-name: "argocd-sbom.intoto.jsonl"
upload-assets: true
post-release:
needs:
- argocd-image
- goreleaser
- generate-sbom
permissions:
contents: write # Needed to push commit to update stable tag
pull-requests: write # Needed to create PR for VERSION update.
if: github.repository == 'argoproj/argo-cd'
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
with: with:
fetch-depth: 0 token: ${{env.HOMEBREW_TOKEN}}
token: ${{ secrets.GITHUB_TOKEN }} formula: argocd
if: ${{ env.HOMEBREW_TOKEN != '' && env.UPDATE_HOMEBREW == 'true' && env.PRE_RELEASE != 'true' }}
- name: Setup Git author information - name: Delete original request tag from repository
run: | run: |
set -ue set -ue
git config --global user.email 'ci@argoproj.com' git push --delete origin ${SOURCE_TAG}
git config --global user.name 'CI' if: ${{ always() }}
- name: Check if tag is the latest version and not a pre-release
run: |
set -xue
# Fetch all tag information
git fetch --prune --tags --force
LATEST_TAG=$(git -c 'versionsort.suffix=-rc' tag --list --sort=version:refname | tail -n1)
PRE_RELEASE=false
# Check if latest tag is a pre-release
if echo $LATEST_TAG | grep -E -- '-rc[0-9]+$';then
PRE_RELEASE=true
fi
# Ensure latest tag matches github.ref_name & not a pre-release
if [[ $LATEST_TAG == ${{ github.ref_name }} ]] && [[ $PRE_RELEASE != 'true' ]];then
echo "TAG_STABLE=true" >> $GITHUB_ENV
else
echo "TAG_STABLE=false" >> $GITHUB_ENV
fi
- name: Update stable tag to latest version
run: |
git tag -f stable ${{ github.ref_name }}
git push -f origin stable
if: ${{ env.TAG_STABLE == 'true' }}
- name: Check to see if VERSION should be updated on master branch
run: |
set -xue
SOURCE_TAG=${{ github.ref_name }}
VERSION_REF="${SOURCE_TAG#*v}"
COMMIT_HASH=$(git rev-parse HEAD)
if echo "$VERSION_REF" | grep -E -- '^[0-9]+\.[0-9]+\.0-rc1';then
VERSION=$(awk 'BEGIN {FS=OFS="."} {$2++; print}' <<< "${VERSION_REF%-rc1}")
echo "Updating VERSION to: $VERSION"
echo "UPDATE_VERSION=true" >> $GITHUB_ENV
echo "NEW_VERSION=$VERSION" >> $GITHUB_ENV
echo "COMMIT_HASH=$COMMIT_HASH" >> $GITHUB_ENV
else
echo "Not updating VERSION"
echo "UPDATE_VERSION=false" >> $GITHUB_ENV
fi
- name: Update VERSION on master branch
run: |
echo ${{ env.NEW_VERSION }} > VERSION
# Replace the 'project-release: vX.X.X-rcX' line in SECURITY-INSIGHTS.yml
sed -i "s/project-release: v.*$/project-release: v${{ env.NEW_VERSION }}/" SECURITY-INSIGHTS.yml
# Update the 'commit-hash: XXXXXXX' line in SECURITY-INSIGHTS.yml
sed -i "s/commit-hash: .*/commit-hash: ${{ env.COMMIT_HASH }}/" SECURITY-INSIGHTS.yml
if: ${{ env.UPDATE_VERSION == 'true' }}
- name: Create PR to update VERSION on master branch
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with:
commit-message: 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
branch-suffix: random
base: master
if: ${{ env.UPDATE_VERSION == 'true' }}

View File

@@ -1,67 +0,0 @@
name: Scorecards supply-chain security
on:
# Only the default branch is supported.
branch_protection_rule:
schedule:
- cron: "39 9 * * 2"
push:
branches: ["master"]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# Declare default permissions as read only.
permissions: read-all
jobs:
analysis:
name: Scorecards analysis
runs-on: ubuntu-22.04
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Used to receive a badge. (Upcoming feature)
id-token: write
# Needs for private repositories.
contents: read
actions: read
if: github.repository == 'argoproj/argo-cd'
steps:
- name: "Checkout code"
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
with:
results_file: results.sarif
results_format: sarif
# (Optional) Read-only PAT token. Uncomment the `repo_token` line below if:
# - you want to enable the Branch-Protection check on a *public* repository, or
# - you are installing Scorecards on a *private* repository
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
# repo_token: ${{ secrets.SCORECARD_READ_TOKEN }}
# Publish the results for public repositories to enable scorecard badges. For more details, see
# https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories, `publish_results` will automatically be set to `false`, regardless
# of the value entered here.
publish_results: true
# 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
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@8fcfedf57053e09257688fce7a0beeb18b1b9ae3 # v2.17.2
with:
sarif_file: results.sarif

View File

@@ -1,6 +1,5 @@
name: Snyk report update name: Snyk report update
on: on:
workflow_dispatch: {}
schedule: schedule:
- cron: '0 0 * * 0' # midnight every Sunday - cron: '0 0 * * 0' # midnight every Sunday
@@ -10,27 +9,23 @@ permissions:
jobs: jobs:
snyk-report: snyk-report:
permissions: permissions:
contents: write contents: write # To push snyk reports
pull-requests: write
if: github.repository == 'argoproj/argo-cd' if: github.repository == 'argoproj/argo-cd'
name: Update Snyk report in the docs directory name: Update Snyk report in the docs directory
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Build reports - name: Build reports
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
run: | run: |
make snyk-report make snyk-report
pr_branch="snyk-update-$(echo $RANDOM | md5sum | head -c 20)"
git checkout -b "$pr_branch"
git config --global user.email 'ci@argoproj.com' git config --global user.email 'ci@argoproj.com'
git config --global user.name 'CI' git config --global user.name 'CI'
git add docs/snyk git add docs/snyk/index.md
git commit -m "[Bot] docs: Update Snyk reports" --signoff git add docs/snyk/*/*.html
git push --set-upstream origin "$pr_branch" git commit -m "[Bot] Update Snyk reports"
gh pr create -B master -H "$pr_branch" --title '[Bot] docs: Update Snyk report' --body '' git push

4
.gitignore vendored
View File

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

4
.gitpod.Dockerfile vendored
View File

@@ -1,4 +1,4 @@
FROM gitpod/workspace-full@sha256:230285e0b949e6d728d384b2029a4111db7b9c87c182f22f32a0be9e36b225df FROM gitpod/workspace-full
USER root USER root
@@ -13,8 +13,6 @@ ENV GOCACHE=/go-build-cache
RUN apt-get install redis-server -y RUN apt-get install redis-server -y
RUN go install github.com/mattn/goreman@latest RUN go install github.com/mattn/goreman@latest
RUN chown -R gitpod:gitpod /go-build-cache
USER gitpod USER gitpod
ENV ARGOCD_REDIS_LOCAL=true ENV ARGOCD_REDIS_LOCAL=true

View File

@@ -1,59 +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
- gosimple
- govet
- ineffassign
- misspell
- perfsprint
- staticcheck
- testifylint
- thelper
- unparam
- unused
- usestdlibvars
- whitespace
linters-settings:
gocritic:
disabled-checks:
- appendAssign
- assignOp # Keep it disabled for readability
- badCond
- commentFormatting
- exitAfterDefer
- ifElseChain
- mapKey
- singleCaseSwitch
- typeSwitchVar
goimports:
local-prefixes: github.com/argoproj/argo-cd/v2
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: false
# Optimizes `fmt.Errorf`.
errorf: false
# Optimizes `fmt.Sprintf` with only one argument.
sprintf1: true
# Optimizes into strings concatenation.
strconcat: false
testifylint:
enable-all: true
disable:
- go-require
run:
timeout: 50m

View File

@@ -1,123 +0,0 @@
version: 2
project_name: argocd
before:
hooks:
- go mod download
- make build-ui
builds:
- id: argocd-cli
main: ./cmd
binary: argocd-{{ .Os}}-{{ .Arch}}
env:
- CGO_ENABLED=0
flags:
- -v
ldflags:
- -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
- darwin
- windows
goarch:
- amd64
- arm64
- s390x
- ppc64le
ignore:
- goos: darwin
goarch: s390x
- goos: darwin
goarch: ppc64le
- goos: windows
goarch: s390x
- goos: windows
goarch: ppc64le
- goos: windows
goarch: arm64
archives:
- id: argocd-archive
builds:
- argocd-cli
name_template: |-
{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}
format: binary
checksum:
name_template: 'cli_checksums.txt'
algorithm: sha256
release:
prerelease: auto
draft: false
header: |
## Quick Start
### Non-HA:
```shell
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/{{.Tag}}/manifests/install.yaml
```
### HA:
```shell
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/{{.Tag}}/manifests/ha/install.yaml
```
## Release Signatures and Provenance
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.
## Upgrading
If upgrading from a different minor version, be sure to read the [upgrading](https://argo-cd.readthedocs.io/en/stable/operator-manual/upgrading/overview/) documentation.
footer: |
**Full Changelog**: https://github.com/argoproj/argo-cd/compare/{{ .PreviousTag }}...{{ .Tag }}
<a href="https://argoproj.github.io/cd/"><img src="https://raw.githubusercontent.com/argoproj/argo-site/master/content/pages/cd/gitops-cd.png" width="25%" ></a>
snapshot: #### To be removed for PR
name_template: "2.6.0"
changelog:
use:
github
sort: asc
abbrev: 0
groups: # Regex use RE2 syntax as defined here: https://github.com/google/re2/wiki/Syntax.
- title: 'Features'
regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
order: 100
- title: 'Bug fixes'
regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$'
order: 200
- title: 'Documentation'
regexp: '^.*?docs(\([[:word:]]+\))??!?:.+$'
order: 300
- title: 'Dependency updates'
regexp: '^.*?(feat|fix|chore)\(deps?.+\)!?:.+$'
order: 400
- title: 'Other work'
order: 999
filters:
exclude:
- '^test:'
- '^.*?Bump(\([[:word:]]+\))?.+$'
- '^.*?\[Bot\](\([[:word:]]+\))?.+$'
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json

View File

@@ -1,76 +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/v2/applicationset/generators:
interfaces:
Generator:
github.com/argoproj/argo-cd/v2/applicationset/services:
interfaces:
Repos:
github.com/argoproj/argo-cd/v2/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/v2/applicationset/utils:
interfaces:
Renderer:
github.com/argoproj/argo-cd/v2/commitserver/commit:
interfaces:
RepoClientFactory:
github.com/argoproj/argo-cd/v2/commitserver/apiclient:
interfaces:
CommitServiceClient:
Clientset:
github.com/argoproj/argo-cd/v2/controller/cache:
interfaces:
LiveStateCache:
github.com/argoproj/argo-cd/v2/reposerver/apiclient:
interfaces:
RepoServerServiceClient:
RepoServerService_GenerateManifestWithFilesClient:
github.com/argoproj/argo-cd/v2/server/application:
interfaces:
Broadcaster:
github.com/argoproj/argo-cd/v2/server/extension:
interfaces:
ApplicationGetter:
ExtensionMetricsRegistry:
ProjectGetter:
RbacEnforcer:
SettingsGetter:
UserGetter:
github.com/argoproj/argo-cd/v2/util/db:
interfaces:
ArgoDB:
github.com/argoproj/argo-cd/v2/util/git:
interfaces:
Client:
github.com/argoproj/argo-cd/v2/util/helm:
interfaces:
Client:
github.com/argoproj/argo-cd/v2/util/io:
interfaces:
TempPaths:
github.com/argoproj/argo-cd/v2/util/notification/argocd:
interfaces:
Service:
# These mocks are not currently used, but they are part of the public API of this package.
github.com/argoproj/argo-cd/v2/pkg/apiclient/session:
interfaces:
SessionServiceServer:
SessionServiceClient:
github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster:
interfaces:
ClusterServiceServer:

View File

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

7
.readthedocs.yml Normal file
View File

@@ -0,0 +1,7 @@
version: 2
formats: all
mkdocs:
fail_on_warning: false
python:
install:
- requirements: docs/requirements.txt

View File

@@ -1,14 +0,0 @@
# All
** @argoproj/argocd-approvers
# Docs
/docs/** @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
/USERS.md @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
/README.md @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
/mkdocs.yml @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
# CI
/.codecov.yml @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
/.github/** @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
/.goreleaser.yaml @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
/sonar-project.properties @argoproj/argocd-approvers @argoproj/argocd-approvers-ci

View File

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

View File

@@ -1,12 +1,12 @@
ARG BASE_IMAGE=docker.io/library/ubuntu:24.04@sha256:3f85b7caad41a95462cf5b787d8a04604c8262cdcdf9a472b8c52ef83375fe15 ARG BASE_IMAGE=docker.io/library/ubuntu:22.04
#################################################################################################### ####################################################################################################
# Builder image # Builder image
# Initial stage which pulls prepares build dependencies and CLI tooling we need for our final 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 # Also used as the image in CI jobs so needs all dependencies
#################################################################################################### ####################################################################################################
FROM docker.io/library/golang:1.23.3@sha256:d56c3e08fe5b27729ee3834854ae8f7015af48fd651cd25d1e3bcf3c19830174 AS builder FROM docker.io/library/golang:1.18 AS builder
RUN echo 'deb http://archive.debian.org/debian buster-backports main' >> /etc/apt/sources.list RUN echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list
RUN apt-get update && apt-get install --no-install-recommends -y \ RUN apt-get update && apt-get install --no-install-recommends -y \
openssh-server \ openssh-server \
@@ -28,7 +28,7 @@ WORKDIR /tmp
COPY hack/install.sh hack/tool-versions.sh ./ COPY hack/install.sh hack/tool-versions.sh ./
COPY hack/installers installers COPY hack/installers installers
RUN ./install.sh helm && \ RUN ./install.sh helm-linux && \
INSTALL_PATH=/usr/local/bin ./install.sh kustomize INSTALL_PATH=/usr/local/bin ./install.sh kustomize
#################################################################################################### ####################################################################################################
@@ -36,8 +36,6 @@ RUN ./install.sh helm && \
#################################################################################################### ####################################################################################################
FROM $BASE_IMAGE AS argocd-base FROM $BASE_IMAGE AS argocd-base
LABEL org.opencontainers.image.source="https://github.com/argoproj/argo-cd"
USER root USER root
ENV ARGOCD_USER_ID=999 ENV ARGOCD_USER_ID=999
@@ -51,7 +49,7 @@ RUN groupadd -g $ARGOCD_USER_ID argocd && \
apt-get update && \ apt-get update && \
apt-get dist-upgrade -y && \ apt-get dist-upgrade -y && \
apt-get install -y \ apt-get install -y \
git git-lfs tini gpg tzdata connect-proxy && \ git git-lfs tini gpg tzdata && \
apt-get clean && \ apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
@@ -83,7 +81,7 @@ WORKDIR /home/argocd
#################################################################################################### ####################################################################################################
# Argo CD UI stage # 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:12.18.4 AS argocd-ui
WORKDIR /src WORKDIR /src
COPY ["ui/package.json", "ui/yarn.lock", "./"] COPY ["ui/package.json", "ui/yarn.lock", "./"]
@@ -101,7 +99,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 # Argo CD Build stage which performs the actual build of Argo CD binaries
#################################################################################################### ####################################################################################################
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.23.3@sha256:d56c3e08fe5b27729ee3834854ae8f7015af48fd651cd25d1e3bcf3c19830174 AS argocd-build FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.18 AS argocd-build
WORKDIR /go/src/github.com/argoproj/argo-cd WORKDIR /go/src/github.com/argoproj/argo-cd
@@ -113,18 +111,7 @@ COPY . .
COPY --from=argocd-ui /src/dist/app /go/src/github.com/argoproj/argo-cd/ui/dist/app COPY --from=argocd-ui /src/dist/app /go/src/github.com/argoproj/argo-cd/ui/dist/app
ARG TARGETOS ARG TARGETOS
ARG TARGETARCH ARG TARGETARCH
# These build args are optional; if not specified the defaults will be taken from the Makefile RUN GOOS=$TARGETOS GOARCH=$TARGETARCH make argocd-all
ARG GIT_TAG
ARG BUILD_DATE
ARG GIT_TREE_STATE
ARG GIT_COMMIT
RUN GIT_COMMIT=$GIT_COMMIT \
GIT_TREE_STATE=$GIT_TREE_STATE \
GIT_TAG=$GIT_TAG \
BUILD_DATE=$BUILD_DATE \
GOOS=$TARGETOS \
GOARCH=$TARGETARCH \
make argocd-all
#################################################################################################### ####################################################################################################
# Final image # Final image
@@ -140,8 +127,6 @@ 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-dex && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-notifications && \ 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-applicationset-controller && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-k8s-auth && \ ln -s /usr/local/bin/argocd /usr/local/bin/argocd-k8s-auth
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-commit-server
USER $ARGOCD_USER_ID USER $ARGOCD_USER_ID
ENTRYPOINT ["/usr/bin/tini", "--"]

227
Makefile
View File

@@ -3,41 +3,31 @@ CURRENT_DIR=$(shell pwd)
DIST_DIR=${CURRENT_DIR}/dist DIST_DIR=${CURRENT_DIR}/dist
CLI_NAME=argocd CLI_NAME=argocd
BIN_NAME=argocd BIN_NAME=argocd
CGO_FLAG=0
GEN_RESOURCES_CLI_NAME=argocd-resources-gen GEN_RESOURCES_CLI_NAME=argocd-resources-gen
HOST_OS:=$(shell go env GOOS) HOST_OS:=$(shell go env GOOS)
HOST_ARCH:=$(shell go env GOARCH) HOST_ARCH:=$(shell go env GOARCH)
TARGET_ARCH?=linux/amd64
VERSION=$(shell cat ${CURRENT_DIR}/VERSION) VERSION=$(shell cat ${CURRENT_DIR}/VERSION)
BUILD_DATE:=$(if $(BUILD_DATE),$(BUILD_DATE),$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')) BUILD_DATE=$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')
GIT_COMMIT:=$(if $(GIT_COMMIT),$(GIT_COMMIT),$(shell git rev-parse HEAD)) GIT_COMMIT=$(shell git rev-parse HEAD)
GIT_TAG:=$(if $(GIT_TAG),$(GIT_TAG),$(shell if [ -z "`git status --porcelain`" ]; then git describe --exact-match --tags HEAD 2>/dev/null; fi)) GIT_TAG=$(shell if [ -z "`git status --porcelain`" ]; then git describe --exact-match --tags HEAD 2>/dev/null; fi)
GIT_TREE_STATE:=$(if $(GIT_TREE_STATE),$(GIT_TREE_STATE),$(shell if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)) GIT_TREE_STATE=$(shell if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)
VOLUME_MOUNT=$(shell if test "$(go env GOOS)" = "darwin"; then echo ":delegated"; elif test selinuxenabled; then echo ":delegated"; else echo ""; fi) VOLUME_MOUNT=$(shell if test "$(go env GOOS)" = "darwin"; then echo ":delegated"; elif test selinuxenabled; then echo ":delegated"; else echo ""; fi)
KUBECTL_VERSION=$(shell go list -m k8s.io/client-go | head -n 1 | rev | cut -d' ' -f1 | rev) KUBECTL_VERSION=$(shell go list -m k8s.io/client-go | head -n 1 | rev | cut -d' ' -f1 | rev)
GOPATH?=$(shell if test -x `which go`; then go env GOPATH; else echo "$(HOME)/go"; fi) GOPATH?=$(shell if test -x `which go`; then go env GOPATH; else echo "$(HOME)/go"; fi)
GOCACHE?=$(HOME)/.cache/go-build 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_SRCDIR?=$(GOPATH)/src
DOCKER_WORKDIR?=/go/src/github.com/argoproj/argo-cd DOCKER_WORKDIR?=/go/src/github.com/argoproj/argo-cd
ARGOCD_PROCFILE?=Procfile ARGOCD_PROCFILE?=Procfile
# pointing to python 3.7 to match https://github.com/argoproj/argo-cd/blob/master/.readthedocs.yml # Strict mode has been disabled in latest versions of mkdocs-material.
MKDOCS_DOCKER_IMAGE?=python:3.7-alpine # 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?= MKDOCS_RUN_ARGS?=
# Configuration for building argocd-test-tools image # Configuration for building argocd-test-tools image
@@ -57,7 +47,7 @@ ARGOCD_E2E_DEX_PORT?=5556
ARGOCD_E2E_YARN_HOST?=localhost ARGOCD_E2E_YARN_HOST?=localhost
ARGOCD_E2E_DISABLE_AUTH?= ARGOCD_E2E_DISABLE_AUTH?=
ARGOCD_E2E_TEST_TIMEOUT?=90m ARGOCD_E2E_TEST_TIMEOUT?=45m
ARGOCD_IN_CI?=false ARGOCD_IN_CI?=false
ARGOCD_TEST_E2E?=true ARGOCD_TEST_E2E?=true
@@ -74,20 +64,13 @@ else
DOCKER_SRC_MOUNT="$(PWD):/go/src/github.com/argoproj/argo-cd$(VOLUME_MOUNT)" DOCKER_SRC_MOUNT="$(PWD):/go/src/github.com/argoproj/argo-cd$(VOLUME_MOUNT)"
endif endif
# User and group IDs to map to the test container
CONTAINER_UID=$(shell id -u)
CONTAINER_GID=$(shell id -g)
# Set SUDO to sudo to run privileged commands with sudo
SUDO?=
# Runs any command in the argocd-test-utils container in server mode # 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 # Server mode container will start with uid 0 and drop privileges during runtime
define run-in-test-server define run-in-test-server
$(SUDO) $(DOCKER) run --rm -it \ docker run --rm -it \
--name argocd-test-server \ --name argocd-test-server \
-u $(CONTAINER_UID):$(CONTAINER_GID) \ -u $(shell id -u):$(shell id -g) \
-e USER_ID=$(CONTAINER_UID) \ -e USER_ID=$(shell id -u) \
-e HOME=/home/user \ -e HOME=/home/user \
-e GOPATH=/go \ -e GOPATH=/go \
-e GOCACHE=/tmp/go-build-cache \ -e GOCACHE=/tmp/go-build-cache \
@@ -109,16 +92,15 @@ define run-in-test-server
-p ${ARGOCD_E2E_APISERVER_PORT}:8080 \ -p ${ARGOCD_E2E_APISERVER_PORT}:8080 \
-p 4000:4000 \ -p 4000:4000 \
-p 5000:5000 \ -p 5000:5000 \
$(PODMAN_ARGS) \
$(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG) \ $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG) \
bash -c "$(1)" bash -c "$(1)"
endef endef
# Runs any command in the argocd-test-utils container in client mode # Runs any command in the argocd-test-utils container in client mode
define run-in-test-client define run-in-test-client
$(SUDO) $(DOCKER) run --rm -it \ docker run --rm -it \
--name argocd-test-client \ --name argocd-test-client \
-u $(CONTAINER_UID):$(CONTAINER_GID) \ -u $(shell id -u):$(shell id -g) \
-e HOME=/home/user \ -e HOME=/home/user \
-e GOPATH=/go \ -e GOPATH=/go \
-e ARGOCD_E2E_K3S=$(ARGOCD_E2E_K3S) \ -e ARGOCD_E2E_K3S=$(ARGOCD_E2E_K3S) \
@@ -131,14 +113,13 @@ define run-in-test-client
-v ${HOME}/.kube:/home/user/.kube${VOLUME_MOUNT} \ -v ${HOME}/.kube:/home/user/.kube${VOLUME_MOUNT} \
-v /tmp:/tmp${VOLUME_MOUNT} \ -v /tmp:/tmp${VOLUME_MOUNT} \
-w ${DOCKER_WORKDIR} \ -w ${DOCKER_WORKDIR} \
$(PODMAN_ARGS) \
$(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG) \ $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG) \
bash -c "$(1)" bash -c "$(1)"
endef endef
# #
define exec-in-test-server 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) docker exec -it -u $(shell id -u):$(shell id -g) -e ARGOCD_E2E_RECORD=$(ARGOCD_E2E_RECORD) -e ARGOCD_E2E_K3S=$(ARGOCD_E2E_K3S) argocd-test-server $(1)
endef endef
PATH:=$(PATH):$(PWD)/hack PATH:=$(PATH):$(PWD)/hack
@@ -153,20 +134,12 @@ DEV_IMAGE?=false
ARGOCD_GPG_ENABLED?=true ARGOCD_GPG_ENABLED?=true
ARGOCD_E2E_APISERVER_PORT?=8080 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 += \ override LDFLAGS += \
-X ${PACKAGE}.version=${VERSION} \ -X ${PACKAGE}.version=${VERSION} \
-X ${PACKAGE}.buildDate=${BUILD_DATE} \ -X ${PACKAGE}.buildDate=${BUILD_DATE} \
-X ${PACKAGE}.gitCommit=${GIT_COMMIT} \ -X ${PACKAGE}.gitCommit=${GIT_COMMIT} \
-X ${PACKAGE}.gitTreeState=${GIT_TREE_STATE}\ -X ${PACKAGE}.gitTreeState=${GIT_TREE_STATE}\
-X ${PACKAGE}.kubectlVersion=${KUBECTL_VERSION}\ -X ${PACKAGE}.kubectlVersion=${KUBECTL_VERSION}
-X "${PACKAGE}.extraBuildInfo=${EXTRA_BUILD_INFO}"
ifeq (${STATIC_BUILD}, true) ifeq (${STATIC_BUILD}, true)
override LDFLAGS += -extldflags "-static" override LDFLAGS += -extldflags "-static"
@@ -192,25 +165,29 @@ endif
.PHONY: all .PHONY: all
all: cli image all: cli image
.PHONY: mockgen # We have some legacy requirements for being checked out within $GOPATH.
mockgen: # The ensure-gopath target can be used as dependency to ensure we are running
./hack/generate-mock.sh # 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 .PHONY: gogen
gogen: gogen: ensure-gopath
export GO111MODULE=off export GO111MODULE=off
go generate ./... go generate ./util/argo/...
.PHONY: protogen .PHONY: protogen
protogen: mod-vendor-local protogen-fast protogen: ensure-gopath mod-vendor-local
.PHONY: protogen-fast
protogen-fast:
export GO111MODULE=off export GO111MODULE=off
./hack/generate-proto.sh ./hack/generate-proto.sh
.PHONY: openapigen .PHONY: openapigen
openapigen: openapigen: ensure-gopath
export GO111MODULE=off export GO111MODULE=off
./hack/update-openapi.sh ./hack/update-openapi.sh
@@ -225,25 +202,19 @@ notification-docs:
.PHONY: clientgen .PHONY: clientgen
clientgen: clientgen: ensure-gopath
export GO111MODULE=off export GO111MODULE=off
./hack/update-codegen.sh ./hack/update-codegen.sh
.PHONY: clidocsgen .PHONY: clidocsgen
clidocsgen: clidocsgen: ensure-gopath
go run tools/cmd-docs/main.go go run tools/cmd-docs/main.go
.PHONY: actionsdocsgen
actionsdocsgen:
hack/generate-actions-list.sh
.PHONY: codegen-local .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 notification-docs notification-catalog gogen protogen clientgen openapigen clidocsgen manifests-local
rm -rf vendor/ 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 .PHONY: codegen
codegen: test-tools-image codegen: test-tools-image
$(call run-in-test-client,make codegen-local) $(call run-in-test-client,make codegen-local)
@@ -254,11 +225,11 @@ cli: test-tools-image
.PHONY: cli-local .PHONY: cli-local
cli-local: clean-debug 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 go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${CLI_NAME} ./cmd
.PHONY: gen-resources-cli-local .PHONY: gen-resources-cli-local
gen-resources-cli-local: clean-debug 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 go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${GEN_RESOURCES_CLI_NAME} ./hack/gen-resources/cmd
.PHONY: release-cli .PHONY: release-cli
release-cli: clean-debug build-ui release-cli: clean-debug build-ui
@@ -273,8 +244,8 @@ release-cli: clean-debug build-ui
.PHONY: test-tools-image .PHONY: test-tools-image
test-tools-image: test-tools-image:
ifndef SKIP_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 . docker build --build-arg UID=$(shell id -u) -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) docker tag $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE) $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG)
endif endif
.PHONY: manifests-local .PHONY: manifests-local
@@ -288,25 +259,25 @@ manifests: test-tools-image
# consolidated binary for cli, util, server, repo-server, controller # consolidated binary for cli, util, server, repo-server, controller
.PHONY: argocd-all .PHONY: argocd-all
argocd-all: clean-debug 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} go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${BIN_NAME} ./cmd
.PHONY: server .PHONY: server
server: clean-debug 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 go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-server ./cmd
.PHONY: repo-server .PHONY: repo-server
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 go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-repo-server ./cmd
.PHONY: controller .PHONY: controller
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 go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-application-controller ./cmd
.PHONY: build-ui .PHONY: build-ui
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 --target argocd-ui .
find ./ui/dist -type f -not -name gitkeep -delete 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 .PHONY: image
ifeq ($(DEV_IMAGE), true) ifeq ($(DEV_IMAGE), true)
@@ -315,29 +286,29 @@ ifeq ($(DEV_IMAGE), true)
# the dist directory is under .dockerignore. # the dist directory is under .dockerignore.
IMAGE_TAG="dev-$(shell git describe --always --dirty)" IMAGE_TAG="dev-$(shell git describe --always --dirty)"
image: build-ui image: build-ui
DOCKER_BUILDKIT=1 $(DOCKER) build --platform=$(TARGET_ARCH) -t argocd-base --target argocd-base . DOCKER_BUILDKIT=1 docker build --platform=linux/amd64 -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 CGO_ENABLED=0 GOOS=linux GOARCH=amd64 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-server
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-application-controller 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-repo-server
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-cmp-server ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-cmp-server
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-dex ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-dex
cp Dockerfile.dev dist 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=linux/amd64 -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) -f dist/Dockerfile.dev dist
else else
image: 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) .
endif 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 .PHONY: armimage
armimage: armimage:
$(DOCKER) build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG)-arm . docker build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG)-arm .
.PHONY: builder-image .PHONY: builder-image
builder-image: builder-image:
$(DOCKER) build -t $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) --target builder . 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 @if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) ; fi
.PHONY: mod-download .PHONY: mod-download
mod-download: test-tools-image mod-download: test-tools-image
@@ -355,7 +326,7 @@ mod-vendor: test-tools-image
mod-vendor-local: mod-download-local mod-vendor-local: mod-download-local
go mod vendor go mod vendor
# Deprecated - replace by install-tools-local # Deprecated - replace by install-local-tools
.PHONY: install-lint-tools .PHONY: install-lint-tools
install-lint-tools: install-lint-tools:
./hack/install.sh lint-tools ./hack/install.sh lint-tools
@@ -371,7 +342,7 @@ lint-local:
golangci-lint --version golangci-lint --version
# NOTE: If you get a "Killed" OOM message, try reducing the value of GOGC # 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 # See https://github.com/golangci/golangci-lint#memory-usage-of-golangci-lint
GOGC=$(ARGOCD_LINT_GOGC) GOMAXPROCS=2 golangci-lint run --fix --verbose GOGC=$(ARGOCD_LINT_GOGC) GOMAXPROCS=2 golangci-lint run --fix --verbose --timeout 3000s
.PHONY: lint-ui .PHONY: lint-ui
lint-ui: test-tools-image lint-ui: test-tools-image
@@ -390,7 +361,7 @@ build: test-tools-image
# Build all Go code (local version) # Build all Go code (local version)
.PHONY: build-local .PHONY: build-local
build-local: build-local:
GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v `go list ./... | grep -v 'resource_customizations\|test/e2e'` go build -v `go list ./... | grep -v 'resource_customizations\|test/e2e'`
# Run all unit tests # Run all unit tests
# #
@@ -405,9 +376,9 @@ test: test-tools-image
.PHONY: test-local .PHONY: test-local
test-local: test-local:
if test "$(TEST_MODULE)" = ""; then \ if test "$(TEST_MODULE)" = ""; then \
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES=`go list ./... | grep -v 'test/e2e'` ./hack/test.sh -args -test.gocoverdir="$(PWD)/test-results"; \ ./hack/test.sh -coverprofile=coverage.out `go list ./... | grep -v 'test/e2e'`; \
else \ else \
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES="$(TEST_MODULE)" ./hack/test.sh -args -test.gocoverdir="$(PWD)/test-results" "$(TEST_MODULE)"; \ ./hack/test.sh -coverprofile=coverage.out "$(TEST_MODULE)"; \
fi fi
.PHONY: test-race .PHONY: test-race
@@ -419,9 +390,9 @@ test-race: test-tools-image
.PHONY: test-race-local .PHONY: test-race-local
test-race-local: test-race-local:
if test "$(TEST_MODULE)" = ""; then \ if test "$(TEST_MODULE)" = ""; then \
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES=`go list ./... | grep -v 'test/e2e'` ./hack/test.sh -race -args -test.gocoverdir="$(PWD)/test-results"; \ ./hack/test.sh -race -coverprofile=coverage.out `go list ./... | grep -v 'test/e2e'`; \
else \ else \
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES="$(TEST_MODULE)" ./hack/test.sh -race -args -test.gocoverdir="$(PWD)/test-results"; \ ./hack/test.sh -race -coverprofile=coverage.out "$(TEST_MODULE)"; \
fi fi
# Run the E2E test suite. E2E test servers (see start-e2e target) must be # Run the E2E test suite. E2E test servers (see start-e2e target) must be
@@ -435,7 +406,7 @@ test-e2e:
test-e2e-local: cli-local 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 # NO_PROXY ensures all tests don't go out through a proxy if one is configured on the test system
export GO111MODULE=off export GO111MODULE=off
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 -args -test.gocoverdir="$(PWD)/test-results" ARGOCD_E2E_RECORD=${ARGOCD_E2E_RECORD} ARGOCD_GPG_ENABLED=true NO_PROXY=* ./hack/test.sh -timeout $(ARGOCD_E2E_TEST_TIMEOUT) -v ./test/e2e
# Spawns a shell in the test server container for debugging purposes # Spawns a shell in the test server container for debugging purposes
debug-test-server: test-tools-image debug-test-server: test-tools-image
@@ -448,7 +419,7 @@ debug-test-client: test-tools-image
# Starts e2e server in a container # Starts e2e server in a container
.PHONY: start-e2e .PHONY: start-e2e
start-e2e: test-tools-image start-e2e: test-tools-image
$(DOCKER) version docker version
mkdir -p ${GOCACHE} mkdir -p ${GOCACHE}
$(call run-in-test-server,make ARGOCD_PROCFILE=test/container/Procfile start-e2e-local) $(call run-in-test-server,make ARGOCD_PROCFILE=test/container/Procfile start-e2e-local)
@@ -457,7 +428,6 @@ start-e2e: test-tools-image
start-e2e-local: mod-vendor-local dep-ui-local cli-local start-e2e-local: mod-vendor-local dep-ui-local cli-local
kubectl create ns argocd-e2e || true kubectl create ns argocd-e2e || true
kubectl create ns argocd-e2e-external || true kubectl create ns argocd-e2e-external || true
kubectl create ns argocd-e2e-external-2 || true
kubectl config set-context --current --namespace=argocd-e2e kubectl config set-context --current --namespace=argocd-e2e
kustomize build test/manifests/base | kubectl apply -f - kustomize build test/manifests/base | kubectl apply -f -
kubectl apply -f https://raw.githubusercontent.com/open-cluster-management/api/a6845f2ebcb186ec26b832f60c988537a58f3859/cluster/v1alpha1/0000_04_clusters.open-cluster-management.io_placementdecisions.crd.yaml kubectl apply -f https://raw.githubusercontent.com/open-cluster-management/api/a6845f2ebcb186ec26b832f60c988537a58f3859/cluster/v1alpha1/0000_04_clusters.open-cluster-management.io_placementdecisions.crd.yaml
@@ -466,13 +436,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/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/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 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 # set paths for locally managed ssh known hosts and tls certs data
ARGOCD_SSH_DATA_PATH=/tmp/argo-e2e/app/config/ssh \ ARGOCD_SSH_DATA_PATH=/tmp/argo-e2e/app/config/ssh \
ARGOCD_TLS_DATA_PATH=/tmp/argo-e2e/app/config/tls \ ARGOCD_TLS_DATA_PATH=/tmp/argo-e2e/app/config/tls \
@@ -485,13 +448,9 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
ARGOCD_ZJWT_FEATURE_FLAG=always \ ARGOCD_ZJWT_FEATURE_FLAG=always \
ARGOCD_IN_CI=$(ARGOCD_IN_CI) \ ARGOCD_IN_CI=$(ARGOCD_IN_CI) \
BIN_MODE=$(ARGOCD_BIN_MODE) \ BIN_MODE=$(ARGOCD_BIN_MODE) \
ARGOCD_APPLICATION_NAMESPACES=argocd-e2e-external,argocd-e2e-external-2 \ ARGOCD_APPLICATION_NAMESPACES=argocd-e2e-external \
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_E2E_TEST=true \
goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START} 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 # Cleans VSCode debug.test files from sub-dirs to prevent them from being included in by golang embed
.PHONY: clean-debug .PHONY: clean-debug
@@ -504,7 +463,7 @@ clean: clean-debug
.PHONY: start .PHONY: start
start: test-tools-image 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}) $(call run-in-test-server,make ARGOCD_PROCFILE=test/container/Procfile start-local ARGOCD_START=${ARGOCD_START})
# Starts a local instance of ArgoCD # Starts a local instance of ArgoCD
@@ -517,11 +476,9 @@ start-local: mod-vendor-local dep-ui-local cli-local
mkdir -p /tmp/argocd-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/keys && chmod 0700 /tmp/argocd-local/gpg/keys
mkdir -p /tmp/argocd-local/gpg/source 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_ZJWT_FEATURE_FLAG=always \
ARGOCD_IN_CI=false \ ARGOCD_IN_CI=false \
ARGOCD_GPG_ENABLED=$(ARGOCD_GPG_ENABLED) \ ARGOCD_GPG_ENABLED=$(ARGOCD_GPG_ENABLED) \
BIN_MODE=$(ARGOCD_BIN_MODE) \
ARGOCD_E2E_TEST=false \ ARGOCD_E2E_TEST=false \
ARGOCD_APPLICATION_NAMESPACES=$(ARGOCD_APPLICATION_NAMESPACES) \ ARGOCD_APPLICATION_NAMESPACES=$(ARGOCD_APPLICATION_NAMESPACES) \
goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START} goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START}
@@ -555,7 +512,7 @@ build-docs-local:
.PHONY: build-docs .PHONY: build-docs
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 .PHONY: serve-docs-local
serve-docs-local: serve-docs-local:
@@ -563,7 +520,8 @@ serve-docs-local:
.PHONY: serve-docs .PHONY: serve-docs
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 # Verify that kubectl can connect to your K8s cluster from Docker
.PHONY: verify-kube-connect .PHONY: verify-kube-connect
@@ -586,8 +544,7 @@ install-tools-local: install-test-tools-local install-codegen-tools-local instal
.PHONY: install-test-tools-local .PHONY: install-test-tools-local
install-test-tools-local: install-test-tools-local:
./hack/install.sh kustomize ./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) # Installs all tools required for running codegen (Linux packages)
.PHONY: install-codegen-tools-local .PHONY: install-codegen-tools-local
@@ -615,7 +572,7 @@ list:
.PHONY: applicationset-controller .PHONY: applicationset-controller
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 CGO_ENABLED=0 go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-applicationset-controller ./cmd
.PHONY: checksums .PHONY: checksums
checksums: checksums:
@@ -632,55 +589,3 @@ snyk-non-container-tests:
.PHONY: snyk-report .PHONY: snyk-report
snyk-report: snyk-report:
./hack/snyk-report.sh $(target_branch) ./hack/snyk-report.sh $(target_branch)
.PHONY: help
help:
@echo 'Note: Generally an item w/ (-local) will run inside docker unless you use the -local variant'
@echo
@echo 'Common targets'
@echo
@echo 'all -- make cli and image'
@echo
@echo 'components:'
@echo ' applicationset-controller -- applicationset controller'
@echo ' cli(-local) -- argocd cli program'
@echo ' controller -- controller (orchestrator)'
@echo ' repo-server -- repo server (manage repository instances)'
@echo ' server -- argocd web application'
@echo
@echo 'build:'
@echo ' image -- make image of the following items'
@echo ' build(-local) -- compile go'
@echo ' build-docs(-local) -- build docs'
@echo ' build-ui -- compile typescript'
@echo
@echo 'run:'
@echo ' run -- run the components locally'
@echo ' serve-docs(-local) -- expose the documents for viewing in a browser'
@echo
@echo 'release:'
@echo ' release-cli'
@echo ' release-precheck'
@echo ' checksums'
@echo
@echo 'docs:'
@echo ' build-docs(-local)'
@echo ' serve-docs(-local)'
@echo ' notification-docs'
@echo ' clidocsgen'
@echo
@echo 'testing:'
@echo ' test(-local)'
@echo ' start-e2e(-local)'
@echo ' test-e2e(-local)'
@echo ' test-race(-local)'
@echo
@echo 'debug:'
@echo ' list -- list all make targets'
@echo ' install-tools-local -- install all the tools below'
@echo ' install-lint-tools(-local)'
@echo
@echo 'codegen:'
@echo ' codegen(-local) -- if using -local, run the following targets first'
@echo ' install-codegen-tools-local -- run this to install the codegen tools'
@echo ' install-go-tools-local -- run this to install go libraries for codegen'

5
OWNERS
View File

@@ -1,12 +1,10 @@
owners: owners:
- alexmt - alexmt
- crenshaw-dev
- jessesuen - jessesuen
approvers: approvers:
- alexec - alexec
- alexmt - alexmt
- gdsoumya
- jannfis - jannfis
- jessesuen - jessesuen
- jgwest - jgwest
@@ -29,6 +27,3 @@ reviewers:
- wanghong230 - wanghong230
- ciiay - ciiay
- saumeya - saumeya
- zachaller
- 34fathombelow
- alexef

View File

@@ -1,13 +1,12 @@
controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/app-controller} HOSTNAME=testappcontroller-1 FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''} --server-side-diff-enabled=${ARGOCD_APPLICATION_CONTROLLER_SERVER_SIDE_DIFF:-'false'}" controller: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''}"
api-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "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:-''}" 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" dex: sh -c "ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/v2/cmd gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml 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 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} redis:$(grep "image: redis" manifests/base/redis/argocd-redis-deployment.yaml | cut -d':' -f3) --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; fi"
repo-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/repo-server} FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-repo-server ARGOCD_GPG_ENABLED=${ARGOCD_GPG_ENABLED:-false} $COMMAND --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --otlp-address=${ARGOCD_OTLP_ADDRESS}" 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}" 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' ui: sh -c 'cd ui && ${ARGOCD_E2E_YARN_CMD:-yarn} start'
git-server: test/fixture/testrepos/start-git.sh git-server: test/fixture/testrepos/start-git.sh
helm-registry: test/fixture/testrepos/start-helm-registry.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} 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}" applicationset-controller: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_ASK_PASS_SOCK=/tmp/applicationset-ask-pass.sock ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-applicationset-controller $COMMAND --loglevel debug --metrics-addr localhost:12345 --probe-addr localhost:12346 --argocd-repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
notification: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "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'}" notification: [ "$BIN_MODE" == 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_BINARY_NAME=argocd-notifications $COMMAND --loglevel debug"

View File

@@ -1,18 +1,4 @@
**Releases:** [![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) [![slack](https://img.shields.io/badge/slack-argoproj-brightgreen.svg?logo=slack)](https://argoproj.github.io/community/join-slack) [![codecov](https://codecov.io/gh/argoproj/argo-cd/branch/master/graph/badge.svg)](https://codecov.io/gh/argoproj/argo-cd) [![Release Version](https://img.shields.io/github/v/release/argoproj/argo-cd?label=argo-cd)](https://github.com/argoproj/argo-cd/releases/latest) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4486/badge)](https://bestpractices.coreinfrastructure.org/projects/4486) [![Twitter Follow](https://img.shields.io/twitter/follow/argoproj?style=social)](https://twitter.com/argoproj)
[![Release Version](https://img.shields.io/github/v/release/argoproj/argo-cd?label=argo-cd)](https://github.com/argoproj/argo-cd/releases/latest)
[![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/argo-cd)](https://artifacthub.io/packages/helm/argo/argo-cd)
[![SLSA 3](https://slsa.dev/images/gh-badge-level3.svg)](https://slsa.dev)
**Code:**
[![Integration tests](https://github.com/argoproj/argo-cd/workflows/Integration%20tests/badge.svg?branch=master)](https://github.com/argoproj/argo-cd/actions?query=workflow%3A%22Integration+tests%22)
[![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)
**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 # Argo CD - Declarative Continuous Delivery for Kubernetes
@@ -56,7 +42,7 @@ Participation in the Argo CD project is governed by the [CNCF Code of Conduct](h
### Blogs and Presentations ### Blogs and Presentations
1. [Awesome-Argo: A Curated List of Awesome Projects and Resources Related to Argo](https://github.com/terrytangyuan/awesome-argo) 1. [Awesome-Argo: A Curated List of Awesome Projects and Resources Related to Argo](https://github.com/terrytangyuan/awesome-argo)
1. [Unveil the Secret Ingredients of Continuous Delivery at Enterprise Scale with Argo CD](https://akuity.io/blog/secret-ingredients-of-continuous-delivery-at-enterprise-scale-with-argocd/) 1. [Unveil the Secret Ingredients of Continuous Delivery at Enterprise Scale with Argo CD](https://blog.akuity.io/unveil-the-secret-ingredients-of-continuous-delivery-at-enterprise-scale-with-argo-cd-7c5b4057ee49)
1. [GitOps Without Pipelines With ArgoCD Image Updater](https://youtu.be/avPUQin9kzU) 1. [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. [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) 1. [How to Apply GitOps to Everything - Combining Argo CD and Crossplane](https://youtu.be/yrj4lmScKHQ)
@@ -82,8 +68,7 @@ Participation in the Argo CD project is governed by the [CNCF Code of Conduct](h
1. [Applied GitOps with Argo CD](https://thenewstack.io/applied-gitops-with-argocd/) 1. [Applied GitOps with Argo CD](https://thenewstack.io/applied-gitops-with-argocd/)
1. [Solving configuration drift using GitOps with Argo CD](https://www.cncf.io/blog/2020/12/17/solving-configuration-drift-using-gitops-with-argo-cd/) 1. [Solving configuration drift using GitOps with Argo CD](https://www.cncf.io/blog/2020/12/17/solving-configuration-drift-using-gitops-with-argo-cd/)
1. [Decentralized GitOps over environments](https://blogs.sap.com/2021/05/06/decentralized-gitops-over-environments/) 1. [Decentralized GitOps over environments](https://blogs.sap.com/2021/05/06/decentralized-gitops-over-environments/)
1. [How GitOps and Operators mark the rise of Infrastructure-As-Software](https://paytmlabs.com/blog/2021/10/how-to-improve-operational-work-with-operators-and-gitops/)
1. [Getting Started with ArgoCD for GitOps Deployments](https://youtu.be/AvLuplh1skA) 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. [Using Argo CD & Datree for Stable Kubernetes CI/CD Deployments](https://youtu.be/17894DTru2Y)
1. [How to create Argo CD Applications Automatically using ApplicationSet? "Automation of GitOps"](https://amralaayassen.medium.com/how-to-create-argocd-applications-automatically-using-applicationset-automation-of-the-gitops-59455eaf4f72)
1. [Progressive Delivery with Service Mesh Argo Rollouts with Istio](https://www.cncf.io/blog/2022/12/16/progressive-delivery-with-service-mesh-argo-rollouts-with-istio/)

View File

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

View File

@@ -1,6 +1,6 @@
# Security Policy for Argo CD # Security Policy for Argo CD
Version: **v1.5 (2023-03-06)** Version: **v1.4 (2022-01-23)**
## Preface ## Preface
@@ -35,11 +35,13 @@ impact on Argo CD before opening an issue at least roughly.
## Supported Versions ## Supported Versions
We currently support the last 3 minor versions of Argo CD with security and bug fixes. We currently support the most recent release (`N`, e.g. `1.8`) and the release
previous to the most recent one (`N-1`, e.g. `1.7`). With the release of
`N+1`, `N-1` drops out of support and `N` becomes `N-1`.
We regularly perform patch releases (e.g. `1.8.5` and `1.7.12`) for the We regularly perform patch releases (e.g. `1.8.5` and `1.7.12`) for the
supported versions, which will contain fixes for security vulnerabilities and supported versions, which will contain fixes for security vulnerabilities and
important bugs. Prior releases might receive critical security fixes on best important bugs. Prior releases might receive critical security fixes on a best
effort basis, however, it cannot be guaranteed that security fixes get effort basis, however, it cannot be guaranteed that security fixes get
back-ported to these unsupported versions. back-ported to these unsupported versions.
@@ -50,7 +52,7 @@ of releasing it within a patch branch for the currently supported releases.
## Reporting a Vulnerability ## Reporting a Vulnerability
If you find a security related bug in Argo CD, we kindly ask you for responsible If you find a security related bug in ArgoCD, we kindly ask you for responsible
disclosure and for giving us appropriate time to react, analyze and develop a disclosure and for giving us appropriate time to react, analyze and develop a
fix to mitigate the found security vulnerability. fix to mitigate the found security vulnerability.
@@ -59,28 +61,13 @@ and disclosure with you. Sometimes, it might take a little longer for us to
react (e.g. out of office conditions), so please bear with us in these cases. react (e.g. out of office conditions), so please bear with us in these cases.
We will publish security advisories using the We will publish security advisories using the
[GitHub Security Advisories](https://github.com/argoproj/argo-cd/security/advisories) [Git Hub Security Advisories](https://github.com/argoproj/argo-cd/security/advisories)
feature to keep our community well-informed, and will credit you for your feature to keep our community well informed, and will credit you for your
findings (unless you prefer to stay anonymous, of course). findings (unless you prefer to stay anonymous, of course).
There are two ways to report a vulnerability to the Argo CD team: Please report vulnerabilities by e-mail to the following address:
* By opening a draft GitHub security advisory: https://github.com/argoproj/argo-cd/security/advisories/new * cncf-argo-security@lists.cncf.io
* By e-mail to the following address: cncf-argo-security@lists.cncf.io
## Internet Bug Bounty collaboration
We're happy to announce that the Argo project is collaborating with the great
folks over at
[Hacker One](https://hackerone.com/) and their
[Internet Bug Bounty program](https://hackerone.com/ibb)
to reward the awesome people who find security vulnerabilities in the four
main Argo projects (CD, Events, Rollouts and Workflows) and then work with
us to fix and disclose them in a responsible manner.
If you report a vulnerability to us as outlined in this security policy, we
will work together with you to find out whether your finding is eligible for
claiming a bounty, and also on how to claim it.
## Securing your Argo CD Instance ## Securing your Argo CD Instance

View File

@@ -1,7 +1,7 @@
# Defined below are the security contacts for this repo. # Defined below are the security contacts for this repo.
# #
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE # DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
# INSTRUCTIONS AT https://github.com/argoproj/argo-cd/security/policy # INSTRUCTIONS AT https://argo-cd.readthedocs.io/en/latest/security_considerations/#reporting-vulnerabilities
alexmt alexmt
edlee2121 edlee2121

160
USERS.md
View File

@@ -7,97 +7,54 @@ Currently, the following organizations are **officially** using Argo CD:
1. [127Labs](https://127labs.com/) 1. [127Labs](https://127labs.com/)
1. [3Rein](https://www.3rein.com/) 1. [3Rein](https://www.3rein.com/)
1. [4data](https://4data.ch/)
1. [7shifts](https://www.7shifts.com/) 1. [7shifts](https://www.7shifts.com/)
1. [Adevinta](https://www.adevinta.com/) 1. [Adevinta](https://www.adevinta.com/)
1. [Adfinis](https://adfinis.com) 1. [Adfinis](https://adfinis.com)
1. [Adobe](https://www.adobe.com/)
1. [Adventure](https://jp.adventurekk.com/) 1. [Adventure](https://jp.adventurekk.com/)
1. [Adyen](https://www.adyen.com)
1. [AirQo](https://airqo.net/)
1. [Akuity](https://akuity.io/) 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. [Alibaba Group](https://www.alibabagroup.com/)
1. [Allianz Direct](https://www.allianzdirect.de/) 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. [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. [ANSTO - Australian Synchrotron](https://www.synchrotron.org.au/)
1. [Ant Group](https://www.antgroup.com/) 1. [Ant Group](https://www.antgroup.com/)
1. [AppDirect](https://www.appdirect.com) 1. [AppDirect](https://www.appdirect.com)
1. [Arctiq Inc.](https://www.arctiq.ca) 1. [Arctiq Inc.](https://www.arctiq.ca)
2. [Arturia](https://www.arturia.com)
1. [ARZ Allgemeines Rechenzentrum GmbH](https://www.arz.at/) 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. [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. [Baloise](https://www.baloise.com)
1. [BCDevExchange DevOps Platform](https://bcdevexchange.org/DevOpsPlatform) 1. [BCDevExchange DevOps Platform](https://bcdevexchange.org/DevOpsPlatform)
1. [Beat](https://thebeat.co/en/) 1. [Beat](https://thebeat.co/en/)
1. [Beez Innovation Labs](https://www.beezlabs.com/) 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. [Beleza Na Web](https://www.belezanaweb.com.br/)
1. [Believable Bots](https://believablebots.io)
1. [BigPanda](https://bigpanda.io) 1. [BigPanda](https://bigpanda.io)
1. [BioBox Analytics](https://biobox.io) 1. [BioBox Analytics](https://biobox.io)
1. [BMW Group](https://www.bmwgroup.com/) 1. [BMW Group](https://www.bmwgroup.com/)
1. [Boozt](https://www.booztgroup.com/) 1. [Boozt](https://www.booztgroup.com/)
1. [Bosch](https://www.bosch.com/)
1. [Boticario](https://www.boticario.com.br/) 1. [Boticario](https://www.boticario.com.br/)
1. [Broker Consulting, a.s.](https://www.bcas.cz/en/)
1. [Bulder Bank](https://bulderbank.no) 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. [Camptocamp](https://camptocamp.com)
1. [Candis](https://www.candis.io)
1. [Capital One](https://www.capitalone.com) 1. [Capital One](https://www.capitalone.com)
1. [CARFAX Europe](https://www.carfax.eu)
1. [CARFAX](https://www.carfax.com) 1. [CARFAX](https://www.carfax.com)
1. [Carrefour Group](https://www.carrefour.com)
1. [Casavo](https://casavo.com) 1. [Casavo](https://casavo.com)
1. [Celonis](https://www.celonis.com/) 1. [Celonis](https://www.celonis.com/)
1. [CERN](https://home.cern/) 1. [CERN](https://home.cern/)
1. [Chainnodes](https://chainnodes.org)
1. [Chargetrip](https://chargetrip.com) 1. [Chargetrip](https://chargetrip.com)
1. [Chime](https://www.chime.com) 1. [Chime](https://www.chime.com)
1. [Cisco ET&I](https://eti.cisco.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. [Cobalt](https://www.cobalt.io/)
1. [Codefresh](https://www.codefresh.io/) 1. [Codefresh](https://www.codefresh.io/)
1. [Codility](https://www.codility.com/) 1. [Codility](https://www.codility.com/)
1. [Cognizant](https://www.cognizant.com/)
1. [Commonbond](https://commonbond.co/) 1. [Commonbond](https://commonbond.co/)
1. [Compatio.AI](https://compatio.ai/)
1. [Contlo](https://contlo.com/)
1. [Coralogix](https://coralogix.com/) 1. [Coralogix](https://coralogix.com/)
1. [Crédit Agricole CIB](https://www.ca-cib.com)
1. [CROZ d.o.o.](https://croz.net/) 1. [CROZ d.o.o.](https://croz.net/)
1. [Crédit Agricole CIB](https://www.ca-cib.com)
1. [CyberAgent](https://www.cyberagent.co.jp/en/) 1. [CyberAgent](https://www.cyberagent.co.jp/en/)
1. [Cybozu](https://cybozu-global.com) 1. [Cybozu](https://cybozu-global.com)
1. [D2iQ](https://www.d2iq.com) 1. [D2iQ](https://www.d2iq.com)
1. [DaoCloud](https://daocloud.io/)
1. [Datarisk](https://www.datarisk.io/) 1. [Datarisk](https://www.datarisk.io/)
1. [Daydream](https://daydream.ing)
1. [Deloitte](https://www.deloitte.com/) 1. [Deloitte](https://www.deloitte.com/)
1. [Deutsche Telekom AG](https://telekom.com)
1. [Devopsi - Poland Software/DevOps Consulting](https://devopsi.pl/) 1. [Devopsi - Poland Software/DevOps Consulting](https://devopsi.pl/)
1. [Devtron Labs](https://github.com/devtron-labs/devtron) 1. [Devtron Labs](https://github.com/devtron-labs/devtron)
1. [DigitalOcean](https://www.digitalocean.com)
1. [Divistant](https://divistant.com)
1. [Dott](https://ridedott.com)
1. [Doximity](https://www.doximity.com/)
1. [EDF Renewables](https://www.edf-re.com/) 1. [EDF Renewables](https://www.edf-re.com/)
1. [edX](https://edx.org) 1. [edX](https://edx.org)
1. [Elastic](https://elastic.co/) 1. [Elastic](https://elastic.co/)
@@ -107,93 +64,59 @@ Currently, the following organizations are **officially** using Argo CD:
1. [END.](https://www.endclothing.com/) 1. [END.](https://www.endclothing.com/)
1. [Energisme](https://energisme.com/) 1. [Energisme](https://energisme.com/)
1. [enigmo](https://enigmo.co.jp/) 1. [enigmo](https://enigmo.co.jp/)
1. [Envoy](https://envoy.com/)
1. [Factorial](https://factorialhr.com/)
1. [Farfetch](https://www.farfetch.com)
1. [Faro](https://www.faro.com/) 1. [Faro](https://www.faro.com/)
1. [Fave](https://myfave.com) 1. [Fave](https://myfave.com)
1. [Flexport](https://www.flexport.com/)
1. [Flip](https://flip.id) 1. [Flip](https://flip.id)
1. [Fly Security](https://www.flysecurity.com.br/)
1. [Fonoa](https://www.fonoa.com/) 1. [Fonoa](https://www.fonoa.com/)
1. [Fortra](https://www.fortra.com)
1. [freee](https://corp.freee.co.jp/en/company/) 1. [freee](https://corp.freee.co.jp/en/company/)
1. [Freshop, Inc](https://www.freshop.com/) 1. [Freshop, Inc](https://www.freshop.com/)
1. [Future PLC](https://www.futureplc.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 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. [Garner](https://www.garnercorp.com)
1. [Generali Deutschland AG](https://www.generali.de/) 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. [Gitpod](https://www.gitpod.io)
1. [Gllue](https://gllue.com) 1. [Gllue](https://gllue.com)
1. [gloat](https://gloat.com/) 1. [gloat](https://gloat.com/)
1. [GLOBIS](https://globis.com) 1. [GLOBIS](https://globis.com)
1. [Glovo](https://www.glovoapp.com) 1. [Glovo](https://www.glovoapp.com)
1. [GlueOps](https://glueops.dev)
1. [GMETRI](https://gmetri.com/) 1. [GMETRI](https://gmetri.com/)
1. [Gojek](https://www.gojek.io/) 1. [Gojek](https://www.gojek.io/)
1. [GoTo Financial](https://gotofinancial.com/)
1. [GoTo](https://www.goto.com/)
1. [Greenpass](https://www.greenpass.com.br/) 1. [Greenpass](https://www.greenpass.com.br/)
1. [Gridfuse](https://gridfuse.com/) 1. [Gridfuse](https://gridfuse.com/)
1. [Groww](https://groww.in)
1. [Grupo MasMovil](https://grupomasmovil.com/en/) 1. [Grupo MasMovil](https://grupomasmovil.com/en/)
1. [Handelsbanken](https://www.handelsbanken.se) 1. [Handelsbanken](https://www.handelsbanken.se)
1. [Hazelcast](https://hazelcast.com/)
1. [Healy](https://www.healyworld.net) 1. [Healy](https://www.healyworld.net)
1. [Helio](https://helio.exchange) 1. [Helio](https://helio.exchange)
1. [Hetki](https://hetki.ai) 1. [Hetki](https://hetki.ai)
1. [hipages](https://hipages.com.au/) 1. [hipages](https://hipages.com.au/)
1. [Hiya](https://hiya.com) 1. [Hiya](https://hiya.com)
1. [Honestbank](https://honestbank.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. [IBM](https://www.ibm.com/)
1. [Ibotta](https://home.ibotta.com) 1. [Ibotta](https://home.ibotta.com)
1. [IFS](https://www.ifs.com)
1. [IITS-Consulting](https://iits-consulting.de) 1. [IITS-Consulting](https://iits-consulting.de)
1. [IllumiDesk](https://www.illumidesk.com)
1. [imaware](https://imaware.health) 1. [imaware](https://imaware.health)
1. [Indeed](https://indeed.com)
1. [Index Exchange](https://www.indexexchange.com/) 1. [Index Exchange](https://www.indexexchange.com/)
1. [Info Support](https://www.infosupport.com/) 1. [Info Support](https://www.infosupport.com/)
1. [InsideBoard](https://www.insideboard.com) 1. [InsideBoard](https://www.insideboard.com)
1. [Instruqt](https://www.instruqt.com)
1. [Intuit](https://www.intuit.com/) 1. [Intuit](https://www.intuit.com/)
1. [Jellysmack](https://www.jellysmack.com)
1. [Joblift](https://joblift.com/) 1. [Joblift](https://joblift.com/)
1. [JovianX](https://www.jovianx.com/) 1. [JovianX](https://www.jovianx.com/)
1. [Kaltura](https://corp.kaltura.com/) 1. [Kaltura](https://corp.kaltura.com/)
1. [Kandji](https://www.kandji.io/) 1. [Kandji](https://www.kandji.io/)
1. [Karrot](https://www.daangn.com/)
1. [KarrotPay](https://www.daangnpay.com/) 1. [KarrotPay](https://www.daangnpay.com/)
1. [Karrot](https://www.daangn.com/)
1. [Kasa](https://kasa.co.kr/) 1. [Kasa](https://kasa.co.kr/)
1. [Kave Home](https://kavehome.com)
1. [Keeeb](https://www.keeeb.com/) 1. [Keeeb](https://www.keeeb.com/)
1. [KelkooGroup](https://www.kelkoogroup.com)
1. [Keptn](https://keptn.sh) 1. [Keptn](https://keptn.sh)
1. [Kinguin](https://www.kinguin.net/) 1. [Kinguin](https://www.kinguin.net/)
1. [KintoHub](https://www.kintohub.com/) 1. [KintoHub](https://www.kintohub.com/)
1. [KompiTech GmbH](https://www.kompitech.com/) 1. [KompiTech GmbH](https://www.kompitech.com/)
1. [Kong Inc.](https://konghq.com/)
1. [KPMG](https://kpmg.com/uk)
1. [KubeSphere](https://github.com/kubesphere) 1. [KubeSphere](https://github.com/kubesphere)
1. [Kurly](https://www.kurly.com/) 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. [LexisNexis](https://www.lexisnexis.com/)
1. [Lian Chu Securities](https://lczq.com) 1. [Lian Chu Securities](https://lczq.com)
1. [Liatrio](https://www.liatrio.com)
1. [Lightricks](https://www.lightricks.com/) 1. [Lightricks](https://www.lightricks.com/)
1. [LINE](https://linecorp.com/en/) 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. [Lytt](https://www.lytt.co/)
1. [Magic Leap](https://www.magicleap.com/) 1. [Magic Leap](https://www.magicleap.com/)
1. [Majid Al Futtaim](https://www.majidalfuttaim.com/) 1. [Majid Al Futtaim](https://www.majidalfuttaim.com/)
@@ -204,119 +127,66 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Max Kelsen](https://www.maxkelsen.com/) 1. [Max Kelsen](https://www.maxkelsen.com/)
1. [MeDirect](https://medirect.com.mt/) 1. [MeDirect](https://medirect.com.mt/)
1. [Meican](https://meican.com/) 1. [Meican](https://meican.com/)
1. [Meilleurs Agents](https://www.meilleursagents.com/)
1. [Mercedes-Benz Tech Innovation](https://www.mercedes-benz-techinnovation.com/)
1. [Mercedes-Benz.io](https://www.mercedes-benz.io/)
1. [Metacore Games](https://metacoregames.com/)
1. [Metanet](http://www.metanet.co.kr/en/) 1. [Metanet](http://www.metanet.co.kr/en/)
1. [MindSpore](https://mindspore.cn) 1. [MindSpore](https://mindspore.cn)
1. [Mirantis](https://mirantis.com/) 1. [Mirantis](https://mirantis.com/)
1. [Mission Lane](https://missionlane.com)
1. [mixi Group](https://mixi.co.jp/) 1. [mixi Group](https://mixi.co.jp/)
1. [Moengage](https://www.moengage.com/) 1. [Moengage](https://www.moengage.com/)
1. [Money Forward](https://corp.moneyforward.com/en/) 1. [Money Forward](https://corp.moneyforward.com/en/)
1. [MOO Print](https://www.moo.com/) 1. [MOO Print](https://www.moo.com/)
1. [Mozilla](https://www.mozilla.org)
1. [MTN Group](https://www.mtn.com/) 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. [Natura &Co](https://naturaeco.com/)
1. [Nethopper](https://nethopper.io) 1. [Nethopper](https://nethopper.io)
1. [New Relic](https://newrelic.com/) 1. [New Relic](https://newrelic.com/)
1. [Nextbasket](https://nextbasket.com)
1. [Nextdoor](https://nextdoor.com/) 1. [Nextdoor](https://nextdoor.com/)
1. [Next Fit Sistemas](https://nextfit.com.br/)
1. [Nikkei](https://www.nikkei.co.jp/nikkeiinfo/en/) 1. [Nikkei](https://www.nikkei.co.jp/nikkeiinfo/en/)
1. [Nitro](https://gonitro.com) 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. [OCCMundial](https://occ.com.mx)
1. [Octadesk](https://octadesk.com) 1. [Octadesk](https://octadesk.com)
1. [Octopus Deploy](https://octopus.com)
1. [Olfeo](https://www.olfeo.com/)
1. [omegaUp](https://omegaUp.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. [openEuler](https://openeuler.org)
1. [openGauss](https://opengauss.org/) 1. [openGauss](https://opengauss.org/)
1. [OpenGov](https://opengov.com)
1. [openLooKeng](https://openlookeng.io) 1. [openLooKeng](https://openlookeng.io)
1. [OpenSaaS Studio](https://opensaas.studio) 1. [OpenSaaS Studio](https://opensaas.studio)
1. [Opensurvey](https://www.opensurvey.co.kr/) 1. [Opensurvey](https://www.opensurvey.co.kr/)
1. [OpsMx](https://opsmx.io)
1. [OpsVerse](https://opsverse.io) 1. [OpsVerse](https://opsverse.io)
1. [Optoro](https://www.optoro.com/) 1. [Optoro](https://www.optoro.com/)
1. [Orbital Insight](https://orbitalinsight.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. [p3r](https://www.p3r.one/)
1. [Packlink](https://www.packlink.com/) 1. [Packlink](https://www.packlink.com/)
1. [PagerDuty](https://www.pagerduty.com/)
1. [Pandosearch](https://www.pandosearch.com/en/home) 1. [Pandosearch](https://www.pandosearch.com/en/home)
1. [PagerDuty](https://www.pagerduty.com/)
1. [Patreon](https://www.patreon.com/) 1. [Patreon](https://www.patreon.com/)
1. [PayIt](https://payitgov.com/)
1. [PayPay](https://paypay.ne.jp/) 1. [PayPay](https://paypay.ne.jp/)
1. [Peloton Interactive](https://www.onepeloton.com/) 1. [Peloton Interactive](https://www.onepeloton.com/)
1. [Percona](https://percona.com/)
1. [PGS](https://www.pgs.com)
1. [Pigment](https://www.gopigment.com/)
1. [Pipedrive](https://www.pipedrive.com/)
1. [Pipefy](https://www.pipefy.com/) 1. [Pipefy](https://www.pipefy.com/)
1. [Pipekit](https://pipekit.io/)
1. [Pismo](https://pismo.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. [Polarpoint.io](https://polarpoint.io)
1. [PostFinance](https://github.com/postfinance) 1. [PostFinance](https://github.com/postfinance)
1. [Preferred Networks](https://preferred.jp/en/) 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. [Productboard](https://www.productboard.com/)
1. [Prudential](https://prudential.com.sg) 1. [Prudential](https://prudential.com.sg)
1. [PT Boer Technology (Btech)](https://btech.id/)
1. [PUBG](https://www.pubg.com) 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. [Qonto](https://qonto.com)
1. [QuintoAndar](https://quintoandar.com.br) 1. [QuintoAndar](https://quintoandar.com.br)
1. [Quipper](https://www.quipper.com/) 1. [Quipper](https://www.quipper.com/)
1. [RapidAPI](https://www.rapidapi.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. [Red Hat](https://www.redhat.com/)
1. [Redpill Linpro](https://www.redpill-linpro.com/) 1. [Redpill Linpro](https://www.redpill-linpro.com/)
1. [Reenigne Cloud](https://reenigne.ca)
1. [reev.com](https://www.reev.com/) 1. [reev.com](https://www.reev.com/)
1. [Relex Solutions](https://www.relexsolutions.com/)
1. [RightRev](https://rightrev.com/) 1. [RightRev](https://rightrev.com/)
1. [Rijkswaterstaat](https://www.rijkswaterstaat.nl/en)
1. [Rise](https://www.risecard.eu/) 1. [Rise](https://www.risecard.eu/)
1. [Riskified](https://www.riskified.com/) 1. [Riskified](https://www.riskified.com/)
1. [Robotinfra](https://www.robotinfra.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. [Rubin Observatory](https://www.lsst.org)
1. [Saildrone](https://www.saildrone.com/) 1. [Saildrone](https://www.saildrone.com/)
1. [Salad Technologies](https://salad.com/)
1. [Saloodo! GmbH](https://www.saloodo.com) 1. [Saloodo! GmbH](https://www.saloodo.com)
1. [Sap Labs](http://sap.com) 1. [Sap Labs](http://sap.com)
1. [Sauce Labs](https://saucelabs.com/)
1. [Schwarz IT](https://jobs.schwarz/it-mission) 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. [Skit](https://skit.ai/)
1. [Skribble](https://skribble.com)
1. [Skyscanner](https://www.skyscanner.net/) 1. [Skyscanner](https://www.skyscanner.net/)
1. [Smart Pension](https://www.smartpension.co.uk/)
1. [Smilee.io](https://smilee.io) 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. [Snapp](https://snapp.ir/)
1. [Snyk](https://snyk.io/) 1. [Snyk](https://snyk.io/)
1. [Softway Medical](https://www.softwaymedical.fr/) 1. [Softway Medical](https://www.softwaymedical.fr/)
@@ -325,9 +195,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Spendesk](https://spendesk.com/) 1. [Spendesk](https://spendesk.com/)
1. [Splunk](https://splunk.com/) 1. [Splunk](https://splunk.com/)
1. [Spores Labs](https://spores.app) 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. [Stuart](https://stuart.com/)
1. [Sumo Logic](https://sumologic.com/) 1. [Sumo Logic](https://sumologic.com/)
1. [Sutpc](http://www.sutpc.com/) 1. [Sutpc](http://www.sutpc.com/)
@@ -335,17 +202,12 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Swisscom](https://www.swisscom.ch) 1. [Swisscom](https://www.swisscom.ch)
1. [Swissquote](https://github.com/swissquote) 1. [Swissquote](https://github.com/swissquote)
1. [Syncier](https://syncier.com/) 1. [Syncier](https://syncier.com/)
1. [Syself](https://syself.com)
1. [TableCheck](https://tablecheck.com/) 1. [TableCheck](https://tablecheck.com/)
1. [Tailor Brands](https://www.tailorbrands.com) 1. [Tailor Brands](https://www.tailorbrands.com)
1. [Tamkeen Technologies](https://tamkeentech.sa/) 1. [Tamkeen Technologies](https://tamkeentech.sa/)
1. [TBC Bank](https://tbcbank.ge/)
1. [Techcombank](https://www.techcombank.com.vn/trang-chu) 1. [Techcombank](https://www.techcombank.com.vn/trang-chu)
1. [Technacy](https://www.technacy.it/) 1. [Technacy](https://www.technacy.it/)
1. [Telavita](https://www.telavita.com.br/)
1. [Tesla](https://tesla.com/) 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. [ThousandEyes](https://www.thousandeyes.com/)
1. [Ticketmaster](https://ticketmaster.com) 1. [Ticketmaster](https://ticketmaster.com)
1. [Tiger Analytics](https://www.tigeranalytics.com/) 1. [Tiger Analytics](https://www.tigeranalytics.com/)
@@ -353,28 +215,17 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Toss](https://toss.im/en) 1. [Toss](https://toss.im/en)
1. [Trendyol](https://www.trendyol.com/) 1. [Trendyol](https://www.trendyol.com/)
1. [tru.ID](https://tru.id) 1. [tru.ID](https://tru.id)
1. [Trusting Social](https://trustingsocial.com/)
1. [Twilio Segment](https://segment.com/)
1. [Twilio SendGrid](https://sendgrid.com) 1. [Twilio SendGrid](https://sendgrid.com)
1. [tZERO](https://www.tzero.com/) 1. [tZERO](https://www.tzero.com/)
1. [U.S. Veterans Affairs Department](https://www.va.gov/)
1. [UBIO](https://ub.io/) 1. [UBIO](https://ub.io/)
1. [UFirstGroup](https://www.ufirstgroup.com/en/) 1. [UFirstGroup](https://www.ufirstgroup.com/en/)
1. [ungleich.ch](https://ungleich.ch/) 1. [ungleich.ch](https://ungleich.ch/)
1. [Unifonic Inc](https://www.unifonic.com/) 1. [Unifonic Inc](https://www.unifonic.com/)
1. [Universidad Mesoamericana](https://www.umes.edu.gt/) 1. [Universidad Mesoamericana](https://www.umes.edu.gt/)
1. [Upsider Inc.](https://up-sider.com/lp/)
1. [Urbantz](https://urbantz.com/)
1. [Vectra](https://www.vectra.ai)
1. [Veepee](https://www.veepee.com)
1. [Verkada](https://www.verkada.com)
1. [Viaduct](https://www.viaduct.ai/) 1. [Viaduct](https://www.viaduct.ai/)
1. [VietMoney](https://vietmoney.vn/)
1. [Vinted](https://vinted.com/)
1. [Virtuo](https://www.govirtuo.com/) 1. [Virtuo](https://www.govirtuo.com/)
1. [VISITS Technologies](https://visits.world/en) 1. [VISITS Technologies](https://visits.world/en)
1. [Volvo Cars](https://www.volvocars.com/) 1. [Volvo Cars](https://www.volvocars.com/)
1. [Voyager Digital](https://www.investvoyager.com/)
1. [VSHN - The DevOps Company](https://vshn.ch/) 1. [VSHN - The DevOps Company](https://vshn.ch/)
1. [Walkbase](https://www.walkbase.com/) 1. [Walkbase](https://www.walkbase.com/)
1. [Webstores](https://www.webstores.nl) 1. [Webstores](https://www.webstores.nl)
@@ -383,14 +234,11 @@ Currently, the following organizations are **officially** using Argo CD:
1. [WeMo Scooter](https://www.wemoscooter.com/) 1. [WeMo Scooter](https://www.wemoscooter.com/)
1. [Whitehat Berlin](https://whitehat.berlin) by Guido Maria Serra +Fenaroli 1. [Whitehat Berlin](https://whitehat.berlin) by Guido Maria Serra +Fenaroli
1. [Witick](https://witick.io/) 1. [Witick](https://witick.io/)
1. [Wolffun Game](https://www.wolffungame.com/)
1. [WooliesX](https://wooliesx.com.au/) 1. [WooliesX](https://wooliesx.com.au/)
1. [Woolworths Group](https://www.woolworthsgroup.com.au/) 1. [Woolworths Group](https://www.woolworthsgroup.com.au/)
1. [WSpot](https://www.wspot.com.br/) 1. [WSpot](https://www.wspot.com.br/)
1. [Yieldlab](https://www.yieldlab.de/) 1. [Yieldlab](https://www.yieldlab.de/)
1. [Youverify](https://youverify.co/) 1. [Youverify](https://youverify.co/)
1. [Yubo](https://www.yubo.live/) 1. [Yubo](https://www.yubo.live/)
1. [ZDF](https://www.zdf.de/)
1. [Zimpler](https://www.zimpler.com/) 1. [Zimpler](https://www.zimpler.com/)
1. [ZipRecuiter](https://www.ziprecruiter.com/)
1. [ZOZO](https://corp.zozo.com/) 1. [ZOZO](https://corp.zozo.com/)

View File

@@ -1 +1 @@
2.14.0 2.5.16

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,9 +2,6 @@ package controllers
import ( import (
"context" "context"
"fmt"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@@ -14,43 +11,44 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/event"
"github.com/argoproj/argo-cd/v2/common" "github.com/argoproj/argo-cd/v2/applicationset/generators"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" 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 // clusterSecretEventHandler is used when watching Secrets to check if they are ArgoCD Cluster Secrets, and if so
// requeue any related ApplicationSets. // requeue any related ApplicationSets.
type clusterSecretEventHandler struct { type clusterSecretEventHandler struct {
// handler.EnqueueRequestForOwner //handler.EnqueueRequestForOwner
Log log.FieldLogger Log log.FieldLogger
Client client.Client Client client.Client
} }
func (h *clusterSecretEventHandler) Create(ctx context.Context, e event.CreateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { func (h *clusterSecretEventHandler) Create(e event.CreateEvent, q workqueue.RateLimitingInterface) {
h.queueRelatedAppGenerators(ctx, q, e.Object) h.queueRelatedAppGenerators(q, e.Object)
} }
func (h *clusterSecretEventHandler) Update(ctx context.Context, e event.UpdateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { func (h *clusterSecretEventHandler) Update(e event.UpdateEvent, q workqueue.RateLimitingInterface) {
h.queueRelatedAppGenerators(ctx, q, e.ObjectNew) h.queueRelatedAppGenerators(q, e.ObjectNew)
} }
func (h *clusterSecretEventHandler) Delete(ctx context.Context, e event.DeleteEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { func (h *clusterSecretEventHandler) Delete(e event.DeleteEvent, q workqueue.RateLimitingInterface) {
h.queueRelatedAppGenerators(ctx, q, e.Object) h.queueRelatedAppGenerators(q, e.Object)
} }
func (h *clusterSecretEventHandler) Generic(ctx context.Context, e event.GenericEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { func (h *clusterSecretEventHandler) Generic(e event.GenericEvent, q workqueue.RateLimitingInterface) {
h.queueRelatedAppGenerators(ctx, q, e.Object) h.queueRelatedAppGenerators(q, e.Object)
} }
// addRateLimitingInterface defines the Add method of workqueue.RateLimitingInterface, allow us to easily mock // addRateLimitingInterface defines the Add method of workqueue.RateLimitingInterface, allow us to easily mock
// it for testing purposes. // it for testing purposes.
type addRateLimitingInterface[T comparable] interface { type addRateLimitingInterface interface {
Add(item T) 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 // 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 return
} }
@@ -60,7 +58,7 @@ func (h *clusterSecretEventHandler) queueRelatedAppGenerators(ctx context.Contex
}).Info("processing event for cluster secret") }).Info("processing event for cluster secret")
appSetList := &argoprojiov1alpha1.ApplicationSetList{} appSetList := &argoprojiov1alpha1.ApplicationSetList{}
err := h.Client.List(ctx, appSetList) err := h.Client.List(context.Background(), appSetList)
if err != nil { if err != nil {
h.Log.WithError(err).Error("unable to list ApplicationSets") h.Log.WithError(err).Error("unable to list ApplicationSets")
return return
@@ -68,98 +66,19 @@ func (h *clusterSecretEventHandler) queueRelatedAppGenerators(ctx context.Contex
h.Log.WithField("count", len(appSetList.Items)).Info("listed ApplicationSets") h.Log.WithField("count", len(appSetList.Items)).Info("listed ApplicationSets")
for _, appSet := range appSetList.Items { for _, appSet := range appSetList.Items {
foundClusterGenerator := false foundClusterGenerator := false
for _, generator := range appSet.Spec.Generators { for _, generator := range appSet.Spec.Generators {
if generator.Clusters != nil { if generator.Clusters != nil {
foundClusterGenerator = true foundClusterGenerator = true
break break
} }
if generator.Matrix != nil {
ok, err := nestedGeneratorsHaveClusterGenerator(generator.Matrix.Generators)
if err != nil {
h.Log.
WithFields(log.Fields{
"namespace": appSet.GetNamespace(),
"name": appSet.GetName(),
}).
WithError(err).
Error("Unable to check if ApplicationSet matrix generators have cluster generator")
}
if ok {
foundClusterGenerator = true
break
}
}
if generator.Merge != nil {
ok, err := nestedGeneratorsHaveClusterGenerator(generator.Merge.Generators)
if err != nil {
h.Log.
WithFields(log.Fields{
"namespace": appSet.GetNamespace(),
"name": appSet.GetName(),
}).
WithError(err).
Error("Unable to check if ApplicationSet merge generators have cluster generator")
}
if ok {
foundClusterGenerator = true
break
}
}
} }
if foundClusterGenerator { if foundClusterGenerator {
// TODO: only queue the AppGenerator if the labels match this cluster // TODO: only queue the AppGenerator if the labels match this cluster
req := ctrl.Request{NamespacedName: types.NamespacedName{Namespace: appSet.Namespace, Name: appSet.Name}} req := ctrl.Request{NamespacedName: types.NamespacedName{Namespace: appSet.Namespace, Name: appSet.Name}}
q.Add(req) q.Add(req)
} }
} }
} }
// nestedGeneratorsHaveClusterGenerator iterate over provided nested generators to check if they have a cluster generator.
func nestedGeneratorsHaveClusterGenerator(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator) (bool, error) {
for _, generator := range generators {
if ok, err := nestedGeneratorHasClusterGenerator(generator); ok || err != nil {
return ok, err
}
}
return false, nil
}
// nestedGeneratorHasClusterGenerator checks if the provided generator has a cluster generator.
func nestedGeneratorHasClusterGenerator(nested argoprojiov1alpha1.ApplicationSetNestedGenerator) (bool, error) {
if nested.Clusters != nil {
return true, nil
}
if nested.Matrix != nil {
nestedMatrix, err := argoprojiov1alpha1.ToNestedMatrixGenerator(nested.Matrix)
if err != nil {
return false, fmt.Errorf("unable to get nested matrix generator: %w", err)
}
if nestedMatrix != nil {
hasClusterGenerator, err := nestedGeneratorsHaveClusterGenerator(nestedMatrix.ToMatrixGenerator().Generators)
if err != nil {
return false, fmt.Errorf("error evaluating nested matrix generator: %w", err)
}
return hasClusterGenerator, nil
}
}
if nested.Merge != nil {
nestedMerge, err := argoprojiov1alpha1.ToNestedMergeGenerator(nested.Merge)
if err != nil {
return false, fmt.Errorf("unable to get nested merge generator: %w", err)
}
if nestedMerge != nil {
hasClusterGenerator, err := nestedGeneratorsHaveClusterGenerator(nestedMerge.ToMergeGenerator().Generators)
if err != nil {
return false, fmt.Errorf("error evaluating nested merge generator: %w", err)
}
return hasClusterGenerator, nil
}
}
return false, nil
}

View File

@@ -1,16 +1,11 @@
package controllers package controllers
import ( import (
"context"
"testing" "testing"
argocommon "github.com/argoproj/argo-cd/v2/common"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
@@ -18,16 +13,18 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/argoproj/argo-cd/v2/applicationset/generators"
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
func TestClusterEventHandler(t *testing.T) { func TestClusterEventHandler(t *testing.T) {
scheme := runtime.NewScheme() scheme := runtime.NewScheme()
err := argov1alpha1.AddToScheme(scheme) err := argov1alpha1.AddToScheme(scheme)
require.NoError(t, err) assert.Nil(t, err)
err = argov1alpha1.AddToScheme(scheme) err = argov1alpha1.AddToScheme(scheme)
require.NoError(t, err) assert.Nil(t, err)
tests := []struct { tests := []struct {
name string name string
@@ -43,7 +40,7 @@ func TestClusterEventHandler(t *testing.T) {
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
}, },
}, },
}, },
@@ -71,7 +68,7 @@ func TestClusterEventHandler(t *testing.T) {
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
}, },
}, },
}, },
@@ -114,7 +111,7 @@ func TestClusterEventHandler(t *testing.T) {
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
}, },
}, },
}, },
@@ -158,7 +155,7 @@ func TestClusterEventHandler(t *testing.T) {
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
}, },
}, },
}, },
@@ -166,6 +163,7 @@ func TestClusterEventHandler(t *testing.T) {
{NamespacedName: types.NamespacedName{Namespace: "another-namespace", Name: "my-app-set"}}, {NamespacedName: types.NamespacedName{Namespace: "another-namespace", Name: "my-app-set"}},
}, },
}, },
{ {
name: "non-argo cd secret should not match", name: "non-argo cd secret should not match",
items: []argov1alpha1.ApplicationSet{ items: []argov1alpha1.ApplicationSet{
@@ -191,352 +189,12 @@ func TestClusterEventHandler(t *testing.T) {
}, },
expectedRequests: []reconcile.Request{}, expectedRequests: []reconcile.Request{},
}, },
{
name: "a matrix generator with a cluster generator should produce a request",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set",
Namespace: "argocd",
},
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{
{
Matrix: &argov1alpha1.MatrixGenerator{
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
{
Clusters: &argov1alpha1.ClusterGenerator{},
},
},
},
},
},
},
},
},
secret: corev1.Secret{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
},
},
},
expectedRequests: []reconcile.Request{{
NamespacedName: types.NamespacedName{Namespace: "argocd", Name: "my-app-set"},
}},
},
{
name: "a matrix generator with non cluster generator should not match",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set",
Namespace: "argocd",
},
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{
{
Matrix: &argov1alpha1.MatrixGenerator{
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
{
List: &argov1alpha1.ListGenerator{},
},
},
},
},
},
},
},
},
secret: corev1.Secret{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
},
},
},
expectedRequests: []reconcile.Request{},
},
{
name: "a matrix generator with a nested matrix generator containing a cluster generator should produce a request",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set",
Namespace: "argocd",
},
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{
{
Matrix: &argov1alpha1.MatrixGenerator{
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
{
Matrix: &apiextensionsv1.JSON{
Raw: []byte(
`{
"generators": [
{
"clusters": {
"selector": {
"matchLabels": {
"argocd.argoproj.io/secret-type": "cluster"
}
}
}
}
]
}`,
),
},
},
},
},
},
},
},
},
},
secret: corev1.Secret{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
},
},
},
expectedRequests: []reconcile.Request{{
NamespacedName: types.NamespacedName{Namespace: "argocd", Name: "my-app-set"},
}},
},
{
name: "a matrix generator with a nested matrix generator containing non cluster generator should not match",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set",
Namespace: "argocd",
},
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{
{
Matrix: &argov1alpha1.MatrixGenerator{
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
{
Matrix: &apiextensionsv1.JSON{
Raw: []byte(
`{
"generators": [
{
"list": {
"elements": [
"a",
"b"
]
}
}
]
}`,
),
},
},
},
},
},
},
},
},
},
secret: corev1.Secret{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
},
},
},
expectedRequests: []reconcile.Request{},
},
{
name: "a merge generator with a cluster generator should produce a request",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set",
Namespace: "argocd",
},
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{
{
Merge: &argov1alpha1.MergeGenerator{
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
{
Clusters: &argov1alpha1.ClusterGenerator{},
},
},
},
},
},
},
},
},
secret: corev1.Secret{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
},
},
},
expectedRequests: []reconcile.Request{{
NamespacedName: types.NamespacedName{Namespace: "argocd", Name: "my-app-set"},
}},
},
{
name: "a matrix generator with non cluster generator should not match",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set",
Namespace: "argocd",
},
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{
{
Merge: &argov1alpha1.MergeGenerator{
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
{
List: &argov1alpha1.ListGenerator{},
},
},
},
},
},
},
},
},
secret: corev1.Secret{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
},
},
},
expectedRequests: []reconcile.Request{},
},
{
name: "a merge generator with a nested merge generator containing a cluster generator should produce a request",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set",
Namespace: "argocd",
},
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{
{
Merge: &argov1alpha1.MergeGenerator{
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
{
Merge: &apiextensionsv1.JSON{
Raw: []byte(
`{
"generators": [
{
"clusters": {
"selector": {
"matchLabels": {
"argocd.argoproj.io/secret-type": "cluster"
}
}
}
}
]
}`,
),
},
},
},
},
},
},
},
},
},
secret: corev1.Secret{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
},
},
},
expectedRequests: []reconcile.Request{{
NamespacedName: types.NamespacedName{Namespace: "argocd", Name: "my-app-set"},
}},
},
{
name: "a merge generator with a nested merge generator containing non cluster generator should not match",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: v1.ObjectMeta{
Name: "my-app-set",
Namespace: "argocd",
},
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{
{
Merge: &argov1alpha1.MergeGenerator{
Generators: []argov1alpha1.ApplicationSetNestedGenerator{
{
Merge: &apiextensionsv1.JSON{
Raw: []byte(
`{
"generators": [
{
"list": {
"elements": [
"a",
"b"
]
}
}
]
}`,
),
},
},
},
},
},
},
},
},
},
secret: corev1.Secret{
ObjectMeta: v1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
},
},
},
expectedRequests: []reconcile.Request{},
},
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
appSetList := argov1alpha1.ApplicationSetList{ appSetList := argov1alpha1.ApplicationSetList{
Items: test.items, Items: test.items,
} }
@@ -550,83 +208,26 @@ func TestClusterEventHandler(t *testing.T) {
mockAddRateLimitingInterface := mockAddRateLimitingInterface{} mockAddRateLimitingInterface := mockAddRateLimitingInterface{}
handler.queueRelatedAppGenerators(context.Background(), &mockAddRateLimitingInterface, &test.secret) handler.queueRelatedAppGenerators(&mockAddRateLimitingInterface, &test.secret)
assert.False(t, mockAddRateLimitingInterface.errorOccurred)
assert.ElementsMatch(t, mockAddRateLimitingInterface.addedItems, test.expectedRequests) assert.ElementsMatch(t, mockAddRateLimitingInterface.addedItems, test.expectedRequests)
}) })
} }
} }
// Add checks the type, and adds it to the internal list of received additions // Add checks the type, and adds it to the internal list of received additions
func (obj *mockAddRateLimitingInterface) Add(item reconcile.Request) { func (obj *mockAddRateLimitingInterface) Add(item interface{}) {
obj.addedItems = append(obj.addedItems, item) if req, ok := item.(ctrl.Request); ok {
obj.addedItems = append(obj.addedItems, req)
} else {
obj.errorOccurred = true
}
} }
type mockAddRateLimitingInterface struct { type mockAddRateLimitingInterface struct {
addedItems []reconcile.Request errorOccurred bool
} addedItems []ctrl.Request
func TestNestedGeneratorHasClusterGenerator_NestedClusterGenerator(t *testing.T) {
nested := argov1alpha1.ApplicationSetNestedGenerator{
Clusters: &argov1alpha1.ClusterGenerator{},
}
hasClusterGenerator, err := nestedGeneratorHasClusterGenerator(nested)
require.NoError(t, err)
assert.True(t, hasClusterGenerator)
}
func TestNestedGeneratorHasClusterGenerator_NestedMergeGenerator(t *testing.T) {
nested := argov1alpha1.ApplicationSetNestedGenerator{
Merge: &apiextensionsv1.JSON{
Raw: []byte(
`{
"generators": [
{
"clusters": {
"selector": {
"matchLabels": {
"argocd.argoproj.io/secret-type": "cluster"
}
}
}
}
]
}`,
),
},
}
hasClusterGenerator, err := nestedGeneratorHasClusterGenerator(nested)
require.NoError(t, err)
assert.True(t, hasClusterGenerator)
}
func TestNestedGeneratorHasClusterGenerator_NestedMergeGeneratorWithInvalidJSON(t *testing.T) {
nested := argov1alpha1.ApplicationSetNestedGenerator{
Merge: &apiextensionsv1.JSON{
Raw: []byte(
`{
"generators": [
{
"clusters": {
"selector": {
"matchLabels": {
"argocd.argoproj.io/secret-type": "cluster"
}
}
}
}
]
`,
),
},
}
hasClusterGenerator, err := nestedGeneratorHasClusterGenerator(nested)
require.Error(t, err)
assert.False(t, hasClusterGenerator)
} }

View File

@@ -5,8 +5,10 @@ import (
"testing" "testing"
"time" "time"
"github.com/argoproj/argo-cd/v2/applicationset/generators"
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/mock"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@@ -15,19 +17,14 @@ import (
kubefake "k8s.io/client-go/kubernetes/fake" kubefake "k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
"sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/argoproj/argo-cd/v2/applicationset/generators"
appsetmetrics "github.com/argoproj/argo-cd/v2/applicationset/metrics"
"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) { func TestRequeueAfter(t *testing.T) {
mockServer := &mocks.Repos{} mockServer := argoCDServiceMock{}
ctx := context.Background() ctx := context.Background()
scheme := runtime.NewScheme() scheme := runtime.NewScheme()
err := argov1alpha1.AddToScheme(scheme) err := argov1alpha1.AddToScheme(scheme)
require.NoError(t, err) assert.Nil(t, err)
gvrToListKind := map[schema.GroupVersionResource]string{{ gvrToListKind := map[schema.GroupVersionResource]string{{
Group: "mallard.io", Group: "mallard.io",
Version: "v1", Version: "v1",
@@ -57,14 +54,14 @@ func TestRequeueAfter(t *testing.T) {
}, },
} }
fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, duckType) fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, duckType)
scmConfig := generators.NewSCMConfig("", []string{""}, true, nil, true)
terminalGenerators := map[string]generators.Generator{ terminalGenerators := map[string]generators.Generator{
"List": generators.NewListGenerator(), "List": generators.NewListGenerator(),
"Clusters": generators.NewClusterGenerator(k8sClient, ctx, appClientset, "argocd"), "Clusters": generators.NewClusterGenerator(k8sClient, ctx, appClientset, "argocd"),
"Git": generators.NewGitGenerator(mockServer, "namespace"), "Git": generators.NewGitGenerator(mockServer),
"SCMProvider": generators.NewSCMProviderGenerator(fake.NewClientBuilder().WithObjects(&corev1.Secret{}).Build(), scmConfig), "SCMProvider": generators.NewSCMProviderGenerator(fake.NewClientBuilder().WithObjects(&corev1.Secret{}).Build(), generators.SCMAuthProviders{}),
"ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, fakeDynClient, appClientset, "argocd"), "ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, fakeDynClient, appClientset, "argocd"),
"PullRequest": generators.NewPullRequestGenerator(k8sClient, scmConfig), "PullRequest": generators.NewPullRequestGenerator(k8sClient, generators.SCMAuthProviders{}),
} }
nestedGenerators := map[string]generators.Generator{ nestedGenerators := map[string]generators.Generator{
@@ -90,18 +87,15 @@ func TestRequeueAfter(t *testing.T) {
} }
client := fake.NewClientBuilder().WithScheme(scheme).Build() client := fake.NewClientBuilder().WithScheme(scheme).Build()
metrics := appsetmetrics.NewFakeAppsetMetrics(client)
r := ApplicationSetReconciler{ r := ApplicationSetReconciler{
Client: client, Client: client,
Scheme: scheme, Scheme: scheme,
Recorder: record.NewFakeRecorder(0), Recorder: record.NewFakeRecorder(0),
Generators: topLevelGenerators, Generators: topLevelGenerators,
Metrics: metrics,
} }
type args struct { type args struct {
appset *argov1alpha1.ApplicationSet appset *argov1alpha1.ApplicationSet
requeueAfterOverride string
} }
tests := []struct { tests := []struct {
name string name string
@@ -109,13 +103,11 @@ func TestRequeueAfter(t *testing.T) {
want time.Duration want time.Duration
wantErr assert.ErrorAssertionFunc wantErr assert.ErrorAssertionFunc
}{ }{
{name: "Cluster", args: args{ {name: "Cluster", args: args{appset: &argov1alpha1.ApplicationSet{
appset: &argov1alpha1.ApplicationSet{ Spec: argov1alpha1.ApplicationSetSpec{
Spec: argov1alpha1.ApplicationSetSpec{ Generators: []argov1alpha1.ApplicationSetGenerator{{Clusters: &argov1alpha1.ClusterGenerator{}}},
Generators: []argov1alpha1.ApplicationSetGenerator{{Clusters: &argov1alpha1.ClusterGenerator{}}}, },
}, }}, want: generators.NoRequeueAfter, wantErr: assert.NoError},
}, requeueAfterOverride: "",
}, want: generators.NoRequeueAfter, wantErr: assert.NoError},
{name: "ClusterMergeNested", args: args{&argov1alpha1.ApplicationSet{ {name: "ClusterMergeNested", args: args{&argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{ Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{ Generators: []argov1alpha1.ApplicationSetGenerator{
@@ -130,7 +122,7 @@ func TestRequeueAfter(t *testing.T) {
}}, }},
}, },
}, },
}, ""}, want: generators.DefaultRequeueAfterSeconds, wantErr: assert.NoError}, }}, want: generators.DefaultRequeueAfterSeconds, wantErr: assert.NoError},
{name: "ClusterMatrixNested", args: args{&argov1alpha1.ApplicationSet{ {name: "ClusterMatrixNested", args: args{&argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{ Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{ Generators: []argov1alpha1.ApplicationSetGenerator{
@@ -145,66 +137,43 @@ func TestRequeueAfter(t *testing.T) {
}}, }},
}, },
}, },
}, ""}, want: generators.DefaultRequeueAfterSeconds, wantErr: assert.NoError}, }}, want: generators.DefaultRequeueAfterSeconds, wantErr: assert.NoError},
{name: "ListGenerator", args: args{appset: &argov1alpha1.ApplicationSet{ {name: "ListGenerator", args: args{appset: &argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{ Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{{List: &argov1alpha1.ListGenerator{}}}, Generators: []argov1alpha1.ApplicationSetGenerator{{List: &argov1alpha1.ListGenerator{}}},
}, },
}}, want: generators.NoRequeueAfter, wantErr: assert.NoError}, }}, 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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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) assert.Equalf(t, tt.want, r.getMinRequeueAfter(tt.args.appset), "getMinRequeueAfter(%v)", tt.args.appset)
}) })
} }
} }
type argoCDServiceMock struct {
mock *mock.Mock
}
func (a argoCDServiceMock) GetApps(ctx context.Context, repoURL string, revision string) ([]string, error) {
args := a.mock.Called(ctx, repoURL, revision)
return args.Get(0).([]string), args.Error(1)
}
func (a argoCDServiceMock) GetFiles(ctx context.Context, repoURL string, revision string, pattern string) (map[string][]byte, error) {
args := a.mock.Called(ctx, repoURL, revision, pattern)
return args.Get(0).(map[string][]byte), args.Error(1)
}
func (a argoCDServiceMock) GetFileContent(ctx context.Context, repoURL string, revision string, path string) ([]byte, error) {
args := a.mock.Called(ctx, repoURL, revision, path)
return args.Get(0).([]byte), args.Error(1)
}
func (a argoCDServiceMock) GetDirectories(ctx context.Context, repoURL string, revision string) ([]string, error) {
args := a.mock.Called(ctx, repoURL, revision)
return args.Get(0).([]string), args.Error(1)
}

View File

@@ -1,43 +0,0 @@
package template
import (
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"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)
}
if err := json.Unmarshal([]byte(convertedTemplatePatch), &appv1.Application{}); err != nil {
return nil, fmt.Errorf("invalid templatePatch %q: %w", convertedTemplatePatch, err)
}
data, err := strategicpatch.StrategicMergePatch(appString, []byte(convertedTemplatePatch), appv1.Application{})
if err != nil {
return nil, fmt.Errorf("error while applying templatePatch template to json %q: %w", convertedTemplatePatch, err)
}
finalApp := appv1.Application{}
err = json.Unmarshal(data, &finalApp)
if err != nil {
return nil, fmt.Errorf("error while unmarhsalling patched application: %w", err)
}
// Prevent changes to the `project` field. This helps prevent malicious template patches
finalApp.Spec.Project = app.Spec.Project
return &finalApp, nil
}

View File

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

View File

@@ -1,101 +0,0 @@
package template
import (
"fmt"
"sigs.k8s.io/controller-runtime/pkg/client"
log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v2/applicationset/generators"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
argov1alpha1 "github.com/argoproj/argo-cd/v2/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]interface{}{}, 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]interface{}) (*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 (
"fmt"
"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/v2/applicationset/generators"
genmock "github.com/argoproj/argo-cd/v2/applicationset/generators/mocks"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
rendmock "github.com/argoproj/argo-cd/v2/applicationset/utils/mocks"
"github.com/argoproj/argo-cd/v2/pkg/apis/application"
"github.com/argoproj/argo-cd/v2/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]interface{}
template v1alpha1.ApplicationSetTemplate
generateParamsError error
rendererError error
expectErr bool
expectedReason v1alpha1.ApplicationSetReasonType
}{
{
name: "Generate two applications",
params: []map[string]interface{}{{"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: fmt.Errorf("error"),
expectErr: true,
expectedReason: v1alpha1.ApplicationSetReasonApplicationParamsGenerationError,
},
{
name: "Handles error from the render",
params: []map[string]interface{}{{"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: fmt.Errorf("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]interface{}
template v1alpha1.ApplicationSetTemplate
overrideTemplate v1alpha1.ApplicationSetTemplate
expectedMerged v1alpha1.ApplicationSetTemplate
expectedApps []v1alpha1.Application
}{
{
name: "Generate app",
params: []map[string]interface{}{{"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]interface{}
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]interface{}{
{
"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,35 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: guestbook
spec:
goTemplate: true
generators:
- list:
elements:
- cluster: engineering-dev
url: https://kubernetes.default.svc
foo: bar
# Update foo value with foo: bar
# Application engineering-prod-guestbook labels will still be baz
# Delete this element
# Application engineering-prod-guestbook will be kept
- cluster: engineering-prod
url: https://kubernetes.default.svc
foo: baz
template:
metadata:
name: '{{.cluster}}-guestbook'
labels:
foo: '{{.foo}}'
spec:
project: default
source:
repoURL: https://github.com/argoproj/argo-cd.git
targetRevision: HEAD
path: applicationset/examples/list-generator/guestbook/{{.cluster}}
destination:
server: '{{.url}}'
namespace: guestbook
syncPolicy:
applicationsSync: create-only

View File

@@ -1,35 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: guestbook
spec:
goTemplate: true
generators:
- list:
elements:
- cluster: engineering-dev
url: https://kubernetes.default.svc
foo: bar
# Update foo value with foo: bar
# Application engineering-prod-guestbook labels will change to foo: bar
# Delete this element
# Application engineering-prod-guestbook will be kept
- cluster: engineering-prod
url: https://kubernetes.default.svc
foo: baz
template:
metadata:
name: '{{.cluster}}-guestbook'
labels:
foo: '{{.foo}}'
spec:
project: default
source:
repoURL: https://github.com/argoproj/argo-cd.git
targetRevision: HEAD
path: applicationset/examples/list-generator/guestbook/{{.cluster}}
destination:
server: '{{.url}}'
namespace: guestbook
syncPolicy:
applicationsSync: create-update

View File

@@ -1,20 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: guestbook-ui
spec:
replicas: 1
revisionHistoryLimit: 3
selector:
matchLabels:
app: guestbook-ui
template:
metadata:
labels:
app: guestbook-ui
spec:
containers:
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
name: guestbook-ui
ports:
- containerPort: 80

View File

@@ -1,10 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: guestbook-ui
spec:
ports:
- port: 80
targetPort: 80
selector:
app: guestbook-ui

View File

@@ -1,20 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: guestbook-ui
spec:
replicas: 1
revisionHistoryLimit: 3
selector:
matchLabels:
app: guestbook-ui
template:
metadata:
labels:
app: guestbook-ui
spec:
containers:
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
name: guestbook-ui
ports:
- containerPort: 80

View File

@@ -1,10 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: guestbook-ui
spec:
ports:
- port: 80
targetPort: 80
selector:
app: guestbook-ui

View File

@@ -4,7 +4,6 @@ metadata:
name: guestbook name: guestbook
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- clusters: {} - clusters: {}
template: template:

View File

@@ -4,7 +4,6 @@ metadata:
name: book-import name: book-import
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- clusterDecisionResource: - clusterDecisionResource:
configMapRef: ocm-placement configMapRef: ocm-placement

View File

@@ -8,7 +8,6 @@ metadata:
name: guestbook name: guestbook
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- clusters: {} - clusters: {}
template: template:

View File

@@ -27,7 +27,6 @@ metadata:
name: cluster-addons name: cluster-addons
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- git: - git:
repoURL: https://github.com/infra-team/cluster-deployments.git repoURL: https://github.com/infra-team/cluster-deployments.git

View File

@@ -38,7 +38,6 @@ metadata:
name: guestbook name: guestbook
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- git: - git:
repoURL: https://github.com/infra-team/cluster-deployments.git repoURL: https://github.com/infra-team/cluster-deployments.git

View File

@@ -51,7 +51,6 @@ metadata:
name: guestbook name: guestbook
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- git: - git:
repoURL: https://github.com/infra-team/cluster-deployments.git repoURL: https://github.com/infra-team/cluster-deployments.git

View File

@@ -5,7 +5,6 @@ metadata:
name: guestbook name: guestbook
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- list: - list:
elements: elements:

View File

@@ -8,7 +8,6 @@ metadata:
name: guestbook name: guestbook
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- list: - list:
elements: elements:

View File

@@ -5,7 +5,6 @@ metadata:
namespace: argocd namespace: argocd
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- git: - git:
repoURL: https://github.com/argoproj/argo-cd.git repoURL: https://github.com/argoproj/argo-cd.git

View File

@@ -5,7 +5,6 @@ metadata:
namespace: argocd namespace: argocd
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- git: - git:
repoURL: https://github.com/argoproj/argo-cd.git repoURL: https://github.com/argoproj/argo-cd.git

View File

@@ -4,7 +4,6 @@ metadata:
name: guestbook name: guestbook
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- git: - git:
repoURL: https://github.com/argoproj/argo-cd.git repoURL: https://github.com/argoproj/argo-cd.git

View File

@@ -1,14 +0,0 @@
key:
components:
- name: component1
chart: podinfo
version: "6.3.2"
releaseName: component1
repoUrl: "https://stefanprodan.github.io/podinfo"
namespace: component1
- name: component2
chart: podinfo
version: "6.3.3"
releaseName: component2
repoUrl: "ghcr.io/stefanprodan/charts"
namespace: component2

View File

@@ -4,7 +4,6 @@ metadata:
name: guestbook name: guestbook
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- list: - list:
elements: elements:

View File

@@ -8,7 +8,6 @@ metadata:
name: cluster-git name: cluster-git
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- matrix: - matrix:
generators: generators:

View File

@@ -8,7 +8,6 @@ metadata:
name: list-git name: list-git
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- matrix: - matrix:
generators: generators:

View File

@@ -5,7 +5,6 @@ metadata:
namespace: argocd namespace: argocd
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- matrix: - matrix:
generators: generators:

View File

@@ -13,7 +13,6 @@ metadata:
name: matrix-and-union-in-matrix name: matrix-and-union-in-matrix
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- matrix: - matrix:
generators: generators:

View File

@@ -4,7 +4,6 @@ metadata:
name: merge-clusters-and-list name: merge-clusters-and-list
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- merge: - merge:
mergeKeys: mergeKeys:

View File

@@ -4,7 +4,6 @@ metadata:
name: merge-two-matrixes name: merge-two-matrixes
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- merge: - merge:
mergeKeys: mergeKeys:

View File

@@ -4,7 +4,6 @@ metadata:
name: myapp name: myapp
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- pullRequest: - pullRequest:
github: github:
@@ -24,8 +23,6 @@ spec:
template: template:
metadata: metadata:
name: 'myapp-{{ .branch }}-{{ .number }}' name: 'myapp-{{ .branch }}-{{ .number }}'
labels:
key1: '{{ index .labels 0 }}'
spec: spec:
source: source:
repoURL: 'https://github.com/myorg/myrepo.git' repoURL: 'https://github.com/myorg/myrepo.git'

View File

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

View File

@@ -4,7 +4,6 @@ metadata:
name: guestbook name: guestbook
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- scmProvider: - scmProvider:
github: github:

View File

@@ -8,7 +8,6 @@ metadata:
name: guestbook name: guestbook
spec: spec:
goTemplate: true goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators: generators:
- list: - list:
elements: elements:

View File

@@ -15,10 +15,14 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v2/applicationset/utils" "github.com/argoproj/argo-cd/v2/applicationset/utils"
"github.com/argoproj/argo-cd/v2/common"
argoappsetv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoappsetv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
const (
ArgoCDSecretTypeLabel = "argocd.argoproj.io/secret-type"
ArgoCDSecretTypeCluster = "cluster"
)
var _ Generator = (*ClusterGenerator)(nil) var _ Generator = (*ClusterGenerator)(nil)
// ClusterGenerator generates Applications for some or all clusters registered with ArgoCD. // ClusterGenerator generates Applications for some or all clusters registered with ArgoCD.
@@ -34,6 +38,7 @@ type ClusterGenerator struct {
var render = &utils.Render{} var render = &utils.Render{}
func NewClusterGenerator(c client.Client, ctx context.Context, 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) settingsManager := settings.NewSettingsManager(ctx, clientset, namespace)
g := &ClusterGenerator{ g := &ClusterGenerator{
@@ -48,7 +53,7 @@ func NewClusterGenerator(c client.Client, ctx context.Context, clientset kuberne
// GetRequeueAfter never requeue the cluster generator because the `clusterSecretEventHandler` will requeue the appsets // GetRequeueAfter never requeue the cluster generator because the `clusterSecretEventHandler` will requeue the appsets
// when the cluster secrets change // when the cluster secrets change
func (g *ClusterGenerator) GetRequeueAfter(_ *argoappsetv1alpha1.ApplicationSetGenerator) time.Duration { func (g *ClusterGenerator) GetRequeueAfter(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator) time.Duration {
return NoRequeueAfter return NoRequeueAfter
} }
@@ -56,8 +61,9 @@ func (g *ClusterGenerator) GetTemplate(appSetGenerator *argoappsetv1alpha1.Appli
return &appSetGenerator.Clusters.Template return &appSetGenerator.Clusters.Template
} }
func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator, appSet *argoappsetv1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) { func (g *ClusterGenerator) GenerateParams(
logCtx := log.WithField("applicationset", appSet.GetName()).WithField("namespace", appSet.GetNamespace()) appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator, appSet *argoappsetv1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil { if appSetGenerator == nil {
return nil, EmptyAppSetGeneratorError return nil, EmptyAppSetGeneratorError
} }
@@ -73,51 +79,44 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
// ListCluster from Argo CD's util/db package 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) clustersFromArgoCD, err := utils.ListClusters(g.ctx, g.clientset, g.namespace)
if err != nil { if err != nil {
return nil, fmt.Errorf("error listing clusters: %w", err) return nil, err
} }
if clustersFromArgoCD == nil { if clustersFromArgoCD == nil {
return nil, nil return nil, nil
} }
clusterSecrets, err := g.getSecretsByClusterName(logCtx, appSetGenerator) clusterSecrets, err := g.getSecretsByClusterName(appSetGenerator)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting cluster secrets: %w", err) return nil, err
} }
res := []map[string]interface{}{} res := []map[string]interface{}{}
secretsFound := []corev1.Secret{} secretsFound := []corev1.Secret{}
isFlatMode := appSetGenerator.Clusters.FlatList
logCtx.Debugf("Using flat mode = %t for cluster generator", isFlatMode)
clustersParams := make([]map[string]interface{}, 0)
for _, cluster := range clustersFromArgoCD.Items { for _, cluster := range clustersFromArgoCD.Items {
// If there is a secret for this cluster, then it's a non-local cluster, so it will be // If there is a secret for this cluster, then it's a non-local cluster, so it will be
// handled by the next step. // handled by the next step.
if secretForCluster, exists := clusterSecrets[cluster.Name]; exists { if secretForCluster, exists := clusterSecrets[cluster.Name]; exists {
secretsFound = append(secretsFound, secretForCluster) secretsFound = append(secretsFound, secretForCluster)
} else if !ignoreLocalClusters { } else if !ignoreLocalClusters {
// If there is no secret for the cluster, it's the local cluster, so handle it here. // If there is no secret for the cluster, it's the local cluster, so handle it here.
params := map[string]interface{}{} params := map[string]interface{}{}
params["name"] = cluster.Name params["name"] = cluster.Name
params["nameNormalized"] = cluster.Name params["nameNormalized"] = cluster.Name
params["server"] = cluster.Server params["server"] = cluster.Server
params["project"] = ""
err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions) err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet)
if err != nil { if err != nil {
return nil, fmt.Errorf("error appending templated values for local cluster: %w", err) return nil, err
} }
if isFlatMode { res = append(res, params)
clustersParams = append(clustersParams, params)
} else {
res = append(res, params)
}
logCtx.WithField("cluster", "local cluster").Info("matched local cluster") log.WithField("cluster", "local cluster").Info("matched local cluster")
} }
} }
@@ -129,13 +128,6 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
params["nameNormalized"] = utils.SanitizeName(string(cluster.Data["name"])) params["nameNormalized"] = utils.SanitizeName(string(cluster.Data["name"]))
params["server"] = string(cluster.Data["server"]) params["server"] = string(cluster.Data["server"])
project, ok := cluster.Data["project"]
if ok {
params["project"] = string(project)
} else {
params["project"] = ""
}
if appSet.Spec.GoTemplate { if appSet.Spec.GoTemplate {
meta := map[string]interface{}{} meta := map[string]interface{}{}
@@ -157,41 +149,71 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
} }
} }
err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions) err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet)
if err != nil { if err != nil {
return nil, fmt.Errorf("error appending templated values for cluster: %w", err) return nil, err
} }
if isFlatMode { res = append(res, params)
clustersParams = append(clustersParams, params)
} else {
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]interface{}{
"clusters": clustersParams,
})
}
return res, nil return res, nil
} }
func (g *ClusterGenerator) getSecretsByClusterName(log *log.Entry, appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator) (map[string]corev1.Secret, error) { func appendTemplatedValues(clusterValues map[string]string, params map[string]interface{}, appSet *argoappsetv1alpha1.ApplicationSet) 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 whitelisted 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]interface{}{}
for key, value := range clusterValues {
result, err := replaceTemplatedString(value, params, appSet)
if err != nil {
return err
}
if appSet.Spec.GoTemplate {
if tmp["values"] == nil {
tmp["values"] = map[string]string{}
}
tmp["values"].(map[string]string)[key] = result
} else {
tmp[fmt.Sprintf("values.%s", key)] = result
}
}
for key, value := range tmp {
params[key] = value
}
return nil
}
func replaceTemplatedString(value string, params map[string]interface{}, appSet *argoappsetv1alpha1.ApplicationSet) (string, error) {
replacedTmplStr, err := render.Replace(value, params, appSet.Spec.GoTemplate)
if err != nil {
return "", err
}
return replacedTmplStr, nil
}
func (g *ClusterGenerator) getSecretsByClusterName(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator) (map[string]corev1.Secret, error) {
// List all Clusters:
clusterSecretList := &corev1.SecretList{} 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) secretSelector, err := metav1.LabelSelectorAsSelector(selector)
if err != nil { 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 { if err := g.Client.List(context.Background(), clusterSecretList, client.MatchingLabelsSelector{Selector: secretSelector}); err != nil {
return nil, err 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{} res := map[string]corev1.Secret{}
@@ -202,4 +224,5 @@ func (g *ClusterGenerator) getSecretsByClusterName(log *log.Entry, appSetGenerat
} }
return res, nil return res, nil
} }

View File

@@ -17,7 +17,6 @@ import (
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
type possiblyErroringFakeCtrlRuntimeClient struct { type possiblyErroringFakeCtrlRuntimeClient struct {
@@ -76,20 +75,18 @@ func TestGenerateParams(t *testing.T) {
}, },
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"config": []byte("{}"), "config": []byte("{}"),
"name": []byte("production_01/west"), "name": []byte("production_01/west"),
"server": []byte("https://production-01.example.com"), "server": []byte("https://production-01.example.com"),
"project": []byte("prod-project"),
}, },
Type: corev1.SecretType("Opaque"), Type: corev1.SecretType("Opaque"),
}, },
} }
testCases := []struct { testCases := []struct {
name string name string
selector metav1.LabelSelector selector metav1.LabelSelector
isFlatMode bool values map[string]string
values map[string]string expected []map[string]interface{}
expected []map[string]interface{}
// clientError is true if a k8s client error should be simulated // clientError is true if a k8s client error should be simulated
clientError bool clientError bool
expectedError error expectedError error
@@ -107,16 +104,13 @@ func TestGenerateParams(t *testing.T) {
"aaa": "{{ server }}", "aaa": "{{ server }}",
"no-op": "{{ this-does-not-exist }}", "no-op": "{{ this-does-not-exist }}",
}, expected: []map[string]interface{}{ }, 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": "{{ 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"},
"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",
"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"},
"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": "{{ 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, clientError: false,
expectedError: nil, expectedError: nil,
@@ -130,15 +124,11 @@ func TestGenerateParams(t *testing.T) {
}, },
values: nil, values: nil,
expected: []map[string]interface{}{ 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",
"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"},
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
},
{ {"name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
"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"},
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
},
}, },
clientError: false, clientError: false,
expectedError: nil, expectedError: nil,
@@ -154,10 +144,8 @@ func TestGenerateParams(t *testing.T) {
"foo": "bar", "foo": "bar",
}, },
expected: []map[string]interface{}{ 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",
"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"},
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
},
}, },
clientError: false, clientError: false,
expectedError: nil, expectedError: nil,
@@ -180,14 +168,10 @@ func TestGenerateParams(t *testing.T) {
"foo": "bar", "foo": "bar",
}, },
expected: []map[string]interface{}{ 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",
"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"},
"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"},
{
"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",
},
}, },
clientError: false, clientError: false,
expectedError: nil, expectedError: nil,
@@ -213,10 +197,8 @@ func TestGenerateParams(t *testing.T) {
"name": "baz", "name": "baz",
}, },
expected: []map[string]interface{}{ 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",
"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"},
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
},
}, },
clientError: false, clientError: false,
expectedError: nil, expectedError: nil,
@@ -227,75 +209,7 @@ func TestGenerateParams(t *testing.T) {
values: nil, values: nil,
expected: nil, expected: nil,
clientError: true, clientError: true,
expectedError: fmt.Errorf("error getting cluster secrets: could not list Secrets"), expectedError: fmt.Errorf("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]interface{}{
{
"clusters": []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": "{{ 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]interface{}{
{
"clusters": []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", "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,
}, },
} }
@@ -306,7 +220,9 @@ func TestGenerateParams(t *testing.T) {
} }
for _, testCase := range testCases { for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
appClientset := kubefake.NewSimpleClientset(runtimeClusters...) appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build() fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
@@ -315,7 +231,7 @@ func TestGenerateParams(t *testing.T) {
testCase.clientError, testCase.clientError,
} }
clusterGenerator := NewClusterGenerator(cl, context.Background(), appClientset, "namespace") var clusterGenerator = NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@@ -328,16 +244,16 @@ func TestGenerateParams(t *testing.T) {
Clusters: &argoprojiov1alpha1.ClusterGenerator{ Clusters: &argoprojiov1alpha1.ClusterGenerator{
Selector: testCase.selector, Selector: testCase.selector,
Values: testCase.values, Values: testCase.values,
FlatList: testCase.isFlatMode,
}, },
}, &applicationSetInfo, nil) }, &applicationSetInfo)
if testCase.expectedError != nil { if testCase.expectedError != nil {
require.EqualError(t, err, testCase.expectedError.Error()) assert.EqualError(t, err, testCase.expectedError.Error())
} else { } else {
require.NoError(t, err) assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, got) assert.ElementsMatch(t, testCase.expected, got)
} }
}) })
} }
} }
@@ -394,11 +310,10 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
}, },
} }
testCases := []struct { testCases := []struct {
name string name string
selector metav1.LabelSelector selector metav1.LabelSelector
values map[string]string values map[string]string
isFlatMode bool expected []map[string]interface{}
expected []map[string]interface{}
// clientError is true if a k8s client error should be simulated // clientError is true if a k8s client error should be simulated
clientError bool clientError bool
expectedError error expectedError error
@@ -420,7 +335,6 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"name": "production_01/west", "name": "production_01/west",
"nameNormalized": "production-01-west", "nameNormalized": "production-01-west",
"server": "https://production-01.example.com", "server": "https://production-01.example.com",
"project": "",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
@@ -446,7 +360,6 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"name": "staging-01", "name": "staging-01",
"nameNormalized": "staging-01", "nameNormalized": "staging-01",
"server": "https://staging-01.example.com", "server": "https://staging-01.example.com",
"project": "",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
@@ -472,7 +385,6 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"nameNormalized": "in-cluster", "nameNormalized": "in-cluster",
"name": "in-cluster", "name": "in-cluster",
"server": "https://kubernetes.default.svc", "server": "https://kubernetes.default.svc",
"project": "",
"values": map[string]string{ "values": map[string]string{
"lol1": "lol", "lol1": "lol",
"lol2": "<no value><no value>", "lol2": "<no value><no value>",
@@ -501,7 +413,6 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"name": "production_01/west", "name": "production_01/west",
"nameNormalized": "production-01-west", "nameNormalized": "production-01-west",
"server": "https://production-01.example.com", "server": "https://production-01.example.com",
"project": "",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
@@ -517,7 +428,6 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"name": "staging-01", "name": "staging-01",
"nameNormalized": "staging-01", "nameNormalized": "staging-01",
"server": "https://staging-01.example.com", "server": "https://staging-01.example.com",
"project": "",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
@@ -548,7 +458,6 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"name": "production_01/west", "name": "production_01/west",
"nameNormalized": "production-01-west", "nameNormalized": "production-01-west",
"server": "https://production-01.example.com", "server": "https://production-01.example.com",
"project": "",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
@@ -589,7 +498,6 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"name": "production_01/west", "name": "production_01/west",
"nameNormalized": "production-01-west", "nameNormalized": "production-01-west",
"server": "https://production-01.example.com", "server": "https://production-01.example.com",
"project": "",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
@@ -608,7 +516,6 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"name": "staging-01", "name": "staging-01",
"nameNormalized": "staging-01", "nameNormalized": "staging-01",
"server": "https://staging-01.example.com", "server": "https://staging-01.example.com",
"project": "",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
@@ -652,7 +559,6 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"name": "staging-01", "name": "staging-01",
"nameNormalized": "staging-01", "nameNormalized": "staging-01",
"server": "https://staging-01.example.com", "server": "https://staging-01.example.com",
"project": "",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
@@ -677,163 +583,7 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
values: nil, values: nil,
expected: nil, expected: nil,
clientError: true, clientError: true,
expectedError: fmt.Errorf("error getting cluster secrets: could not list Secrets"), expectedError: fmt.Errorf("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]interface{}{
{
"clusters": []map[string]interface{}{
{
"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]interface{}{
"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]interface{}{
"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]interface{}{
{
"clusters": []map[string]interface{}{
{
"name": "production_01/west",
"nameNormalized": "production-01-west",
"server": "https://production-01.example.com",
"project": "",
"metadata": map[string]interface{}{
"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]interface{}{
"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,
}, },
} }
@@ -844,7 +594,9 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
} }
for _, testCase := range testCases { for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
appClientset := kubefake.NewSimpleClientset(runtimeClusters...) appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build() fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
@@ -853,7 +605,7 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
testCase.clientError, testCase.clientError,
} }
clusterGenerator := NewClusterGenerator(cl, context.Background(), appClientset, "namespace") var clusterGenerator = NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@@ -868,16 +620,16 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
Clusters: &argoprojiov1alpha1.ClusterGenerator{ Clusters: &argoprojiov1alpha1.ClusterGenerator{
Selector: testCase.selector, Selector: testCase.selector,
Values: testCase.values, Values: testCase.values,
FlatList: testCase.isFlatMode,
}, },
}, &applicationSetInfo, nil) }, &applicationSetInfo)
if testCase.expectedError != nil { if testCase.expectedError != nil {
require.EqualError(t, err, testCase.expectedError.Error()) assert.EqualError(t, err, testCase.expectedError.Error())
} else { } else {
require.NoError(t, err) assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, got) assert.ElementsMatch(t, testCase.expected, got)
} }
}) })
} }
} }

View File

@@ -7,7 +7,6 @@ import (
"time" "time"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v2/util/settings" "github.com/argoproj/argo-cd/v2/util/settings"
@@ -33,6 +32,7 @@ type DuckTypeGenerator struct {
} }
func NewDuckTypeGenerator(ctx context.Context, dynClient dynamic.Interface, clientset kubernetes.Interface, namespace string) Generator { func NewDuckTypeGenerator(ctx context.Context, dynClient dynamic.Interface, clientset kubernetes.Interface, namespace string) Generator {
settingsManager := settings.NewSettingsManager(ctx, clientset, namespace) settingsManager := settings.NewSettingsManager(ctx, clientset, namespace)
g := &DuckTypeGenerator{ g := &DuckTypeGenerator{
@@ -46,20 +46,22 @@ func NewDuckTypeGenerator(ctx context.Context, dynClient dynamic.Interface, clie
} }
func (g *DuckTypeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration { func (g *DuckTypeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
// Return a requeue default of 3 minutes, if no override is specified. // Return a requeue default of 3 minutes, if no override is specified.
if appSetGenerator.ClusterDecisionResource.RequeueAfterSeconds != nil { if appSetGenerator.ClusterDecisionResource.RequeueAfterSeconds != nil {
return time.Duration(*appSetGenerator.ClusterDecisionResource.RequeueAfterSeconds) * time.Second return time.Duration(*appSetGenerator.ClusterDecisionResource.RequeueAfterSeconds) * time.Second
} }
return getDefaultRequeueAfter() return DefaultRequeueAfterSeconds
} }
func (g *DuckTypeGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate { func (g *DuckTypeGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
return &appSetGenerator.ClusterDecisionResource.Template return &appSetGenerator.ClusterDecisionResource.Template
} }
func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) { func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil { if appSetGenerator == nil {
return nil, EmptyAppSetGeneratorError return nil, EmptyAppSetGeneratorError
} }
@@ -72,7 +74,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
// ListCluster from Argo CD's util/db package 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) clustersFromArgoCD, err := utils.ListClusters(g.ctx, g.clientset, g.namespace)
if err != nil { if err != nil {
return nil, fmt.Errorf("error listing clusters: %w", err) return nil, err
} }
if clustersFromArgoCD == nil { if clustersFromArgoCD == nil {
@@ -81,8 +83,9 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
// Read the configMapRef // Read the configMapRef
cm, err := g.clientset.CoreV1().ConfigMaps(g.namespace).Get(g.ctx, appSetGenerator.ClusterDecisionResource.ConfigMapRef, metav1.GetOptions{}) cm, err := g.clientset.CoreV1().ConfigMaps(g.namespace).Get(g.ctx, appSetGenerator.ClusterDecisionResource.ConfigMapRef, metav1.GetOptions{})
if err != nil { if err != nil {
return nil, fmt.Errorf("error reading configMapRef: %w", err) return nil, err
} }
// Extract GVK data for the dynamic client to use // Extract GVK data for the dynamic client to use
@@ -101,6 +104,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
if (resourceName == "" && labelSelector.MatchLabels == nil && labelSelector.MatchExpressions == nil) || if (resourceName == "" && labelSelector.MatchLabels == nil && labelSelector.MatchExpressions == nil) ||
(resourceName != "" && (labelSelector.MatchExpressions != nil || labelSelector.MatchLabels != 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) log.Warningf("You must choose either resourceName=%v, labelSelector.matchLabels=%v or labelSelect.matchExpressions=%v", resourceName, labelSelector.MatchLabels, labelSelector.MatchExpressions)
return nil, fmt.Errorf("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")
} }
@@ -118,14 +122,15 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
log.WithField("listOptions.LabelSelector", listOptions.LabelSelector).Info("selection type") log.WithField("listOptions.LabelSelector", listOptions.LabelSelector).Info("selection type")
} else { } else {
listOptions.FieldSelector = fields.OneTermEqualSelector("metadata.name", resourceName).String() 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") log.WithField("listOptions.FieldSelector", listOptions.FieldSelector).Info("selection type")
} }
duckResources, err := g.dynClient.Resource(duckGVR).Namespace(g.namespace).List(g.ctx, listOptions) duckResources, err := g.dynClient.Resource(duckGVR).Namespace(g.namespace).List(g.ctx, listOptions)
if err != nil { if err != nil {
log.WithField("GVK", duckGVR).Warning("resources were not found") 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 { if len(duckResources.Items) == 0 {
@@ -144,6 +149,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
if matchKey == "" { if matchKey == "" {
log.WithField("matchKey", matchKey).Warning("matchKey not found in " + cm.Name) log.WithField("matchKey", matchKey).Warning("matchKey not found in " + cm.Name)
return nil, nil return nil, nil
} }
res := []map[string]interface{}{} res := []map[string]interface{}{}
@@ -161,6 +167,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
log.WithField("duckResourceStatus", duckResource.Object["status"]).Debug("found resource") log.WithField("duckResourceStatus", duckResource.Object["status"]).Debug("found resource")
clusterDecisions = append(clusterDecisions, duckResource.Object["status"].(map[string]interface{})[statusListKey].([]interface{})...) clusterDecisions = append(clusterDecisions, duckResource.Object["status"].(map[string]interface{})[statusListKey].([]interface{})...)
} }
log.Infof("Number of decisions found: %v", len(clusterDecisions)) log.Infof("Number of decisions found: %v", len(clusterDecisions))
@@ -169,6 +176,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
if len(clusterDecisions) > 0 { if len(clusterDecisions) > 0 {
for _, cluster := range clusterDecisions { for _, cluster := range clusterDecisions {
// generated instance of cluster params // generated instance of cluster params
params := map[string]interface{}{} params := map[string]interface{}{}
@@ -186,6 +194,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
for _, argoCluster := range argoClusters { for _, argoCluster := range argoClusters {
if argoCluster.Name == strMatchValue { if argoCluster.Name == strMatchValue {
log.WithField(matchKey, argoCluster.Name).Info("matched cluster in ArgoCD") log.WithField(matchKey, argoCluster.Name).Info("matched cluster in ArgoCD")
params["name"] = argoCluster.Name params["name"] = argoCluster.Name
params["server"] = argoCluster.Server params["server"] = argoCluster.Server
@@ -193,6 +202,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
found = true found = true
break // Stop looking break // Stop looking
} }
} }
if !found { if !found {
@@ -218,7 +228,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
res = append(res, params) res = append(res, params)
} }
} else { } else {
log.Warningf("clusterDecisionResource status.%s missing", statusListKey) log.Warningf("clusterDecisionResource status." + statusListKey + " missing")
return nil, nil return nil, nil
} }

View File

@@ -3,10 +3,8 @@ package generators
import ( import (
"context" "context"
"fmt" "fmt"
"testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -17,13 +15,13 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"testing"
) )
const ( const resourceApiVersion = "mallard.io/v1"
resourceApiVersion = "mallard.io/v1" const resourceKind = "ducks"
resourceKind = "ducks" const resourceName = "quak"
resourceName = "quak"
)
func TestGenerateParamsForDuckType(t *testing.T) { func TestGenerateParamsForDuckType(t *testing.T) {
clusters := []client.Object{ clusters := []client.Object{
@@ -282,7 +280,9 @@ func TestGenerateParamsForDuckType(t *testing.T) {
} }
for _, testCase := range testCases { for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
appClientset := kubefake.NewSimpleClientset(append(runtimeClusters, configMap)...) appClientset := kubefake.NewSimpleClientset(append(runtimeClusters, configMap)...)
gvrToListKind := map[schema.GroupVersionResource]string{{ gvrToListKind := map[schema.GroupVersionResource]string{{
@@ -293,7 +293,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, testCase.resource) fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, testCase.resource)
duckTypeGenerator := NewDuckTypeGenerator(context.Background(), fakeDynClient, appClientset, "namespace") var duckTypeGenerator = NewDuckTypeGenerator(context.Background(), fakeDynClient, appClientset, "namespace")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@@ -309,12 +309,12 @@ func TestGenerateParamsForDuckType(t *testing.T) {
LabelSelector: testCase.labelSelector, LabelSelector: testCase.labelSelector,
Values: testCase.values, Values: testCase.values,
}, },
}, &applicationSetInfo, nil) }, &applicationSetInfo)
if testCase.expectedError != nil { if testCase.expectedError != nil {
require.EqualError(t, err, testCase.expectedError.Error()) assert.EqualError(t, err, testCase.expectedError.Error())
} else { } else {
require.NoError(t, err) assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, got) assert.ElementsMatch(t, testCase.expected, got)
} }
}) })
@@ -578,7 +578,9 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
} }
for _, testCase := range testCases { for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
appClientset := kubefake.NewSimpleClientset(append(runtimeClusters, configMap)...) appClientset := kubefake.NewSimpleClientset(append(runtimeClusters, configMap)...)
gvrToListKind := map[schema.GroupVersionResource]string{{ gvrToListKind := map[schema.GroupVersionResource]string{{
@@ -589,7 +591,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, testCase.resource) fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, testCase.resource)
duckTypeGenerator := NewDuckTypeGenerator(context.Background(), fakeDynClient, appClientset, "namespace") var duckTypeGenerator = NewDuckTypeGenerator(context.Background(), fakeDynClient, appClientset, "namespace")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@@ -607,12 +609,12 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
LabelSelector: testCase.labelSelector, LabelSelector: testCase.labelSelector,
Values: testCase.values, Values: testCase.values,
}, },
}, &applicationSetInfo, nil) }, &applicationSetInfo)
if testCase.expectedError != nil { if testCase.expectedError != nil {
require.EqualError(t, err, testCase.expectedError.Error()) assert.EqualError(t, err, testCase.expectedError.Error())
} else { } else {
require.NoError(t, err) assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, got) assert.ElementsMatch(t, testCase.expected, got)
} }
}) })

View File

@@ -1,14 +1,11 @@
package generators package generators
import ( import (
"fmt"
"reflect" "reflect"
"github.com/jeremywohl/flatten"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v2/applicationset/utils" "github.com/argoproj/argo-cd/v2/applicationset/utils"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
@@ -27,13 +24,10 @@ type TransformResult struct {
} }
// Transform a spec generator to list of paramSets and a template // Transform a spec generator to list of paramSets and a template
func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, allGenerators map[string]Generator, baseTemplate argoprojiov1alpha1.ApplicationSetTemplate, appSet *argoprojiov1alpha1.ApplicationSet, genParams map[string]interface{}, 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 selector, err := metav1.LabelSelectorAsSelector(requestedGenerator.Selector)
// verbatim from that package, with the difference that we do not have any restrictions on label values. This is done
// so that, among other things, we can match on cluster urls.
selector, err := utils.LabelSelectorAsSelector(requestedGenerator.Selector)
if err != nil { if err != nil {
return nil, fmt.Errorf("error parsing label selector: %w", err) return nil, err
} }
res := []TransformResult{} res := []TransformResult{}
@@ -54,7 +48,7 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
} }
var params []map[string]interface{} var params []map[string]interface{}
if len(genParams) != 0 { if len(genParams) != 0 {
tempInterpolatedGenerator, err := InterpolateGenerator(&requestedGenerator, genParams, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions) tempInterpolatedGenerator, err := InterpolateGenerator(&requestedGenerator, genParams, appSet.Spec.GoTemplate)
interpolatedGenerator = &tempInterpolatedGenerator interpolatedGenerator = &tempInterpolatedGenerator
if err != nil { if err != nil {
log.WithError(err).WithField("genParams", genParams). log.WithError(err).WithField("genParams", genParams).
@@ -65,7 +59,7 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
continue continue
} }
} }
params, err = g.GenerateParams(interpolatedGenerator, appSet, client) params, err = g.GenerateParams(interpolatedGenerator, appSet)
if err != nil { if err != nil {
log.WithError(err).WithField("generator", g). log.WithError(err).WithField("generator", g).
Error("error generating params") Error("error generating params")
@@ -76,17 +70,8 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
} }
var filterParams []map[string]interface{} var filterParams []map[string]interface{}
for _, param := range params { for _, param := range params {
flatParam, err := flattenParameters(param)
if err != nil {
log.WithError(err).WithField("generator", g).
Error("error flattening params")
if firstError == nil {
firstError = err
}
continue
}
if requestedGenerator.Selector != nil && !selector.Matches(labels.Set(flatParam)) { if requestedGenerator.Selector != nil && !selector.Matches(labels.Set(keepOnlyStringValues(param))) {
continue continue
} }
filterParams = append(filterParams, param) filterParams = append(filterParams, param)
@@ -101,6 +86,18 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
return res, firstError return res, firstError
} }
func keepOnlyStringValues(in map[string]interface{}) map[string]string {
var out map[string]string = map[string]string{}
for key, value := range in {
if _, ok := value.(string); ok {
out[key] = value.(string)
}
}
return out
}
func GetRelevantGenerators(requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, generators map[string]Generator) []Generator { func GetRelevantGenerators(requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, generators map[string]Generator) []Generator {
var res []Generator var res []Generator
@@ -123,20 +120,6 @@ func GetRelevantGenerators(requestedGenerator *argoprojiov1alpha1.ApplicationSet
return res return res
} }
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)
}
out := make(map[string]string, len(flat))
for k, v := range flat {
out[k] = fmt.Sprintf("%v", v)
}
return out, nil
}
func mergeGeneratorTemplate(g Generator, requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetTemplate argoprojiov1alpha1.ApplicationSetTemplate) (argoprojiov1alpha1.ApplicationSetTemplate, error) { func mergeGeneratorTemplate(g Generator, requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetTemplate argoprojiov1alpha1.ApplicationSetTemplate) (argoprojiov1alpha1.ApplicationSetTemplate, error) {
// Make a copy of the value from `GetTemplate()` before merge, rather than copying directly into // Make a copy of the value from `GetTemplate()` before merge, rather than copying directly into
// the provided parameter (which will touch the original resource object returned by client-go) // the provided parameter (which will touch the original resource object returned by client-go)
@@ -149,26 +132,13 @@ func mergeGeneratorTemplate(g Generator, requestedGenerator *argoprojiov1alpha1.
// InterpolateGenerator allows interpolating the matrix's 2nd child generator with values from the 1st child generator // 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. // "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]interface{}, useGoTemplate bool, goTemplateOptions []string) (argoprojiov1alpha1.ApplicationSetGenerator, error) { func InterpolateGenerator(requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, params map[string]interface{}, useGoTemplate bool) (argoprojiov1alpha1.ApplicationSetGenerator, error) {
render := utils.Render{} render := utils.Render{}
interpolatedGenerator, err := render.RenderGeneratorParams(requestedGenerator, params, useGoTemplate, goTemplateOptions) interpolatedGenerator, err := render.RenderGeneratorParams(requestedGenerator, params, useGoTemplate)
if err != nil { if err != nil {
log.WithError(err).WithField("interpolatedGenerator", interpolatedGenerator).Error("error interpolating generator with other generator's parameter") log.WithError(err).WithField("interpolatedGenerator", interpolatedGenerator).Error("error interpolating generator with other generator's parameter")
return argoprojiov1alpha1.ApplicationSetGenerator{}, err return *interpolatedGenerator, err
} }
return *interpolatedGenerator, nil 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,8 +10,7 @@ import (
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks" testutils "github.com/argoproj/argo-cd/v2/applicationset/utils/test"
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
@@ -20,6 +19,8 @@ import (
kubefake "k8s.io/client-go/kubernetes/fake" kubefake "k8s.io/client-go/kubernetes/fake"
crtclient "sigs.k8s.io/controller-runtime/pkg/client" crtclient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/client/fake"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
func TestMatchValues(t *testing.T) { func TestMatchValues(t *testing.T) {
@@ -65,195 +66,36 @@ func TestMatchValues(t *testing.T) {
for _, testCase := range testCases { for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
listGenerator := NewListGenerator() var listGenerator = NewListGenerator()
data := map[string]Generator{ var data = map[string]Generator{
"List": listGenerator, "List": listGenerator,
} }
applicationSetInfo := argov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "set", Name: "set",
}, },
Spec: argov1alpha1.ApplicationSetSpec{ Spec: argoprojiov1alpha1.ApplicationSetSpec{},
GoTemplate: false,
},
} }
results, err := Transform(argov1alpha1.ApplicationSetGenerator{ results, err := Transform(argoprojiov1alpha1.ApplicationSetGenerator{
Selector: testCase.selector, Selector: testCase.selector,
List: &argov1alpha1.ListGenerator{ List: &argoprojiov1alpha1.ListGenerator{
Elements: testCase.elements, Elements: testCase.elements,
Template: emptyTemplate(), Template: emptyTemplate(),
}, }},
},
data, data,
emptyTemplate(), emptyTemplate(),
&applicationSetInfo, nil, nil) &applicationSetInfo, nil)
require.NoError(t, err) assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, results[0].Params) assert.ElementsMatch(t, testCase.expected, results[0].Params)
}) })
} }
} }
func TestMatchValuesGoTemplate(t *testing.T) { func emptyTemplate() argoprojiov1alpha1.ApplicationSetTemplate {
testCases := []struct { return argoprojiov1alpha1.ApplicationSetTemplate{
name string
elements []apiextensionsv1.JSON
selector *metav1.LabelSelector
expected []map[string]interface{}
}{
{
name: "no filter",
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
selector: &metav1.LabelSelector{},
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url"}},
},
{
name: "nil",
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
selector: nil,
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url"}},
},
{
name: "values.foo should be foo but is ignore element",
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}},
selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"values.foo": "foo",
},
},
expected: []map[string]interface{}{},
},
{
name: "values.foo should be bar",
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}},
selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"values.foo": "bar",
},
},
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url", "values": map[string]interface{}{"foo": "bar"}}},
},
{
name: "values.0 should be bar",
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":["bar"]}`)}},
selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"values.0": "bar",
},
},
expected: []map[string]interface{}{{"cluster": "cluster", "url": "url", "values": []interface{}{"bar"}}},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
listGenerator := NewListGenerator()
data := map[string]Generator{
"List": listGenerator,
}
applicationSetInfo := argov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argov1alpha1.ApplicationSetSpec{
GoTemplate: true,
},
}
results, err := Transform(argov1alpha1.ApplicationSetGenerator{
Selector: testCase.selector,
List: &argov1alpha1.ListGenerator{
Elements: testCase.elements,
Template: emptyTemplate(),
},
},
data,
emptyTemplate(),
&applicationSetInfo, nil, nil)
require.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, results[0].Params)
})
}
}
func TestTransForm(t *testing.T) {
testCases := []struct {
name string
selector *metav1.LabelSelector
expected []map[string]interface{}
}{
{
name: "server filter",
selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"server": "https://production-01.example.com"},
},
expected: []map[string]interface{}{{
"metadata.annotations.foo.argoproj.io": "production",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster",
"metadata.labels.environment": "production",
"metadata.labels.org": "bar",
"name": "production_01/west",
"nameNormalized": "production-01-west",
"server": "https://production-01.example.com",
"project": "",
}},
},
{
name: "server filter with long url",
selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"server": "https://some-really-long-url-that-will-exceed-63-characters.com"},
},
expected: []map[string]interface{}{{
"metadata.annotations.foo.argoproj.io": "production",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster",
"metadata.labels.environment": "production",
"metadata.labels.org": "bar",
"name": "some-really-long-server-url",
"nameNormalized": "some-really-long-server-url",
"server": "https://some-really-long-url-that-will-exceed-63-characters.com",
"project": "",
}},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
testGenerators := map[string]Generator{
"Clusters": getMockClusterGenerator(),
}
applicationSetInfo := argov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argov1alpha1.ApplicationSetSpec{},
}
results, err := Transform(
argov1alpha1.ApplicationSetGenerator{
Selector: testCase.selector,
Clusters: &argov1alpha1.ClusterGenerator{
Selector: metav1.LabelSelector{},
Template: argov1alpha1.ApplicationSetTemplate{},
Values: nil,
},
},
testGenerators,
emptyTemplate(),
&applicationSetInfo, nil, nil)
require.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, results[0].Params)
})
}
}
func emptyTemplate() argov1alpha1.ApplicationSetTemplate {
return argov1alpha1.ApplicationSetTemplate{
Spec: argov1alpha1.ApplicationSpec{ Spec: argov1alpha1.ApplicationSpec{
Project: "project", Project: "project",
}, },
@@ -310,35 +152,8 @@ func getMockClusterGenerator() Generator {
}, },
Type: corev1.SecretType("Opaque"), Type: corev1.SecretType("Opaque"),
}, },
&corev1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: "Secret",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "some-really-long-server-url",
Namespace: "namespace",
Labels: map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
"environment": "production",
"org": "bar",
},
Annotations: map[string]string{
"foo.argoproj.io": "production",
},
},
Data: map[string][]byte{
"config": []byte("{}"),
"name": []byte("some-really-long-server-url"),
"server": []byte("https://some-really-long-url-that-will-exceed-63-characters.com"),
},
Type: corev1.SecretType("Opaque"),
},
} }
runtimeClusters := []runtime.Object{} runtimeClusters := []runtime.Object{}
for _, clientCluster := range clusters {
runtimeClusters = append(runtimeClusters, clientCluster)
}
appClientset := kubefake.NewSimpleClientset(runtimeClusters...) appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build() fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
@@ -346,13 +161,14 @@ func getMockClusterGenerator() Generator {
} }
func getMockGitGenerator() Generator { func getMockGitGenerator() Generator {
argoCDServiceMock := mocks.Repos{} argoCDServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return([]string{"app1", "app2", "app_3", "p1/app4"}, nil) argoCDServiceMock.Mock.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 return gitGenerator
} }
func TestGetRelevantGenerators(t *testing.T) { func TestGetRelevantGenerators(t *testing.T) {
testGenerators := map[string]Generator{ testGenerators := map[string]Generator{
"Clusters": getMockClusterGenerator(), "Clusters": getMockClusterGenerator(),
"Git": getMockGitGenerator(), "Git": getMockGitGenerator(),
@@ -362,20 +178,19 @@ func TestGetRelevantGenerators(t *testing.T) {
testGenerators["Merge"] = NewMergeGenerator(testGenerators) testGenerators["Merge"] = NewMergeGenerator(testGenerators)
testGenerators["List"] = NewListGenerator() testGenerators["List"] = NewListGenerator()
requestedGenerator := &argov1alpha1.ApplicationSetGenerator{ requestedGenerator := &argoprojiov1alpha1.ApplicationSetGenerator{
List: &argov1alpha1.ListGenerator{ List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}}, Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}},
}, }}
}
relevantGenerators := GetRelevantGenerators(requestedGenerator, testGenerators) relevantGenerators := GetRelevantGenerators(requestedGenerator, testGenerators)
assert.Len(t, relevantGenerators, 1) assert.Len(t, relevantGenerators, 1)
assert.IsType(t, &ListGenerator{}, relevantGenerators[0]) assert.IsType(t, &ListGenerator{}, relevantGenerators[0])
requestedGenerator = &argov1alpha1.ApplicationSetGenerator{ requestedGenerator = &argoprojiov1alpha1.ApplicationSetGenerator{
Clusters: &argov1alpha1.ClusterGenerator{ Clusters: &argoprojiov1alpha1.ClusterGenerator{
Selector: metav1.LabelSelector{}, Selector: metav1.LabelSelector{},
Template: argov1alpha1.ApplicationSetTemplate{}, Template: argoprojiov1alpha1.ApplicationSetTemplate{},
Values: nil, Values: nil,
}, },
} }
@@ -384,14 +199,14 @@ func TestGetRelevantGenerators(t *testing.T) {
assert.Len(t, relevantGenerators, 1) assert.Len(t, relevantGenerators, 1)
assert.IsType(t, &ClusterGenerator{}, relevantGenerators[0]) assert.IsType(t, &ClusterGenerator{}, relevantGenerators[0])
requestedGenerator = &argov1alpha1.ApplicationSetGenerator{ requestedGenerator = &argoprojiov1alpha1.ApplicationSetGenerator{
Git: &argov1alpha1.GitGenerator{ Git: &argoprojiov1alpha1.GitGenerator{
RepoURL: "", RepoURL: "",
Directories: nil, Directories: nil,
Files: nil, Files: nil,
Revision: "", Revision: "",
RequeueAfterSeconds: nil, RequeueAfterSeconds: nil,
Template: argov1alpha1.ApplicationSetTemplate{}, Template: argoprojiov1alpha1.ApplicationSetTemplate{},
}, },
} }
@@ -401,16 +216,15 @@ func TestGetRelevantGenerators(t *testing.T) {
} }
func TestInterpolateGenerator(t *testing.T) { func TestInterpolateGenerator(t *testing.T) {
requestedGenerator := &argov1alpha1.ApplicationSetGenerator{ requestedGenerator := &argoprojiov1alpha1.ApplicationSetGenerator{
Clusters: &argov1alpha1.ClusterGenerator{ Clusters: &argoprojiov1alpha1.ClusterGenerator{
Selector: metav1.LabelSelector{ Selector: metav1.LabelSelector{
MatchLabels: map[string]string{ MatchLabels: map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
"path-basename": "{{path.basename}}", "path-basename": "{{path.basename}}",
"path-zero": "{{path[0]}}", "path-zero": "{{path[0]}}",
"path-full": "{{path}}", "path-full": "{{path}}",
}, }},
},
}, },
} }
gitGeneratorParams := map[string]interface{}{ gitGeneratorParams := map[string]interface{}{
@@ -420,7 +234,7 @@ func TestInterpolateGenerator(t *testing.T) {
"path[1]": "p2", "path[1]": "p2",
"path.basenameNormalized": "app3", "path.basenameNormalized": "app3",
} }
interpolatedGenerator, err := InterpolateGenerator(requestedGenerator, gitGeneratorParams, false, nil) interpolatedGenerator, err := InterpolateGenerator(requestedGenerator, gitGeneratorParams, false)
if err != nil { if err != nil {
log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator") log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator")
return return
@@ -429,23 +243,23 @@ func TestInterpolateGenerator(t *testing.T) {
assert.Equal(t, "p1", interpolatedGenerator.Clusters.Selector.MatchLabels["path-zero"]) assert.Equal(t, "p1", interpolatedGenerator.Clusters.Selector.MatchLabels["path-zero"])
assert.Equal(t, "p1/p2/app3", interpolatedGenerator.Clusters.Selector.MatchLabels["path-full"]) assert.Equal(t, "p1/p2/app3", interpolatedGenerator.Clusters.Selector.MatchLabels["path-full"])
fileNamePath := argov1alpha1.GitFileGeneratorItem{ fileNamePath := argoprojiov1alpha1.GitFileGeneratorItem{
Path: "{{name}}", Path: "{{name}}",
} }
fileServerPath := argov1alpha1.GitFileGeneratorItem{ fileServerPath := argoprojiov1alpha1.GitFileGeneratorItem{
Path: "{{server}}", Path: "{{server}}",
} }
requestedGenerator = &argov1alpha1.ApplicationSetGenerator{ requestedGenerator = &argoprojiov1alpha1.ApplicationSetGenerator{
Git: &argov1alpha1.GitGenerator{ Git: &argoprojiov1alpha1.GitGenerator{
Files: append([]argov1alpha1.GitFileGeneratorItem{}, fileNamePath, fileServerPath), Files: append([]argoprojiov1alpha1.GitFileGeneratorItem{}, fileNamePath, fileServerPath),
Template: argov1alpha1.ApplicationSetTemplate{}, Template: argoprojiov1alpha1.ApplicationSetTemplate{},
}, },
} }
clusterGeneratorParams := map[string]interface{}{ clusterGeneratorParams := map[string]interface{}{
"name": "production_01/west", "server": "https://production-01.example.com", "name": "production_01/west", "server": "https://production-01.example.com",
} }
interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, false, nil) interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, false)
if err != nil { if err != nil {
log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator") log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator")
return return
@@ -455,8 +269,8 @@ func TestInterpolateGenerator(t *testing.T) {
} }
func TestInterpolateGenerator_go(t *testing.T) { func TestInterpolateGenerator_go(t *testing.T) {
requestedGenerator := &argov1alpha1.ApplicationSetGenerator{ requestedGenerator := &argoprojiov1alpha1.ApplicationSetGenerator{
Clusters: &argov1alpha1.ClusterGenerator{ Clusters: &argoprojiov1alpha1.ClusterGenerator{
Selector: metav1.LabelSelector{ Selector: metav1.LabelSelector{
MatchLabels: map[string]string{ MatchLabels: map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
@@ -464,8 +278,7 @@ func TestInterpolateGenerator_go(t *testing.T) {
"path-zero": "{{index .path.segments 0}}", "path-zero": "{{index .path.segments 0}}",
"path-full": "{{.path.path}}", "path-full": "{{.path.path}}",
"kubernetes.io/environment": `{{default "foo" .my_label}}`, "kubernetes.io/environment": `{{default "foo" .my_label}}`,
}, }},
},
}, },
} }
gitGeneratorParams := map[string]interface{}{ gitGeneratorParams := map[string]interface{}{
@@ -474,7 +287,7 @@ func TestInterpolateGenerator_go(t *testing.T) {
"segments": []string{"p1", "p2", "app3"}, "segments": []string{"p1", "p2", "app3"},
}, },
} }
interpolatedGenerator, err := InterpolateGenerator(requestedGenerator, gitGeneratorParams, true, nil) interpolatedGenerator, err := InterpolateGenerator(requestedGenerator, gitGeneratorParams, true)
require.NoError(t, err) require.NoError(t, err)
if err != nil { if err != nil {
log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator") log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator")
@@ -484,23 +297,23 @@ func TestInterpolateGenerator_go(t *testing.T) {
assert.Equal(t, "p1", interpolatedGenerator.Clusters.Selector.MatchLabels["path-zero"]) assert.Equal(t, "p1", interpolatedGenerator.Clusters.Selector.MatchLabels["path-zero"])
assert.Equal(t, "p1/p2/app3", interpolatedGenerator.Clusters.Selector.MatchLabels["path-full"]) assert.Equal(t, "p1/p2/app3", interpolatedGenerator.Clusters.Selector.MatchLabels["path-full"])
fileNamePath := argov1alpha1.GitFileGeneratorItem{ fileNamePath := argoprojiov1alpha1.GitFileGeneratorItem{
Path: "{{.name}}", Path: "{{.name}}",
} }
fileServerPath := argov1alpha1.GitFileGeneratorItem{ fileServerPath := argoprojiov1alpha1.GitFileGeneratorItem{
Path: "{{.server}}", Path: "{{.server}}",
} }
requestedGenerator = &argov1alpha1.ApplicationSetGenerator{ requestedGenerator = &argoprojiov1alpha1.ApplicationSetGenerator{
Git: &argov1alpha1.GitGenerator{ Git: &argoprojiov1alpha1.GitGenerator{
Files: append([]argov1alpha1.GitFileGeneratorItem{}, fileNamePath, fileServerPath), Files: append([]argoprojiov1alpha1.GitFileGeneratorItem{}, fileNamePath, fileServerPath),
Template: argov1alpha1.ApplicationSetTemplate{}, Template: argoprojiov1alpha1.ApplicationSetTemplate{},
}, },
} }
clusterGeneratorParams := map[string]interface{}{ clusterGeneratorParams := map[string]interface{}{
"name": "production_01/west", "server": "https://production-01.example.com", "name": "production_01/west", "server": "https://production-01.example.com",
} }
interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, true, nil) interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, true)
if err != nil { if err != nil {
log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator") log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator")
return return
@@ -508,60 +321,3 @@ func TestInterpolateGenerator_go(t *testing.T) {
assert.Equal(t, "production_01/west", interpolatedGenerator.Git.Files[0].Path) assert.Equal(t, "production_01/west", interpolatedGenerator.Git.Files[0].Path)
assert.Equal(t, "https://production-01.example.com", interpolatedGenerator.Git.Files[1].Path) assert.Equal(t, "https://production-01.example.com", interpolatedGenerator.Git.Files[1].Path)
} }
func TestInterpolateGeneratorError(t *testing.T) {
type args struct {
requestedGenerator *argov1alpha1.ApplicationSetGenerator
params map[string]interface{}
useGoTemplate bool
goTemplateOptions []string
}
tests := []struct {
name string
args args
want argov1alpha1.ApplicationSetGenerator
expectedErrStr string
}{
{name: "Empty Gen", args: args{
requestedGenerator: nil,
params: nil,
useGoTemplate: false,
goTemplateOptions: nil,
}, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: "generator is empty"},
{name: "No Params", args: args{
requestedGenerator: &argov1alpha1.ApplicationSetGenerator{},
params: map[string]interface{}{},
useGoTemplate: false,
goTemplateOptions: nil,
}, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: ""},
{name: "Error templating", args: args{
requestedGenerator: &argov1alpha1.ApplicationSetGenerator{Git: &argov1alpha1.GitGenerator{
RepoURL: "foo",
Files: []argov1alpha1.GitFileGeneratorItem{{Path: "bar/"}},
Revision: "main",
Values: map[string]string{
"git_test": "{{ toPrettyJson . }}",
"selection": "{{ default .override .test }}",
"resolved": "{{ index .rmap (default .override .test) }}",
},
}},
params: map[string]interface{}{
"name": "in-cluster",
"override": "foo",
},
useGoTemplate: true,
goTemplateOptions: []string{},
}, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: "failed to replace parameters in generator: failed to execute go template {{ index .rmap (default .override .test) }}: template: :1:3: executing \"\" at <index .rmap (default .override .test)>: error calling index: index of untyped nil"},
}
for _, tt := range tests {
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)
} else {
require.NoError(t, err)
}
assert.Equalf(t, tt.want, got, "InterpolateGenerator(%v, %v, %v, %v)", tt.args.requestedGenerator, tt.args.params, tt.args.useGoTemplate, tt.args.goTemplateOptions)
})
}
}

View File

@@ -11,29 +11,23 @@ import (
"github.com/jeremywohl/flatten" "github.com/jeremywohl/flatten"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"github.com/argoproj/argo-cd/v2/applicationset/services" "github.com/argoproj/argo-cd/v2/applicationset/services"
"github.com/argoproj/argo-cd/v2/applicationset/utils" "github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/gpg"
) )
var _ Generator = (*GitGenerator)(nil) var _ Generator = (*GitGenerator)(nil)
type GitGenerator struct { type GitGenerator struct {
repos services.Repos repos services.Repos
namespace string
} }
func NewGitGenerator(repos services.Repos, namespace string) Generator { func NewGitGenerator(repos services.Repos) Generator {
g := &GitGenerator{ g := &GitGenerator{
repos: repos, repos: repos,
namespace: namespace,
} }
return g return g
} }
@@ -42,16 +36,18 @@ func (g *GitGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Applicati
} }
func (g *GitGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration { func (g *GitGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
// Return a requeue default of 3 minutes, if no default is specified. // Return a requeue default of 3 minutes, if no default is specified.
if appSetGenerator.Git.RequeueAfterSeconds != nil { if appSetGenerator.Git.RequeueAfterSeconds != nil {
return time.Duration(*appSetGenerator.Git.RequeueAfterSeconds) * time.Second 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]interface{}, error) { func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil { if appSetGenerator == nil {
return nil, EmptyAppSetGeneratorError return nil, EmptyAppSetGeneratorError
} }
@@ -60,73 +56,50 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
return nil, EmptyAppSetGeneratorError return nil, EmptyAppSetGeneratorError
} }
noRevisionCache := appSet.RefreshRequired()
verifyCommit := false
// When the project field is templated, the contents of the git repo are required to run the git generator and get the templated value,
// but git generator cannot be called without verifying the commit signature.
// In this case, we skip the signature verification.
if !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()
}
var err error var err error
var res []map[string]interface{} var res []map[string]interface{}
if len(appSetGenerator.Git.Directories) != 0 { if len(appSetGenerator.Git.Directories) != 0 {
res, err = g.generateParamsForGitDirectories(appSetGenerator, noRevisionCache, verifyCommit, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions) res, err = g.generateParamsForGitDirectories(appSetGenerator, appSet.Spec.GoTemplate)
} else if len(appSetGenerator.Git.Files) != 0 { } else if len(appSetGenerator.Git.Files) != 0 {
res, err = g.generateParamsForGitFiles(appSetGenerator, noRevisionCache, verifyCommit, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions) res, err = g.generateParamsForGitFiles(appSetGenerator, appSet.Spec.GoTemplate)
} else { } else {
return nil, EmptyAppSetGeneratorError return nil, EmptyAppSetGeneratorError
} }
if err != nil { if err != nil {
return nil, fmt.Errorf("error generating params from git: %w", err) return nil, err
} }
return res, nil return res, nil
} }
func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit bool, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) { func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool) ([]map[string]interface{}, error) {
// Directories, not files // Directories, not files
allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, noRevisionCache, verifyCommit) allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting directories from repo: %w", err) return nil, err
} }
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"allPaths": allPaths, "allPaths": allPaths,
"total": len(allPaths), "total": len(allPaths),
"repoURL": appSetGenerator.Git.RepoURL, "repoURL": appSetGenerator.Git.RepoURL,
"revision": appSetGenerator.Git.Revision, "revision": appSetGenerator.Git.Revision,
"pathParamPrefix": appSetGenerator.Git.PathParamPrefix,
}).Info("applications result from the repo service") }).Info("applications result from the repo service")
requestedApps := g.filterApps(appSetGenerator.Git.Directories, allPaths) requestedApps := g.filterApps(appSetGenerator.Git.Directories, allPaths)
res, err := g.generateParamsFromApps(requestedApps, appSetGenerator, useGoTemplate, goTemplateOptions) res := g.generateParamsFromApps(requestedApps, appSetGenerator, useGoTemplate)
if err != nil {
return nil, fmt.Errorf("error generating params from apps: %w", err)
}
return res, nil return res, nil
} }
func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit bool, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) { func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool) ([]map[string]interface{}, error) {
// Get all files that match the requested path string, removing duplicates // Get all files that match the requested path string, removing duplicates
allFiles := make(map[string][]byte) allFiles := make(map[string][]byte)
for _, requestedPath := range appSetGenerator.Git.Files { for _, requestedPath := range appSetGenerator.Git.Files {
files, err := g.repos.GetFiles(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, requestedPath.Path, noRevisionCache, verifyCommit) files, err := g.repos.GetFiles(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, requestedPath.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -146,18 +119,21 @@ func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1al
// Generate params from each path, and return // Generate params from each path, and return
res := []map[string]interface{}{} res := []map[string]interface{}{}
for _, path := range allPaths { for _, path := range allPaths {
// A JSON / YAML file path can contain multiple sets of parameters (ie it is an array) // 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) paramsArray, err := g.generateParamsFromGitFile(path, allFiles[path], useGoTemplate)
if err != nil { 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...) for index := range paramsArray {
res = append(res, paramsArray[index])
}
} }
return res, nil return res, nil
} }
func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []byte, values map[string]string, useGoTemplate bool, goTemplateOptions []string, pathParamPrefix string) ([]map[string]interface{}, error) { func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []byte, useGoTemplate bool) ([]map[string]interface{}, error) {
objectsFound := []map[string]interface{}{} objectsFound := []map[string]interface{}{}
// First, we attempt to parse as an array // First, we attempt to parse as an array
@@ -167,17 +143,15 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
singleObj := make(map[string]interface{}) singleObj := make(map[string]interface{})
err = yaml.Unmarshal(fileContent, &singleObj) err = yaml.Unmarshal(fileContent, &singleObj)
if err != nil { 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) 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]interface{}{})
} }
res := []map[string]interface{}{} res := []map[string]interface{}{}
for _, objectFound := range objectsFound { for _, objectFound := range objectsFound {
params := map[string]interface{}{} params := map[string]interface{}{}
if useGoTemplate { if useGoTemplate {
@@ -193,53 +167,41 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
paramPath["basenameNormalized"] = utils.SanitizeName(path.Base(paramPath["path"].(string))) paramPath["basenameNormalized"] = utils.SanitizeName(path.Base(paramPath["path"].(string)))
paramPath["filenameNormalized"] = utils.SanitizeName(path.Base(paramPath["filename"].(string))) paramPath["filenameNormalized"] = utils.SanitizeName(path.Base(paramPath["filename"].(string)))
paramPath["segments"] = strings.Split(paramPath["path"].(string), "/") paramPath["segments"] = strings.Split(paramPath["path"].(string), "/")
if pathParamPrefix != "" { params["path"] = paramPath
params[pathParamPrefix] = map[string]interface{}{"path": paramPath}
} else {
params["path"] = paramPath
}
} else { } else {
flat, err := flatten.Flatten(objectFound, "", flatten.DotStyle) flat, err := flatten.Flatten(objectFound, "", flatten.DotStyle)
if err != nil { if err != nil {
return nil, fmt.Errorf("error flattening object: %w", err) return nil, err
} }
for k, v := range flat { for k, v := range flat {
params[k] = fmt.Sprintf("%v", v) params[k] = fmt.Sprintf("%v", v)
} }
pathParamName := "path" params["path"] = path.Dir(filePath)
if pathParamPrefix != "" { params["path.basename"] = path.Base(params["path"].(string))
pathParamName = pathParamPrefix + "." + pathParamName params["path.filename"] = path.Base(filePath)
} params["path.basenameNormalized"] = utils.SanitizeName(path.Base(params["path"].(string)))
params[pathParamName] = path.Dir(filePath) params["path.filenameNormalized"] = utils.SanitizeName(path.Base(params["path.filename"].(string)))
params[pathParamName+".basename"] = path.Base(params[pathParamName].(string)) for k, v := range strings.Split(params["path"].(string), "/") {
params[pathParamName+".filename"] = path.Base(filePath)
params[pathParamName+".basenameNormalized"] = utils.SanitizeName(path.Base(params[pathParamName].(string)))
params[pathParamName+".filenameNormalized"] = utils.SanitizeName(path.Base(params[pathParamName+".filename"].(string)))
for k, v := range strings.Split(params[pathParamName].(string), "/") {
if len(v) > 0 { if len(v) > 0 {
params[pathParamName+"["+strconv.Itoa(k)+"]"] = v params["path["+strconv.Itoa(k)+"]"] = v
} }
} }
} }
err := appendTemplatedValues(values, params, useGoTemplate, goTemplateOptions)
if err != nil {
return nil, fmt.Errorf("failed to append templated values: %w", err)
}
res = append(res, params) res = append(res, params)
} }
return res, nil 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{} res := []string{}
for _, appPath := range allPaths { for _, appPath := range allPaths {
appInclude := false appInclude := false
appExclude := false appExclude := false
// Iterating over each appPath and check whether directories object has requestedPath that matches the appPath // 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) match, err := path.Match(requestedPath.Path, appPath)
if err != nil { if err != nil {
log.WithError(err).WithField("requestedPath", requestedPath). log.WithError(err).WithField("requestedPath", requestedPath).
@@ -261,9 +223,12 @@ func (g *GitGenerator) filterApps(directories []argoprojiov1alpha1.GitDirectoryG
return res return res
} }
func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) { func (g *GitGenerator) generateParamsFromApps(requestedApps []string, _ *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool) []map[string]interface{} {
// TODO: At some point, the applicationSetGenerator param should be used
res := make([]map[string]interface{}, len(requestedApps)) res := make([]map[string]interface{}, len(requestedApps))
for i, a := range requestedApps { for i, a := range requestedApps {
params := make(map[string]interface{}, 5) params := make(map[string]interface{}, 5)
if useGoTemplate { if useGoTemplate {
@@ -272,33 +237,20 @@ func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGene
paramPath["basename"] = path.Base(a) paramPath["basename"] = path.Base(a)
paramPath["basenameNormalized"] = utils.SanitizeName(path.Base(a)) paramPath["basenameNormalized"] = utils.SanitizeName(path.Base(a))
paramPath["segments"] = strings.Split(paramPath["path"].(string), "/") paramPath["segments"] = strings.Split(paramPath["path"].(string), "/")
if appSetGenerator.Git.PathParamPrefix != "" { params["path"] = paramPath
params[appSetGenerator.Git.PathParamPrefix] = map[string]interface{}{"path": paramPath}
} else {
params["path"] = paramPath
}
} else { } else {
pathParamName := "path" params["path"] = a
if appSetGenerator.Git.PathParamPrefix != "" { params["path.basename"] = path.Base(a)
pathParamName = appSetGenerator.Git.PathParamPrefix + "." + pathParamName params["path.basenameNormalized"] = utils.SanitizeName(path.Base(a))
} for k, v := range strings.Split(params["path"].(string), "/") {
params[pathParamName] = a
params[pathParamName+".basename"] = path.Base(a)
params[pathParamName+".basenameNormalized"] = utils.SanitizeName(path.Base(a))
for k, v := range strings.Split(params[pathParamName].(string), "/") {
if len(v) > 0 { if len(v) > 0 {
params[pathParamName+"["+strconv.Itoa(k)+"]"] = v params["path["+strconv.Itoa(k)+"]"] = v
} }
} }
} }
err := appendTemplatedValues(appSetGenerator.Git.Values, params, useGoTemplate, goTemplateOptions)
if err != nil {
return nil, fmt.Errorf("failed to append templated values: %w", err)
}
res[i] = params res[i] = params
} }
return res, nil return res
} }

View File

@@ -6,188 +6,79 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks" testutils "github.com/argoproj/argo-cd/v2/applicationset/utils/test"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
// type clientSet struct {
// RepoServerServiceClient apiclient.RepoServerServiceClient
// }
// func (c *clientSet) NewRepoServerClient() (io.Closer, apiclient.RepoServerServiceClient, error) {
// return io.NewCloser(func() error { return nil }), c.RepoServerServiceClient, nil
// }
func Test_generateParamsFromGitFile(t *testing.T) { func Test_generateParamsFromGitFile(t *testing.T) {
defaultContent := []byte(` params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
foo: foo:
bar: baz bar: baz
`) `), false)
type args struct { if err != nil {
filePath string t.Fatal(err)
fileContent []byte
values map[string]string
useGoTemplate bool
goTemplateOptions []string
pathParamPrefix string
} }
tests := []struct { assert.Equal(t, []map[string]interface{}{
name string
args args
want []map[string]interface{}
wantErr bool
}{
{ {
name: "empty file returns path parameters", "foo.bar": "baz",
args: args{ "path": "path/dir",
filePath: "path/dir/file_name.yaml", "path.basename": "dir",
fileContent: []byte(""), "path.filename": "file_name.yaml",
values: map[string]string{}, "path.basenameNormalized": "dir",
useGoTemplate: false, "path.filenameNormalized": "file-name.yaml",
}, "path[0]": "path",
want: []map[string]interface{}{ "path[1]": "dir",
{
"path": "path/dir",
"path.basename": "dir",
"path.filename": "file_name.yaml",
"path.basenameNormalized": "dir",
"path.filenameNormalized": "file-name.yaml",
"path[0]": "path",
"path[1]": "dir",
},
},
},
{
name: "invalid json/yaml file returns error",
args: args{
filePath: "path/dir/file_name.yaml",
fileContent: []byte("this is not json or yaml"),
values: map[string]string{},
useGoTemplate: false,
},
wantErr: true,
},
{
name: "file parameters are added to params",
args: args{
filePath: "path/dir/file_name.yaml",
fileContent: defaultContent,
values: map[string]string{},
useGoTemplate: false,
},
want: []map[string]interface{}{
{
"foo.bar": "baz",
"path": "path/dir",
"path.basename": "dir",
"path.filename": "file_name.yaml",
"path.basenameNormalized": "dir",
"path.filenameNormalized": "file-name.yaml",
"path[0]": "path",
"path[1]": "dir",
},
},
},
{
name: "path parameter are prefixed",
args: args{
filePath: "path/dir/file_name.yaml",
fileContent: defaultContent,
values: map[string]string{},
useGoTemplate: false,
pathParamPrefix: "myRepo",
},
want: []map[string]interface{}{
{
"foo.bar": "baz",
"myRepo.path": "path/dir",
"myRepo.path.basename": "dir",
"myRepo.path.filename": "file_name.yaml",
"myRepo.path.basenameNormalized": "dir",
"myRepo.path.filenameNormalized": "file-name.yaml",
"myRepo.path[0]": "path",
"myRepo.path[1]": "dir",
},
},
},
{
name: "file parameters are added to params with go template",
args: args{
filePath: "path/dir/file_name.yaml",
fileContent: defaultContent,
values: map[string]string{},
useGoTemplate: true,
},
want: []map[string]interface{}{
{
"foo": map[string]interface{}{
"bar": "baz",
},
"path": map[string]interface{}{
"path": "path/dir",
"basename": "dir",
"filename": "file_name.yaml",
"basenameNormalized": "dir",
"filenameNormalized": "file-name.yaml",
"segments": []string{
"path",
"dir",
},
},
},
},
},
{
name: "path parameter are prefixed with go template",
args: args{
filePath: "path/dir/file_name.yaml",
fileContent: defaultContent,
values: map[string]string{},
useGoTemplate: true,
pathParamPrefix: "myRepo",
},
want: []map[string]interface{}{
{
"foo": map[string]interface{}{
"bar": "baz",
},
"myRepo": map[string]interface{}{
"path": map[string]interface{}{
"path": "path/dir",
"basename": "dir",
"filename": "file_name.yaml",
"basenameNormalized": "dir",
"filenameNormalized": "file-name.yaml",
"segments": []string{
"path",
"dir",
},
},
},
},
},
}, },
}, params)
}
func Test_generateParamsFromGitFileGoTemplate(t *testing.T) {
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
foo:
bar: baz
`), true)
if err != nil {
t.Fatal(err)
} }
for _, tt := range tests { assert.Equal(t, []map[string]interface{}{
t.Run(tt.name, func(t *testing.T) { {
params, err := (*GitGenerator)(nil).generateParamsFromGitFile(tt.args.filePath, tt.args.fileContent, tt.args.values, tt.args.useGoTemplate, tt.args.goTemplateOptions, tt.args.pathParamPrefix) "foo": map[string]interface{}{
if (err != nil) != tt.wantErr { "bar": "baz",
t.Errorf("GitGenerator.generateParamsFromGitFile() error = %v, wantErr %v", err, tt.wantErr) },
return "path": map[string]interface{}{
} "path": "path/dir",
assert.Equal(t, tt.want, params) "basename": "dir",
}) "filename": "file_name.yaml",
} "basenameNormalized": "dir",
"filenameNormalized": "file-name.yaml",
"segments": []string{
"path",
"dir",
},
},
},
}, params)
} }
func TestGitGenerateParamsFromDirectories(t *testing.T) { func TestGitGenerateParamsFromDirectories(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
directories []argoprojiov1alpha1.GitDirectoryGeneratorItem directories []argoprojiov1alpha1.GitDirectoryGeneratorItem
pathParamPrefix string repoApps []string
repoApps []string repoError error
repoError error expected []map[string]interface{}
values map[string]string expectedError error
expected []map[string]interface{}
expectedError error
}{ }{
{ {
name: "happy flow - created apps", name: "happy flow - created apps",
@@ -206,24 +97,6 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
}, },
expectedError: nil, expectedError: nil,
}, },
{
name: "It prefixes path parameters with PathParamPrefix",
directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
pathParamPrefix: "myRepo",
repoApps: []string{
"app1",
"app2",
"app_3",
"p1/app4",
},
repoError: nil,
expected: []map[string]interface{}{
{"myRepo.path": "app1", "myRepo.path.basename": "app1", "myRepo.path.basenameNormalized": "app1", "myRepo.path[0]": "app1"},
{"myRepo.path": "app2", "myRepo.path.basename": "app2", "myRepo.path.basenameNormalized": "app2", "myRepo.path[0]": "app2"},
{"myRepo.path": "app_3", "myRepo.path.basename": "app_3", "myRepo.path.basenameNormalized": "app-3", "myRepo.path[0]": "app_3"},
},
expectedError: nil,
},
{ {
name: "It filters application according to the paths", name: "It filters application according to the paths",
directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "p1/*"}, {Path: "p1/*/*"}}, directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "p1/*"}, {Path: "p1/*/*"}},
@@ -276,25 +149,6 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
}, },
expectedError: nil, expectedError: nil,
}, },
{
name: "Value variable interpolation",
directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}, {Path: "*/*"}},
repoApps: []string{
"app1",
"p1/app2",
},
repoError: nil,
values: map[string]string{
"foo": "bar",
"aaa": "{{ path[0] }}",
"no-op": "{{ this-does-not-exist }}",
},
expected: []map[string]interface{}{
{"values.foo": "bar", "values.no-op": "{{ this-does-not-exist }}", "values.aaa": "app1", "path": "app1", "path.basename": "app1", "path[0]": "app1", "path.basenameNormalized": "app1"},
{"values.foo": "bar", "values.no-op": "{{ this-does-not-exist }}", "values.aaa": "p1", "path": "p1/app2", "path.basename": "app2", "path[0]": "p1", "path[1]": "app2", "path.basenameNormalized": "app2"},
},
expectedError: nil,
},
{ {
name: "handles empty response from repo server", name: "handles empty response from repo server",
directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}}, directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
@@ -309,7 +163,7 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
repoApps: []string{}, repoApps: []string{},
repoError: fmt.Errorf("error"), repoError: fmt.Errorf("error"),
expected: []map[string]interface{}{}, expected: []map[string]interface{}{},
expectedError: fmt.Errorf("error generating params from git: error getting directories from repo: error"), expectedError: fmt.Errorf("error"),
}, },
} }
@@ -319,11 +173,11 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) { t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel() t.Parallel()
argoCDServiceMock := mocks.Repos{} argoCDServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError) argoCDServiceMock.Mock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
gitGenerator := NewGitGenerator(&argoCDServiceMock, "") var gitGenerator = NewGitGenerator(argoCDServiceMock)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "set", Name: "set",
@@ -331,46 +185,37 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
Spec: argoprojiov1alpha1.ApplicationSetSpec{ Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{ Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
Git: &argoprojiov1alpha1.GitGenerator{ Git: &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL", RepoURL: "RepoURL",
Revision: "Revision", Revision: "Revision",
Directories: testCaseCopy.directories, Directories: testCaseCopy.directories,
PathParamPrefix: testCaseCopy.pathParamPrefix,
Values: testCaseCopy.values,
}, },
}}, }},
}, },
} }
scheme := runtime.NewScheme() got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
appProject := argoprojiov1alpha1.AppProject{}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, client)
if testCaseCopy.expectedError != nil { if testCaseCopy.expectedError != nil {
require.EqualError(t, err, testCaseCopy.expectedError.Error()) assert.EqualError(t, err, testCaseCopy.expectedError.Error())
} else { } else {
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got) assert.Equal(t, testCaseCopy.expected, got)
} }
argoCDServiceMock.AssertExpectations(t) argoCDServiceMock.Mock.AssertExpectations(t)
}) })
} }
} }
func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) { func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
directories []argoprojiov1alpha1.GitDirectoryGeneratorItem directories []argoprojiov1alpha1.GitDirectoryGeneratorItem
pathParamPrefix string repoApps []string
repoApps []string repoError error
repoError error expected []map[string]interface{}
expected []map[string]interface{} expectedError error
expectedError error
}{ }{
{ {
name: "happy flow - created apps", name: "happy flow - created apps",
@@ -416,57 +261,6 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
}, },
expectedError: nil, expectedError: nil,
}, },
{
name: "It prefixes path parameters with PathParamPrefix",
directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
pathParamPrefix: "myRepo",
repoApps: []string{
"app1",
"app2",
"app_3",
"p1/app4",
},
repoError: nil,
expected: []map[string]interface{}{
{
"myRepo": map[string]interface{}{
"path": map[string]interface{}{
"path": "app1",
"basename": "app1",
"basenameNormalized": "app1",
"segments": []string{
"app1",
},
},
},
},
{
"myRepo": map[string]interface{}{
"path": map[string]interface{}{
"path": "app2",
"basename": "app2",
"basenameNormalized": "app2",
"segments": []string{
"app2",
},
},
},
},
{
"myRepo": map[string]interface{}{
"path": map[string]interface{}{
"path": "app_3",
"basename": "app_3",
"basenameNormalized": "app-3",
"segments": []string{
"app_3",
},
},
},
},
},
expectedError: nil,
},
{ {
name: "It filters application according to the paths", name: "It filters application according to the paths",
directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "p1/*"}, {Path: "p1/*/*"}}, directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "p1/*"}, {Path: "p1/*/*"}},
@@ -562,6 +356,7 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
}, },
repoError: nil, repoError: nil,
expected: []map[string]interface{}{ expected: []map[string]interface{}{
{ {
"path": map[string]interface{}{ "path": map[string]interface{}{
"path": "app1", "path": "app1",
@@ -610,7 +405,7 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
repoApps: []string{}, repoApps: []string{},
repoError: fmt.Errorf("error"), repoError: fmt.Errorf("error"),
expected: []map[string]interface{}{}, expected: []map[string]interface{}{},
expectedError: fmt.Errorf("error generating params from git: error getting directories from repo: error"), expectedError: fmt.Errorf("error"),
}, },
} }
@@ -620,11 +415,11 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) { t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel() t.Parallel()
argoCDServiceMock := mocks.Repos{} argoCDServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError) argoCDServiceMock.Mock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
gitGenerator := NewGitGenerator(&argoCDServiceMock, "") var gitGenerator = NewGitGenerator(argoCDServiceMock)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "set", Name: "set",
@@ -633,37 +428,31 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
GoTemplate: true, GoTemplate: true,
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{ Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
Git: &argoprojiov1alpha1.GitGenerator{ Git: &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL", RepoURL: "RepoURL",
Revision: "Revision", Revision: "Revision",
Directories: testCaseCopy.directories, Directories: testCaseCopy.directories,
PathParamPrefix: testCaseCopy.pathParamPrefix,
}, },
}}, }},
}, },
} }
scheme := runtime.NewScheme() got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
appProject := argoprojiov1alpha1.AppProject{}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, client)
if testCaseCopy.expectedError != nil { if testCaseCopy.expectedError != nil {
require.EqualError(t, err, testCaseCopy.expectedError.Error()) assert.EqualError(t, err, testCaseCopy.expectedError.Error())
} else { } else {
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got) assert.Equal(t, testCaseCopy.expected, got)
} }
argoCDServiceMock.AssertExpectations(t) argoCDServiceMock.Mock.AssertExpectations(t)
}) })
} }
} }
func TestGitGenerateParamsFromFiles(t *testing.T) { func TestGitGenerateParamsFromFiles(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
// files is the list of paths/globs to match // files is the list of paths/globs to match
@@ -672,7 +461,6 @@ func TestGitGenerateParamsFromFiles(t *testing.T) {
repoFileContents map[string][]byte repoFileContents map[string][]byte
// if repoPathsError is non-nil, the call to GetPaths(...) will return this error value // if repoPathsError is non-nil, the call to GetPaths(...) will return this error value
repoPathsError error repoPathsError error
values map[string]string
expected []map[string]interface{} expected []map[string]interface{}
expectedError error expectedError error
}{ }{
@@ -736,81 +524,13 @@ func TestGitGenerateParamsFromFiles(t *testing.T) {
}, },
expectedError: nil, expectedError: nil,
}, },
{
name: "Value variable interpolation",
files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.json"}},
repoFileContents: map[string][]byte{
"cluster-config/production/config.json": []byte(`{
"cluster": {
"owner": "john.doe@example.com",
"name": "production",
"address": "https://kubernetes.default.svc"
},
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123
}`),
"cluster-config/staging/config.json": []byte(`{
"cluster": {
"owner": "foo.bar@example.com",
"name": "staging",
"address": "https://kubernetes.default.svc"
}
}`),
},
repoPathsError: nil,
values: map[string]string{
"aaa": "{{ cluster.owner }}",
"no-op": "{{ this-does-not-exist }}",
},
expected: []map[string]interface{}{
{
"cluster.owner": "john.doe@example.com",
"cluster.name": "production",
"cluster.address": "https://kubernetes.default.svc",
"key1": "val1",
"key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123",
"path": "cluster-config/production",
"path.basename": "production",
"path[0]": "cluster-config",
"path[1]": "production",
"path.basenameNormalized": "production",
"path.filename": "config.json",
"path.filenameNormalized": "config.json",
"values.aaa": "john.doe@example.com",
"values.no-op": "{{ this-does-not-exist }}",
},
{
"cluster.owner": "foo.bar@example.com",
"cluster.name": "staging",
"cluster.address": "https://kubernetes.default.svc",
"path": "cluster-config/staging",
"path.basename": "staging",
"path[0]": "cluster-config",
"path[1]": "staging",
"path.basenameNormalized": "staging",
"path.filename": "config.json",
"path.filenameNormalized": "config.json",
"values.aaa": "foo.bar@example.com",
"values.no-op": "{{ this-does-not-exist }}",
},
},
expectedError: nil,
},
{ {
name: "handles error during getting repo paths", name: "handles error during getting repo paths",
files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.json"}}, files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.json"}},
repoFileContents: map[string][]byte{}, repoFileContents: map[string][]byte{},
repoPathsError: fmt.Errorf("paths error"), repoPathsError: fmt.Errorf("paths error"),
expected: []map[string]interface{}{}, expected: []map[string]interface{}{},
expectedError: fmt.Errorf("error generating params from git: paths error"), expectedError: fmt.Errorf("paths error"),
}, },
{ {
name: "test invalid JSON file returns error", name: "test invalid JSON file returns error",
@@ -820,7 +540,7 @@ func TestGitGenerateParamsFromFiles(t *testing.T) {
}, },
repoPathsError: nil, repoPathsError: nil,
expected: []map[string]interface{}{}, expected: []map[string]interface{}{},
expectedError: fmt.Errorf("error generating params from git: unable to process file 'cluster-config/production/config.json': unable to parse file: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type map[string]interface {}"), expectedError: fmt.Errorf("unable to process file 'cluster-config/production/config.json': unable to parse file: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type map[string]interface {}"),
}, },
{ {
name: "test JSON array", name: "test JSON array",
@@ -985,11 +705,11 @@ cluster:
t.Run(testCaseCopy.name, func(t *testing.T) { t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel() t.Parallel()
argoCDServiceMock := mocks.Repos{} argoCDServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). argoCDServiceMock.Mock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError) Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
gitGenerator := NewGitGenerator(&argoCDServiceMock, "") var gitGenerator = NewGitGenerator(argoCDServiceMock)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "set", Name: "set",
@@ -1000,35 +720,28 @@ cluster:
RepoURL: "RepoURL", RepoURL: "RepoURL",
Revision: "Revision", Revision: "Revision",
Files: testCaseCopy.files, Files: testCaseCopy.files,
Values: testCaseCopy.values,
}, },
}}, }},
}, },
} }
scheme := runtime.NewScheme() got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
appProject := argoprojiov1alpha1.AppProject{}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, client)
fmt.Println(got, err) fmt.Println(got, err)
if testCaseCopy.expectedError != nil { if testCaseCopy.expectedError != nil {
require.EqualError(t, err, testCaseCopy.expectedError.Error()) assert.EqualError(t, err, testCaseCopy.expectedError.Error())
} else { } else {
require.NoError(t, err) assert.NoError(t, err)
assert.ElementsMatch(t, testCaseCopy.expected, got) assert.ElementsMatch(t, testCaseCopy.expected, got)
} }
argoCDServiceMock.AssertExpectations(t) argoCDServiceMock.Mock.AssertExpectations(t)
}) })
} }
} }
func TestGitGenerateParamsFromFilesGoTemplate(t *testing.T) { func TestGitGenerateParamsFromFilesGoTemplate(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
// files is the list of paths/globs to match // files is the list of paths/globs to match
@@ -1122,7 +835,7 @@ func TestGitGenerateParamsFromFilesGoTemplate(t *testing.T) {
repoFileContents: map[string][]byte{}, repoFileContents: map[string][]byte{},
repoPathsError: fmt.Errorf("paths error"), repoPathsError: fmt.Errorf("paths error"),
expected: []map[string]interface{}{}, expected: []map[string]interface{}{},
expectedError: fmt.Errorf("error generating params from git: paths error"), expectedError: fmt.Errorf("paths error"),
}, },
{ {
name: "test invalid JSON file returns error", name: "test invalid JSON file returns error",
@@ -1132,7 +845,7 @@ func TestGitGenerateParamsFromFilesGoTemplate(t *testing.T) {
}, },
repoPathsError: nil, repoPathsError: nil,
expected: []map[string]interface{}{}, expected: []map[string]interface{}{},
expectedError: fmt.Errorf("error generating params from git: unable to process file 'cluster-config/production/config.json': unable to parse file: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type map[string]interface {}"), expectedError: fmt.Errorf("unable to process file 'cluster-config/production/config.json': unable to parse file: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type map[string]interface {}"),
}, },
{ {
name: "test JSON array", name: "test JSON array",
@@ -1341,11 +1054,11 @@ cluster:
t.Run(testCaseCopy.name, func(t *testing.T) { t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel() t.Parallel()
argoCDServiceMock := mocks.Repos{} argoCDServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). argoCDServiceMock.Mock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError) Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
gitGenerator := NewGitGenerator(&argoCDServiceMock, "") var gitGenerator = NewGitGenerator(argoCDServiceMock)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "set", Name: "set",
@@ -1362,135 +1075,17 @@ cluster:
}, },
} }
scheme := runtime.NewScheme() got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
appProject := argoprojiov1alpha1.AppProject{}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, client)
fmt.Println(got, err) fmt.Println(got, err)
if testCaseCopy.expectedError != nil { if testCaseCopy.expectedError != nil {
require.EqualError(t, err, testCaseCopy.expectedError.Error()) assert.EqualError(t, err, testCaseCopy.expectedError.Error())
} else { } else {
require.NoError(t, err) assert.NoError(t, err)
assert.ElementsMatch(t, testCaseCopy.expected, got) assert.ElementsMatch(t, testCaseCopy.expected, got)
} }
argoCDServiceMock.AssertExpectations(t) argoCDServiceMock.Mock.AssertExpectations(t)
}) })
} }
} }
func TestGitGenerator_GenerateParams(t *testing.T) {
cases := []struct {
name string
directories []argoprojiov1alpha1.GitDirectoryGeneratorItem
pathParamPrefix string
repoApps []string
repoPathsError error
repoFileContents map[string][]byte
values map[string]string
expected []map[string]interface{}
expectedError error
appset argoprojiov1alpha1.ApplicationSet
callGetDirectories bool
}{
{
name: "Signature Verification - ignores templated project field",
repoApps: []string{
"app1",
},
repoPathsError: nil,
appset: argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
Namespace: "namespace",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
Git: &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL",
Revision: "Revision",
Directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
PathParamPrefix: "",
Values: map[string]string{
"foo": "bar",
},
},
}},
Template: argoprojiov1alpha1.ApplicationSetTemplate{
Spec: argoprojiov1alpha1.ApplicationSpec{
Project: "{{.project}}",
},
},
},
},
callGetDirectories: true,
expected: []map[string]interface{}{{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "path[0]": "app1", "values.foo": "bar"}},
expectedError: nil,
},
{
name: "Signature Verification - Checks for non-templated project field",
repoApps: []string{
"app1",
},
repoPathsError: nil,
appset: argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
Namespace: "namespace",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
Git: &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL",
Revision: "Revision",
Directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
PathParamPrefix: "",
Values: map[string]string{
"foo": "bar",
},
},
}},
Template: argoprojiov1alpha1.ApplicationSetTemplate{
Spec: argoprojiov1alpha1.ApplicationSpec{
Project: "project",
},
},
},
},
callGetDirectories: false,
expected: []map[string]interface{}{{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "path[0]": "app1", "values.foo": "bar"}},
expectedError: fmt.Errorf("error getting project project: appprojects.argoproj.io \"project\" not found"),
},
}
for _, testCase := range cases {
argoCDServiceMock := mocks.Repos{}
if testCase.callGetDirectories {
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCase.repoApps, testCase.repoPathsError)
}
gitGenerator := NewGitGenerator(&argoCDServiceMock, "namespace")
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
appProject := argoprojiov1alpha1.AppProject{}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
got, err := gitGenerator.GenerateParams(&testCase.appset.Spec.Generators[0], &testCase.appset, client)
if testCase.expectedError != nil {
require.EqualError(t, err, testCase.expectedError.Error())
} else {
require.NoError(t, err)
assert.Equal(t, testCase.expected, got)
}
argoCDServiceMock.AssertExpectations(t)
}
}

View File

@@ -4,10 +4,7 @@ import (
"fmt" "fmt"
"time" "time"
"sigs.k8s.io/controller-runtime/pkg/client"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/env"
) )
// Generator defines the interface implemented by all ApplicationSet generators. // 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. // 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 // 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. // against the current state of the Applications in the cluster.
GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]interface{}, error) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error)
// GetRequeueAfter is the generator can controller the next reconciled loop // 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. // 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 GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate
} }
var ( var EmptyAppSetGeneratorError = fmt.Errorf("ApplicationSet is empty")
EmptyAppSetGeneratorError = fmt.Errorf("ApplicationSet is empty") var NoRequeueAfter time.Duration
NoRequeueAfter time.Duration
)
// DefaultRequeueAfterSeconds is used when GetRequeueAfter is not specified, it is the default time to wait before the next reconcile loop
const ( const (
DefaultRequeueAfterSeconds = 3 * time.Minute DefaultRequeueAfterSeconds = 3 * time.Minute
) )
func getDefaultRequeueAfter() time.Duration {
// Default is 3 minutes, min is 1 second, max is 1 year
return env.ParseDurationFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_REQUEUE_AFTER", 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

@@ -5,15 +5,13 @@ import (
"fmt" "fmt"
"time" "time"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
var _ Generator = (*ListGenerator)(nil) var _ Generator = (*ListGenerator)(nil)
type ListGenerator struct{} type ListGenerator struct {
}
func NewListGenerator() Generator { func NewListGenerator() Generator {
g := &ListGenerator{} g := &ListGenerator{}
@@ -28,7 +26,7 @@ func (g *ListGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Applicat
return &appSetGenerator.List.Template return &appSetGenerator.List.Template
} }
func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) { func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil { if appSetGenerator == nil {
return nil, EmptyAppSetGeneratorError return nil, EmptyAppSetGeneratorError
} }
@@ -44,7 +42,7 @@ func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appli
var element map[string]interface{} var element map[string]interface{}
err := json.Unmarshal(tmpItem.Raw, &element) err := json.Unmarshal(tmpItem.Raw, &element)
if err != nil { 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 { if appSet.Spec.GoTemplate {
@@ -59,14 +57,14 @@ func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appli
for k, v := range values { for k, v := range values {
value, ok := v.(string) value, ok := v.(string)
if !ok { 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[fmt.Sprintf("values.%s", k)] = value params[fmt.Sprintf("values.%s", k)] = value
} }
} else { } else {
v, ok := value.(string) v, ok := value.(string)
if !ok { 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 params[key] = v
} }
@@ -75,15 +73,5 @@ func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appli
} }
} }
// Append elements from ElementsYaml to the response
if len(appSetGenerator.List.ElementsYaml) > 0 {
var yamlElements []map[string]interface{}
err := yaml.Unmarshal([]byte(appSetGenerator.List.ElementsYaml), &yamlElements)
if err != nil {
return nil, fmt.Errorf("error unmarshling decoded ElementsYaml %w", err)
}
res = append(res, yamlElements...)
}
return res, nil return res, nil
} }

View File

@@ -4,7 +4,6 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -26,7 +25,8 @@ func TestGenerateListParams(t *testing.T) {
} }
for _, testCase := range testCases { for _, testCase := range testCases {
listGenerator := NewListGenerator()
var listGenerator = NewListGenerator()
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@@ -38,11 +38,11 @@ func TestGenerateListParams(t *testing.T) {
got, err := listGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{ got, err := listGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
List: &argoprojiov1alpha1.ListGenerator{ List: &argoprojiov1alpha1.ListGenerator{
Elements: testCase.elements, Elements: testCase.elements,
}, }}, &applicationSetInfo)
}, &applicationSetInfo, nil)
require.NoError(t, err) assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, got) assert.ElementsMatch(t, testCase.expected, got)
} }
} }
@@ -61,7 +61,8 @@ func TestGenerateListParamsGoTemplate(t *testing.T) {
} }
for _, testCase := range testCases { for _, testCase := range testCases {
listGenerator := NewListGenerator()
var listGenerator = NewListGenerator()
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@@ -75,10 +76,9 @@ func TestGenerateListParamsGoTemplate(t *testing.T) {
got, err := listGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{ got, err := listGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
List: &argoprojiov1alpha1.ListGenerator{ List: &argoprojiov1alpha1.ListGenerator{
Elements: testCase.elements, Elements: testCase.elements,
}, }}, &applicationSetInfo)
}, &applicationSetInfo, nil)
require.NoError(t, err) assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, got) assert.ElementsMatch(t, testCase.expected, got)
} }
} }

View File

@@ -5,12 +5,9 @@ import (
"time" "time"
"github.com/imdario/mergo" "github.com/imdario/mergo"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v2/applicationset/utils" "github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
log "github.com/sirupsen/logrus"
) )
var _ Generator = (*MatrixGenerator)(nil) var _ Generator = (*MatrixGenerator)(nil)
@@ -33,7 +30,8 @@ func NewMatrixGenerator(supportedGenerators map[string]Generator) Generator {
return m return m
} }
func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]interface{}, error) { func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator.Matrix == nil { if appSetGenerator.Matrix == nil {
return nil, EmptyAppSetGeneratorError return nil, EmptyAppSetGeneratorError
} }
@@ -48,23 +46,24 @@ func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.App
res := []map[string]interface{}{} 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 { if err != nil {
return nil, fmt.Errorf("error failed to get params for first generator in matrix generator: %w", err) return nil, err
} }
for _, a := range g0 { 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 { if err != nil {
return nil, fmt.Errorf("failed to get params for second generator in the matrix generator: %w", err) return nil, fmt.Errorf("failed to get params for second generator in the matrix generator: %w", err)
} }
for _, b := range g1 { for _, b := range g1 {
if appSet.Spec.GoTemplate { if appSet.Spec.GoTemplate {
tmp := map[string]interface{}{} tmp := map[string]interface{}{}
if err := mergo.Merge(&tmp, b, mergo.WithOverride); err != nil { if err := mergo.Merge(&tmp, a); err != nil {
return nil, fmt.Errorf("failed to merge params from the second generator in the matrix generator with temp map: %w", err) return nil, fmt.Errorf("failed to merge params from the first generator in the matrix generator with temp map: %w", err)
} }
if err := mergo.Merge(&tmp, a, mergo.WithOverride); err != nil { if err := mergo.Merge(&tmp, b); err != nil {
return nil, fmt.Errorf("failed to merge params from the second generator in the matrix generator with the first: %w", err) return nil, fmt.Errorf("failed to merge params from the first generator in the matrix generator with the second: %w", err)
} }
res = append(res, tmp) res = append(res, tmp)
} else { } else {
@@ -80,26 +79,14 @@ func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.App
return res, nil return res, nil
} }
func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, params map[string]interface{}, client client.Client) ([]map[string]interface{}, error) { func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, params map[string]interface{}) ([]map[string]interface{}, error) {
matrixGen, err := getMatrixGenerator(appSetBaseGenerator) matrixGen, err := getMatrixGenerator(appSetBaseGenerator)
if err != nil { if err != nil {
return nil, err 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) mergeGen, err := getMergeGenerator(appSetBaseGenerator)
if err != nil { if err != nil {
return nil, fmt.Errorf("error retrieving merge generator: %w", err) return nil, err
}
if mergeGen != nil && !appSet.Spec.ApplyNestedSelectors {
foundSelector := dropDisabledNestedSelectors(mergeGen.Generators)
if foundSelector {
log.Warnf("AppSet '%v' defines selector on nested merge generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selectors", appSet.Name)
}
} }
t, err := Transform( t, err := Transform(
@@ -110,7 +97,6 @@ func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Appli
SCMProvider: appSetBaseGenerator.SCMProvider, SCMProvider: appSetBaseGenerator.SCMProvider,
ClusterDecisionResource: appSetBaseGenerator.ClusterDecisionResource, ClusterDecisionResource: appSetBaseGenerator.ClusterDecisionResource,
PullRequest: appSetBaseGenerator.PullRequest, PullRequest: appSetBaseGenerator.PullRequest,
Plugin: appSetBaseGenerator.Plugin,
Matrix: matrixGen, Matrix: matrixGen,
Merge: mergeGen, Merge: mergeGen,
Selector: appSetBaseGenerator.Selector, Selector: appSetBaseGenerator.Selector,
@@ -118,10 +104,10 @@ func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Appli
m.supportedGenerators, m.supportedGenerators,
argoprojiov1alpha1.ApplicationSetTemplate{}, argoprojiov1alpha1.ApplicationSetTemplate{},
appSet, appSet,
params, params)
client)
if err != nil { 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 { if len(t) == 0 {
@@ -145,15 +131,12 @@ func (m *MatrixGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Ap
matrixGen, _ := getMatrixGenerator(r) matrixGen, _ := getMatrixGenerator(r)
mergeGen, _ := getMergeGenerator(r) mergeGen, _ := getMergeGenerator(r)
base := &argoprojiov1alpha1.ApplicationSetGenerator{ base := &argoprojiov1alpha1.ApplicationSetGenerator{
List: r.List, List: r.List,
Clusters: r.Clusters, Clusters: r.Clusters,
Git: r.Git, Git: r.Git,
PullRequest: r.PullRequest, PullRequest: r.PullRequest,
Plugin: r.Plugin, Matrix: matrixGen,
SCMProvider: r.SCMProvider, Merge: mergeGen,
ClusterDecisionResource: r.ClusterDecisionResource,
Matrix: matrixGen,
Merge: mergeGen,
} }
generators := GetRelevantGenerators(base, m.supportedGenerators) generators := GetRelevantGenerators(base, m.supportedGenerators)
@@ -171,6 +154,7 @@ func (m *MatrixGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Ap
} else { } else {
return NoRequeueAfter return NoRequeueAfter
} }
} }
func getMatrixGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*argoprojiov1alpha1.MatrixGenerator, error) { func getMatrixGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*argoprojiov1alpha1.MatrixGenerator, error) {

View File

@@ -13,17 +13,16 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" testutils "github.com/argoproj/argo-cd/v2/applicationset/utils/test"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
func TestMatrixGenerate(t *testing.T) { func TestMatrixGenerate(t *testing.T) {
gitGenerator := &argoprojiov1alpha1.GitGenerator{ gitGenerator := &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL", RepoURL: "RepoURL",
Revision: "Revision", Revision: "Revision",
@@ -31,7 +30,7 @@ func TestMatrixGenerate(t *testing.T) {
} }
listGenerator := &argoprojiov1alpha1.ListGenerator{ listGenerator := &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url", "templated": "test-{{path.basenameNormalized}}"}`)}}, Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url"}`)}},
} }
testCases := []struct { testCases := []struct {
@@ -51,8 +50,8 @@ func TestMatrixGenerate(t *testing.T) {
}, },
}, },
expected: []map[string]interface{}{ expected: []map[string]interface{}{
{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "cluster": "Cluster", "url": "Url", "templated": "test-app1"}, {"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "cluster": "Cluster", "url": "Url"},
{"path": "app2", "path.basename": "app2", "path.basenameNormalized": "app2", "cluster": "Cluster", "url": "Url", "templated": "test-app2"}, {"path": "app2", "path.basename": "app2", "path.basenameNormalized": "app2", "cluster": "Cluster", "url": "Url"},
}, },
}, },
{ {
@@ -147,11 +146,12 @@ func TestMatrixGenerate(t *testing.T) {
} }
for _, g := range testCaseCopy.baseGenerators { for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{ gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git, Git: g.Git,
List: g.List, List: g.List,
} }
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet, mock.Anything).Return([]map[string]interface{}{ genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]interface{}{
{ {
"path": "app1", "path": "app1",
"path.basename": "app1", "path.basename": "app1",
@@ -168,7 +168,7 @@ func TestMatrixGenerate(t *testing.T) {
Return(&argoprojiov1alpha1.ApplicationSetTemplate{}) Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
} }
matrixGenerator := NewMatrixGenerator( var matrixGenerator = NewMatrixGenerator(
map[string]Generator{ map[string]Generator{
"Git": genMock, "Git": genMock,
"List": &ListGenerator{}, "List": &ListGenerator{},
@@ -180,19 +180,22 @@ func TestMatrixGenerate(t *testing.T) {
Generators: testCaseCopy.baseGenerators, Generators: testCaseCopy.baseGenerators,
Template: argoprojiov1alpha1.ApplicationSetTemplate{}, Template: argoprojiov1alpha1.ApplicationSetTemplate{},
}, },
}, appSet, nil) }, appSet)
if testCaseCopy.expectedErr != nil { if testCaseCopy.expectedErr != nil {
require.ErrorIs(t, err, testCaseCopy.expectedErr) assert.ErrorIs(t, err, testCaseCopy.expectedErr)
} else { } else {
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got) assert.Equal(t, testCaseCopy.expected, got)
} }
}) })
} }
} }
func TestMatrixGenerateGoTemplate(t *testing.T) { func TestMatrixGenerateGoTemplate(t *testing.T) {
gitGenerator := &argoprojiov1alpha1.GitGenerator{ gitGenerator := &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL", RepoURL: "RepoURL",
Revision: "Revision", Revision: "Revision",
@@ -267,28 +270,6 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
{"a": "2", "b": "2"}, {"a": "2", "b": "2"},
}, },
}, },
{
name: "parameter override: first list elements take precedence",
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{
{Raw: []byte(`{"booleanFalse": false, "booleanTrue": true, "stringFalse": "false", "stringTrue": "true"}`)},
},
},
},
{
List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{
{Raw: []byte(`{"booleanFalse": true, "booleanTrue": false, "stringFalse": "true", "stringTrue": "false"}`)},
},
},
},
},
expected: []map[string]interface{}{
{"booleanFalse": false, "booleanTrue": true, "stringFalse": "false", "stringTrue": "true"},
},
},
{ {
name: "returns error if there is less than two base generators", name: "returns error if there is less than two base generators",
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
@@ -356,11 +337,12 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
} }
for _, g := range testCaseCopy.baseGenerators { for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{ gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git, Git: g.Git,
List: g.List, List: g.List,
} }
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet, mock.Anything).Return([]map[string]interface{}{ genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]interface{}{
{ {
"path": map[string]string{ "path": map[string]string{
"path": "app1", "path": "app1",
@@ -381,7 +363,7 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
Return(&argoprojiov1alpha1.ApplicationSetTemplate{}) Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
} }
matrixGenerator := NewMatrixGenerator( var matrixGenerator = NewMatrixGenerator(
map[string]Generator{ map[string]Generator{
"Git": genMock, "Git": genMock,
"List": &ListGenerator{}, "List": &ListGenerator{},
@@ -393,19 +375,22 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
Generators: testCaseCopy.baseGenerators, Generators: testCaseCopy.baseGenerators,
Template: argoprojiov1alpha1.ApplicationSetTemplate{}, Template: argoprojiov1alpha1.ApplicationSetTemplate{},
}, },
}, appSet, nil) }, appSet)
if testCaseCopy.expectedErr != nil { if testCaseCopy.expectedErr != nil {
require.ErrorIs(t, err, testCaseCopy.expectedErr) assert.ErrorIs(t, err, testCaseCopy.expectedErr)
} else { } else {
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got) assert.Equal(t, testCaseCopy.expected, got)
} }
}) })
} }
} }
func TestMatrixGetRequeueAfter(t *testing.T) { func TestMatrixGetRequeueAfter(t *testing.T) {
gitGenerator := &argoprojiov1alpha1.GitGenerator{ gitGenerator := &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL", RepoURL: "RepoURL",
Revision: "Revision", Revision: "Revision",
@@ -418,10 +403,6 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
pullRequestGenerator := &argoprojiov1alpha1.PullRequestGenerator{} pullRequestGenerator := &argoprojiov1alpha1.PullRequestGenerator{}
scmGenerator := &argoprojiov1alpha1.SCMProviderGenerator{}
duckTypeGenerator := &argoprojiov1alpha1.DuckTypeGenerator{}
testCases := []struct { testCases := []struct {
name string name string
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
@@ -479,30 +460,6 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
}, },
expected: time.Duration(30 * time.Minute), expected: time.Duration(30 * time.Minute),
}, },
{
name: "returns the default time for duck type generator",
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
Git: gitGenerator,
},
{
ClusterDecisionResource: duckTypeGenerator,
},
},
expected: time.Duration(3 * time.Minute),
},
{
name: "returns the default time for scm generator",
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
Git: gitGenerator,
},
{
SCMProvider: scmGenerator,
},
},
expected: time.Duration(30 * time.Minute),
},
} }
for _, testCase := range testCases { for _, testCase := range testCases {
@@ -513,22 +470,18 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
for _, g := range testCaseCopy.baseGenerators { for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{ gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git, Git: g.Git,
List: g.List, List: g.List,
PullRequest: g.PullRequest, PullRequest: g.PullRequest,
SCMProvider: g.SCMProvider,
ClusterDecisionResource: g.ClusterDecisionResource,
} }
mock.On("GetRequeueAfter", &gitGeneratorSpec).Return(testCaseCopy.gitGetRequeueAfter, nil) mock.On("GetRequeueAfter", &gitGeneratorSpec).Return(testCaseCopy.gitGetRequeueAfter, nil)
} }
matrixGenerator := NewMatrixGenerator( var matrixGenerator = NewMatrixGenerator(
map[string]Generator{ map[string]Generator{
"Git": mock, "Git": mock,
"List": &ListGenerator{}, "List": &ListGenerator{},
"PullRequest": &PullRequestGenerator{}, "PullRequest": &PullRequestGenerator{},
"SCMProvider": &SCMProviderGenerator{},
"ClusterDecisionResource": &DuckTypeGenerator{},
}, },
) )
@@ -540,7 +493,9 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
}) })
assert.Equal(t, testCaseCopy.expected, got) assert.Equal(t, testCaseCopy.expected, got)
}) })
} }
} }
@@ -578,8 +533,8 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
}, },
}, },
expected: []map[string]interface{}{ 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", "project": ""}, {"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", "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"},
}, },
clientError: false, clientError: false,
}, },
@@ -645,9 +600,10 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
fakeClient, fakeClient,
testCase.clientError, testCase.clientError,
} }
clusterGenerator := NewClusterGenerator(cl, context.Background(), appClientset, "namespace") var clusterGenerator = NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
for _, g := range testCaseCopy.baseGenerators { for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{ gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git, Git: g.Git,
Clusters: g.Clusters, Clusters: g.Clusters,
@@ -667,7 +623,7 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
genMock.On("GetTemplate", &gitGeneratorSpec). genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&argoprojiov1alpha1.ApplicationSetTemplate{}) Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
} }
matrixGenerator := NewMatrixGenerator( var matrixGenerator = NewMatrixGenerator(
map[string]Generator{ map[string]Generator{
"Git": genMock, "Git": genMock,
"Clusters": clusterGenerator, "Clusters": clusterGenerator,
@@ -679,14 +635,15 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
Generators: testCaseCopy.baseGenerators, Generators: testCaseCopy.baseGenerators,
Template: argoprojiov1alpha1.ApplicationSetTemplate{}, Template: argoprojiov1alpha1.ApplicationSetTemplate{},
}, },
}, appSet, nil) }, appSet)
if testCaseCopy.expectedErr != nil { if testCaseCopy.expectedErr != nil {
require.ErrorIs(t, err, testCaseCopy.expectedErr) assert.ErrorIs(t, err, testCaseCopy.expectedErr)
} else { } else {
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got) assert.Equal(t, testCaseCopy.expected, got)
} }
}) })
} }
} }
@@ -734,7 +691,6 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
"name": "dev-01", "name": "dev-01",
"nameNormalized": "dev-01", "nameNormalized": "dev-01",
"server": "https://dev-01.example.com", "server": "https://dev-01.example.com",
"project": "",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"environment": "dev", "environment": "dev",
@@ -751,7 +707,6 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
"name": "prod-01", "name": "prod-01",
"nameNormalized": "prod-01", "nameNormalized": "prod-01",
"server": "https://prod-01.example.com", "server": "https://prod-01.example.com",
"project": "",
"metadata": map[string]interface{}{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"environment": "prod", "environment": "prod",
@@ -828,14 +783,16 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
fakeClient, fakeClient,
testCase.clientError, testCase.clientError,
} }
clusterGenerator := NewClusterGenerator(cl, context.Background(), appClientset, "namespace") var clusterGenerator = NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
for _, g := range testCaseCopy.baseGenerators { for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{ gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git, Git: g.Git,
Clusters: g.Clusters, Clusters: g.Clusters,
} }
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]interface{}{ genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]interface{}{
{ {
"path": map[string]string{ "path": map[string]string{
"path": "examples/git-generator-files-discovery/cluster-config/dev/config.json", "path": "examples/git-generator-files-discovery/cluster-config/dev/config.json",
@@ -854,7 +811,7 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
genMock.On("GetTemplate", &gitGeneratorSpec). genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&argoprojiov1alpha1.ApplicationSetTemplate{}) Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
} }
matrixGenerator := NewMatrixGenerator( var matrixGenerator = NewMatrixGenerator(
map[string]Generator{ map[string]Generator{
"Git": genMock, "Git": genMock,
"Clusters": clusterGenerator, "Clusters": clusterGenerator,
@@ -866,176 +823,17 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
Generators: testCaseCopy.baseGenerators, Generators: testCaseCopy.baseGenerators,
Template: argoprojiov1alpha1.ApplicationSetTemplate{}, Template: argoprojiov1alpha1.ApplicationSetTemplate{},
}, },
}, appSet, nil) }, appSet)
if testCaseCopy.expectedErr != nil { if testCaseCopy.expectedErr != nil {
require.ErrorIs(t, err, testCaseCopy.expectedErr) assert.ErrorIs(t, err, testCaseCopy.expectedErr)
} else { } else {
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got) assert.Equal(t, testCaseCopy.expected, got)
} }
}) })
}
}
func TestMatrixGenerateListElementsYaml(t *testing.T) {
gitGenerator := &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL",
Revision: "Revision",
Files: []argoprojiov1alpha1.GitFileGeneratorItem{
{Path: "config.yaml"},
},
}
listGenerator := &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{},
ElementsYaml: "{{ .foo.bar | toJson }}",
}
testCases := []struct {
name string
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
expectedErr error
expected []map[string]interface{}
}{
{
name: "happy flow - generate params",
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
Git: gitGenerator,
},
{
List: listGenerator,
},
},
expected: []map[string]interface{}{
{
"chart": "a",
"version": "1",
"foo": map[string]interface{}{
"bar": []interface{}{
map[string]interface{}{
"chart": "a",
"version": "1",
},
map[string]interface{}{
"chart": "b",
"version": "2",
},
},
},
"path": map[string]interface{}{
"basename": "dir",
"basenameNormalized": "dir",
"filename": "file_name.yaml",
"filenameNormalized": "file-name.yaml",
"path": "path/dir",
"segments": []string{
"path",
"dir",
},
},
},
{
"chart": "b",
"version": "2",
"foo": map[string]interface{}{
"bar": []interface{}{
map[string]interface{}{
"chart": "a",
"version": "1",
},
map[string]interface{}{
"chart": "b",
"version": "2",
},
},
},
"path": map[string]interface{}{
"basename": "dir",
"basenameNormalized": "dir",
"filename": "file_name.yaml",
"filenameNormalized": "file-name.yaml",
"path": "path/dir",
"segments": []string{
"path",
"dir",
},
},
},
},
},
}
for _, testCase := range testCases {
testCaseCopy := testCase // Since tests may run in parallel
t.Run(testCaseCopy.name, func(t *testing.T) {
genMock := &generatorMock{}
appSet := &argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
GoTemplate: true,
},
}
for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git,
List: g.List,
}
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]any{{
"foo": map[string]interface{}{
"bar": []interface{}{
map[string]interface{}{
"chart": "a",
"version": "1",
},
map[string]interface{}{
"chart": "b",
"version": "2",
},
},
},
"path": map[string]interface{}{
"basename": "dir",
"basenameNormalized": "dir",
"filename": "file_name.yaml",
"filenameNormalized": "file-name.yaml",
"path": "path/dir",
"segments": []string{
"path",
"dir",
},
},
}}, nil)
genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
}
matrixGenerator := NewMatrixGenerator(
map[string]Generator{
"Git": genMock,
"List": &ListGenerator{},
},
)
got, err := matrixGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
Matrix: &argoprojiov1alpha1.MatrixGenerator{
Generators: testCaseCopy.baseGenerators,
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
},
}, appSet, nil)
if testCaseCopy.expectedErr != nil {
require.ErrorIs(t, err, testCaseCopy.expectedErr)
} else {
require.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got)
}
})
} }
} }
@@ -1049,7 +847,7 @@ func (g *generatorMock) GetTemplate(appSetGenerator *argoprojiov1alpha1.Applicat
return args.Get(0).(*argoprojiov1alpha1.ApplicationSetTemplate) return args.Get(0).(*argoprojiov1alpha1.ApplicationSetTemplate)
} }
func (g *generatorMock) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) { func (g *generatorMock) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
args := g.Called(appSetGenerator, appSet) args := g.Called(appSetGenerator, appSet)
return args.Get(0).([]map[string]interface{}), args.Error(1) return args.Get(0).([]map[string]interface{}), args.Error(1)
@@ -1059,6 +857,7 @@ func (g *generatorMock) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Appl
args := g.Called(appSetGenerator) args := g.Called(appSetGenerator)
return args.Get(0).(time.Duration) return args.Get(0).(time.Duration)
} }
func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) { func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
@@ -1075,7 +874,7 @@ func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
// of that bug. // of that bug.
listGeneratorMock := &generatorMock{} listGeneratorMock := &generatorMock{}
listGeneratorMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).Return([]map[string]interface{}{ listGeneratorMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), mock.AnythingOfType("*v1alpha1.ApplicationSet")).Return([]map[string]interface{}{
{"some": "value"}, {"some": "value"},
}, nil) }, nil)
listGeneratorMock.On("GetTemplate", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator")).Return(&argoprojiov1alpha1.ApplicationSetTemplate{}) listGeneratorMock.On("GetTemplate", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator")).Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
@@ -1087,11 +886,11 @@ func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
}, },
} }
repoServiceMock := &mocks.Repos{} repoServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
repoServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(map[string][]byte{ repoServiceMock.Mock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(map[string][]byte{
"some/path.json": []byte("test: content"), "some/path.json": []byte("test: content"),
}, nil) }, nil)
gitGenerator := NewGitGenerator(repoServiceMock, "") gitGenerator := NewGitGenerator(repoServiceMock)
matrixGenerator := NewMatrixGenerator(map[string]Generator{ matrixGenerator := NewMatrixGenerator(map[string]Generator{
"List": listGeneratorMock, "List": listGeneratorMock,
@@ -1114,17 +913,9 @@ func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
}, },
}, },
} }
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
appProject := argoprojiov1alpha1.AppProject{}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
params, err := matrixGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{ params, err := matrixGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
Matrix: matrixGeneratorSpec, Matrix: matrixGeneratorSpec,
}, &argoprojiov1alpha1.ApplicationSet{}, client) }, &argoprojiov1alpha1.ApplicationSet{})
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []map[string]interface{}{{ assert.Equal(t, []map[string]interface{}{{
"path": "some", "path": "some",

View File

@@ -6,12 +6,9 @@ import (
"time" "time"
"github.com/imdario/mergo" "github.com/imdario/mergo"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v2/applicationset/utils" "github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
log "github.com/sirupsen/logrus"
) )
var _ Generator = (*MergeGenerator)(nil) var _ Generator = (*MergeGenerator)(nil)
@@ -37,12 +34,12 @@ func NewMergeGenerator(supportedGenerators map[string]Generator) Generator {
// getParamSetsForAllGenerators generates params for each child generator in a MergeGenerator. Param sets are returned // 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. // 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]interface{}, error) { func (m *MergeGenerator) getParamSetsForAllGenerators(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([][]map[string]interface{}, error) {
var paramSets [][]map[string]interface{} var paramSets [][]map[string]interface{}
for i, generator := range generators { for _, generator := range generators {
generatorParamSets, err := m.getParams(generator, appSet, client) generatorParamSets, err := m.getParams(generator, appSet)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting params from generator %d of %d: %w", i+1, len(generators), err) return nil, err
} }
// concatenate param lists produced by each generator // concatenate param lists produced by each generator
paramSets = append(paramSets, generatorParamSets) paramSets = append(paramSets, generatorParamSets)
@@ -51,7 +48,7 @@ func (m *MergeGenerator) getParamSetsForAllGenerators(generators []argoprojiov1a
} }
// GenerateParams gets the params produced by the MergeGenerator. // GenerateParams gets the params produced by the MergeGenerator.
func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]interface{}, error) { func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator.Merge == nil { if appSetGenerator.Merge == nil {
return nil, EmptyAppSetGeneratorError return nil, EmptyAppSetGeneratorError
} }
@@ -60,33 +57,34 @@ func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appl
return nil, ErrLessThanTwoGeneratorsInMerge return nil, ErrLessThanTwoGeneratorsInMerge
} }
paramSetsFromGenerators, err := m.getParamSetsForAllGenerators(appSetGenerator.Merge.Generators, appSet, client) paramSetsFromGenerators, err := m.getParamSetsForAllGenerators(appSetGenerator.Merge.Generators, appSet)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting param sets from generators: %w", err) return nil, err
} }
baseParamSetsByMergeKey, err := getParamSetsByMergeKey(appSetGenerator.Merge.MergeKeys, paramSetsFromGenerators[0]) baseParamSetsByMergeKey, err := getParamSetsByMergeKey(appSetGenerator.Merge.MergeKeys, paramSetsFromGenerators[0])
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting param sets by merge key: %w", err) return nil, err
} }
for _, paramSets := range paramSetsFromGenerators[1:] { for _, paramSets := range paramSetsFromGenerators[1:] {
paramSetsByMergeKey, err := getParamSetsByMergeKey(appSetGenerator.Merge.MergeKeys, paramSets) paramSetsByMergeKey, err := getParamSetsByMergeKey(appSetGenerator.Merge.MergeKeys, paramSets)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting param sets by merge key: %w", err) return nil, err
} }
for mergeKeyValue, baseParamSet := range baseParamSetsByMergeKey { for mergeKeyValue, baseParamSet := range baseParamSetsByMergeKey {
if overrideParamSet, exists := paramSetsByMergeKey[mergeKeyValue]; exists { if overrideParamSet, exists := paramSetsByMergeKey[mergeKeyValue]; exists {
if appSet.Spec.GoTemplate { if appSet.Spec.GoTemplate {
if err := mergo.Merge(&baseParamSet, overrideParamSet, mergo.WithOverride); err != nil { if err := mergo.Merge(&baseParamSet, overrideParamSet, mergo.WithOverride); err != nil {
return nil, fmt.Errorf("error merging base param set with override param set: %w", err) return nil, fmt.Errorf("failed to merge base param set with override param set: %w", err)
} }
baseParamSetsByMergeKey[mergeKeyValue] = baseParamSet baseParamSetsByMergeKey[mergeKeyValue] = baseParamSet
} else { } else {
overriddenParamSet, err := utils.CombineStringMapsAllowDuplicates(baseParamSet, overrideParamSet) overriddenParamSet, err := utils.CombineStringMapsAllowDuplicates(baseParamSet, overrideParamSet)
if err != nil { if err != nil {
return nil, fmt.Errorf("error combining string maps: %w", err) return nil, err
} }
baseParamSetsByMergeKey[mergeKeyValue] = utils.ConvertToMapStringInterface(overriddenParamSet) baseParamSetsByMergeKey[mergeKeyValue] = utils.ConvertToMapStringInterface(overriddenParamSet)
} }
@@ -95,7 +93,7 @@ func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appl
} }
mergedParamSets := make([]map[string]interface{}, len(baseParamSetsByMergeKey)) mergedParamSets := make([]map[string]interface{}, len(baseParamSetsByMergeKey))
i := 0 var i = 0
for _, mergedParamSet := range baseParamSetsByMergeKey { for _, mergedParamSet := range baseParamSetsByMergeKey {
mergedParamSets[i] = mergedParamSet mergedParamSets[i] = mergedParamSet
i += 1 i += 1
@@ -125,7 +123,7 @@ func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]interface
} }
paramSetKeyJson, err := json.Marshal(paramSetKey) paramSetKeyJson, err := json.Marshal(paramSetKey)
if err != nil { if err != nil {
return nil, fmt.Errorf("error marshalling param set key json: %w", err) return nil, err
} }
paramSetKeyString := string(paramSetKeyJson) paramSetKeyString := string(paramSetKeyJson)
if _, exists := paramSetsByMergeKey[paramSetKeyString]; exists { if _, exists := paramSetsByMergeKey[paramSetKeyString]; exists {
@@ -138,27 +136,15 @@ func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]interface
} }
// getParams get the parameters generated by this generator. // getParams get the parameters generated by this generator.
func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]interface{}, error) { func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
matrixGen, err := getMatrixGenerator(appSetBaseGenerator) matrixGen, err := getMatrixGenerator(appSetBaseGenerator)
if err != nil { if err != nil {
return nil, err 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) mergeGen, err := getMergeGenerator(appSetBaseGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if mergeGen != nil && !appSet.Spec.ApplyNestedSelectors {
foundSelector := dropDisabledNestedSelectors(mergeGen.Generators)
if foundSelector {
log.Warnf("AppSet '%v' defines selector on nested merge generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selector", appSet.Name)
}
}
t, err := Transform( t, err := Transform(
argoprojiov1alpha1.ApplicationSetGenerator{ argoprojiov1alpha1.ApplicationSetGenerator{
@@ -168,7 +154,6 @@ func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Applic
SCMProvider: appSetBaseGenerator.SCMProvider, SCMProvider: appSetBaseGenerator.SCMProvider,
ClusterDecisionResource: appSetBaseGenerator.ClusterDecisionResource, ClusterDecisionResource: appSetBaseGenerator.ClusterDecisionResource,
PullRequest: appSetBaseGenerator.PullRequest, PullRequest: appSetBaseGenerator.PullRequest,
Plugin: appSetBaseGenerator.Plugin,
Matrix: matrixGen, Matrix: matrixGen,
Merge: mergeGen, Merge: mergeGen,
Selector: appSetBaseGenerator.Selector, Selector: appSetBaseGenerator.Selector,
@@ -176,9 +161,10 @@ func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Applic
m.supportedGenerators, m.supportedGenerators,
argoprojiov1alpha1.ApplicationSetTemplate{}, argoprojiov1alpha1.ApplicationSetTemplate{},
appSet, appSet,
map[string]interface{}{}, client) map[string]interface{}{})
if err != nil { 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 { if len(t) == 0 {
@@ -200,15 +186,12 @@ func (m *MergeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.App
matrixGen, _ := getMatrixGenerator(r) matrixGen, _ := getMatrixGenerator(r)
mergeGen, _ := getMergeGenerator(r) mergeGen, _ := getMergeGenerator(r)
base := &argoprojiov1alpha1.ApplicationSetGenerator{ base := &argoprojiov1alpha1.ApplicationSetGenerator{
List: r.List, List: r.List,
Clusters: r.Clusters, Clusters: r.Clusters,
Git: r.Git, Git: r.Git,
PullRequest: r.PullRequest, PullRequest: r.PullRequest,
Plugin: r.Plugin, Matrix: matrixGen,
SCMProvider: r.SCMProvider, Merge: mergeGen,
ClusterDecisionResource: r.ClusterDecisionResource,
Matrix: matrixGen,
Merge: mergeGen,
} }
generators := GetRelevantGenerators(base, m.supportedGenerators) generators := GetRelevantGenerators(base, m.supportedGenerators)
@@ -226,6 +209,7 @@ func (m *MergeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.App
} else { } else {
return NoRequeueAfter return NoRequeueAfter
} }
} }
func getMergeGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*argoprojiov1alpha1.MergeGenerator, error) { func getMergeGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*argoprojiov1alpha1.MergeGenerator, error) {
@@ -234,7 +218,7 @@ func getMergeGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*arg
} }
merge, err := argoprojiov1alpha1.ToNestedMergeGenerator(r.Merge) merge, err := argoprojiov1alpha1.ToNestedMergeGenerator(r.Merge)
if err != nil { if err != nil {
return nil, fmt.Errorf("error converting to nested merge generator: %w", err) return nil, err
} }
return merge.ToMergeGenerator(), nil return merge.ToMergeGenerator(), nil
} }

View File

@@ -6,7 +6,6 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
@@ -50,6 +49,7 @@ func listOfMapsToSet(maps []map[string]interface{}) (map[string]bool, error) {
} }
func TestMergeGenerate(t *testing.T) { func TestMergeGenerate(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
@@ -156,7 +156,7 @@ func TestMergeGenerate(t *testing.T) {
appSet := &argoprojiov1alpha1.ApplicationSet{} appSet := &argoprojiov1alpha1.ApplicationSet{}
mergeGenerator := NewMergeGenerator( var mergeGenerator = NewMergeGenerator(
map[string]Generator{ map[string]Generator{
"List": &ListGenerator{}, "List": &ListGenerator{},
"Matrix": &MatrixGenerator{ "Matrix": &MatrixGenerator{
@@ -178,18 +178,18 @@ func TestMergeGenerate(t *testing.T) {
MergeKeys: testCaseCopy.mergeKeys, MergeKeys: testCaseCopy.mergeKeys,
Template: argoprojiov1alpha1.ApplicationSetTemplate{}, Template: argoprojiov1alpha1.ApplicationSetTemplate{},
}, },
}, appSet, nil) }, appSet)
if testCaseCopy.expectedErr != nil { if testCaseCopy.expectedErr != nil {
require.EqualError(t, err, testCaseCopy.expectedErr.Error()) assert.EqualError(t, err, testCaseCopy.expectedErr.Error())
} else { } else {
expectedSet, err := listOfMapsToSet(testCaseCopy.expected) expectedSet, err := listOfMapsToSet(testCaseCopy.expected)
require.NoError(t, err) assert.NoError(t, err)
actualSet, err := listOfMapsToSet(got) 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) assert.Equal(t, expectedSet, actualSet)
} }
}) })
@@ -197,7 +197,7 @@ func TestMergeGenerate(t *testing.T) {
} }
func toAPIExtensionsJSON(t *testing.T, g interface{}) *apiextensionsv1.JSON { func toAPIExtensionsJSON(t *testing.T, g interface{}) *apiextensionsv1.JSON {
t.Helper()
resVal, err := json.Marshal(g) resVal, err := json.Marshal(g)
if err != nil { if err != nil {
t.Error("unable to unmarshal json", g) t.Error("unable to unmarshal json", g)
@@ -339,11 +339,13 @@ func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
got, err := getParamSetsByMergeKey(testCaseCopy.mergeKeys, testCaseCopy.paramSets) got, err := getParamSetsByMergeKey(testCaseCopy.mergeKeys, testCaseCopy.paramSets)
if testCaseCopy.expectedErr != nil { if testCaseCopy.expectedErr != nil {
require.EqualError(t, err, testCaseCopy.expectedErr.Error()) assert.EqualError(t, err, testCaseCopy.expectedErr.Error())
} else { } else {
require.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got) assert.Equal(t, testCaseCopy.expected, got)
} }
}) })
} }
} }

View File

@@ -1,100 +0,0 @@
// Code generated by mockery v2.43.2. 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/v2/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

@@ -1,206 +0,0 @@
package generators
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"github.com/jeremywohl/flatten"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/settings"
"github.com/argoproj/argo-cd/v2/applicationset/services/plugin"
)
const (
DefaultPluginRequeueAfterSeconds = 30 * time.Minute
)
var _ Generator = (*PluginGenerator)(nil)
type PluginGenerator struct {
client client.Client
ctx context.Context
clientset kubernetes.Interface
namespace string
}
func NewPluginGenerator(client client.Client, ctx context.Context, clientset kubernetes.Interface, namespace string) Generator {
g := &PluginGenerator{
client: client,
ctx: ctx,
clientset: clientset,
namespace: namespace,
}
return g
}
func (g *PluginGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
// Return a requeue default of 30 minutes, if no default is specified.
if appSetGenerator.Plugin.RequeueAfterSeconds != nil {
return time.Duration(*appSetGenerator.Plugin.RequeueAfterSeconds) * time.Second
}
return DefaultPluginRequeueAfterSeconds
}
func (g *PluginGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
return &appSetGenerator.Plugin.Template
}
func (g *PluginGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
if appSetGenerator == nil {
return nil, EmptyAppSetGeneratorError
}
if appSetGenerator.Plugin == nil {
return nil, EmptyAppSetGeneratorError
}
ctx := context.Background()
providerConfig := appSetGenerator.Plugin
pluginClient, err := g.getPluginFromGenerator(ctx, applicationSetInfo.Name, providerConfig)
if err != nil {
return nil, fmt.Errorf("error getting plugin from generator: %w", err)
}
list, err := pluginClient.List(ctx, providerConfig.Input.Parameters)
if err != nil {
return nil, fmt.Errorf("error listing params: %w", err)
}
res, err := g.generateParams(appSetGenerator, applicationSetInfo, list.Output.Parameters, appSetGenerator.Plugin.Input.Parameters, applicationSetInfo.Spec.GoTemplate)
if err != nil {
return nil, fmt.Errorf("error generating params: %w", err)
}
return res, nil
}
func (g *PluginGenerator) getPluginFromGenerator(ctx context.Context, appSetName string, generatorConfig *argoprojiov1alpha1.PluginGenerator) (*plugin.Service, error) {
cm, err := g.getConfigMap(ctx, generatorConfig.ConfigMapRef.Name)
if err != nil {
return nil, fmt.Errorf("error fetching ConfigMap: %w", err)
}
token, err := g.getToken(ctx, cm["token"])
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err)
}
var requestTimeout int
requestTimeoutStr, ok := cm["requestTimeout"]
if ok {
requestTimeout, err = strconv.Atoi(requestTimeoutStr)
if err != nil {
return nil, fmt.Errorf("error set requestTimeout : %w", err)
}
}
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]interface{}, pluginParams argoprojiov1alpha1.PluginParameters, useGoTemplate bool) ([]map[string]interface{}, error) {
res := []map[string]interface{}{}
for _, objectFound := range objectsFound {
params := map[string]interface{}{}
if useGoTemplate {
for k, v := range objectFound {
params[k] = v
}
} else {
flat, err := flatten.Flatten(objectFound, "", flatten.DotStyle)
if err != nil {
return nil, err
}
for k, v := range flat {
params[k] = fmt.Sprintf("%v", v)
}
}
params["generator"] = map[string]interface{}{
"input": map[string]argoprojiov1alpha1.PluginParameters{
"parameters": pluginParams,
},
}
err := appendTemplatedValues(appSetGenerator.Plugin.Values, params, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
if err != nil {
return nil, err
}
res = append(res, params)
}
return res, nil
}
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)
}
secretName, tokenKey := plugin.ParseSecretKey(tokenRef)
secret := &corev1.Secret{}
err := g.client.Get(
ctx,
client.ObjectKey{
Name: secretName,
Namespace: g.namespace,
},
secret)
if err != nil {
return "", fmt.Errorf("error fetching secret %s/%s: %w", g.namespace, secretName, err)
}
secretValues := make(map[string]string, len(secret.Data))
for k, v := range secret.Data {
secretValues[k] = string(v)
}
token := settings.ReplaceStringSecret(tokenKey, secretValues)
return token, err
}
func (g *PluginGenerator) getConfigMap(ctx context.Context, configMapRef string) (map[string]string, error) {
cm := &corev1.ConfigMap{}
err := g.client.Get(
ctx,
client.ObjectKey{
Name: configMapRef,
Namespace: g.namespace,
},
cm)
if err != nil {
return nil, err
}
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, fmt.Errorf("token not found in ConfigMap")
}
return cm.Data, nil
}

View File

@@ -1,701 +0,0 @@
package generators
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
kubefake "k8s.io/client-go/kubernetes/fake"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/argoproj/argo-cd/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 *v1.ConfigMap
secret *v1.Secret
inputParameters map[string]apiextensionsv1.JSON
values map[string]string
gotemplate bool
expected []map[string]interface{}
content []byte
expectedError error
}{
{
name: "simple case",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"baseUrl": "http://127.0.0.1",
"token": "$plugin.token",
},
},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
},
Data: map[string][]byte{
"plugin.token": []byte("my-secret"),
},
},
inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
gotemplate: false,
content: []byte(`{"output": {
"parameters": [{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123
}]
}}`),
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]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
},
},
},
},
expectedError: nil,
},
{
name: "simple case with values",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"baseUrl": "http://127.0.0.1",
"token": "$plugin.token",
},
},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
},
Data: map[string][]byte{
"plugin.token": []byte("my-secret"),
},
},
inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
values: map[string]string{
"valuekey1": "valuevalue1",
"valuekey2": "templated-{{key1}}",
},
gotemplate: false,
content: []byte(`{"output": {
"parameters": [{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123
}]
}}`),
expected: []map[string]interface{}{
{
"key1": "val1",
"key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123",
"values.valuekey1": "valuevalue1",
"values.valuekey2": "templated-val1",
"generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
},
},
},
},
expectedError: nil,
},
{
name: "simple case with gotemplate",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"baseUrl": "http://127.0.0.1",
"token": "$plugin.token",
},
},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
},
Data: map[string][]byte{
"plugin.token": []byte("my-secret"),
},
},
inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
gotemplate: true,
content: []byte(`{"output": {
"parameters": [{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123
}]
}}`),
expected: []map[string]interface{}{
{
"key1": "val1",
"key2": map[string]interface{}{
"key2_1": "val2_1",
"key2_2": map[string]interface{}{
"key2_2_1": "val2_2_1",
},
},
"key3": float64(123),
"generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
},
},
},
},
expectedError: nil,
},
{
name: "simple case with appended params",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"baseUrl": "http://127.0.0.1",
"token": "$plugin.token",
},
},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
},
Data: map[string][]byte{
"plugin.token": []byte("my-secret"),
},
},
inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
gotemplate: false,
content: []byte(`{"output": {"parameters": [{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123,
"pkey2": "valplugin"
}]}}`),
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]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
},
},
},
},
expectedError: nil,
},
{
name: "no params",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"baseUrl": "http://127.0.0.1",
"token": "$plugin.token",
},
},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
},
Data: map[string][]byte{
"plugin.token": []byte("my-secret"),
},
},
inputParameters: argoprojiov1alpha1.PluginParameters{},
gotemplate: false,
content: []byte(`{"output": {
"parameters": [{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123
}]
}}`),
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]interface{}{
"input": map[string]map[string]interface{}{
"parameters": {},
},
},
},
},
expectedError: nil,
},
{
name: "empty return",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"baseUrl": "http://127.0.0.1",
"token": "$plugin.token",
},
},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
},
Data: map[string][]byte{
"plugin.token": []byte("my-secret"),
},
},
inputParameters: map[string]apiextensionsv1.JSON{},
gotemplate: false,
content: []byte(`{"input": {"parameters": []}}`),
expected: []map[string]interface{}{},
expectedError: nil,
},
{
name: "wrong return",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"baseUrl": "http://127.0.0.1",
"token": "$plugin.token",
},
},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
},
Data: map[string][]byte{
"plugin.token": []byte("my-secret"),
},
},
inputParameters: map[string]apiextensionsv1.JSON{},
gotemplate: false,
content: []byte(`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: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"baseUrl": "http://127.0.0.1",
"token": "$plugin-secret:plugin.token",
},
},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "plugin-secret",
Namespace: "default",
},
Data: map[string][]byte{
"plugin.token": []byte("my-secret"),
},
},
inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
gotemplate: false,
content: []byte(`{"output": {"parameters": [{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123,
"pkey2": "valplugin"
}]}}`),
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]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
},
},
},
},
expectedError: nil,
},
{
name: "no secret",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"baseUrl": "http://127.0.0.1",
"token": "$plugin.token",
},
},
secret: &v1.Secret{},
inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
gotemplate: false,
content: []byte(`{"output": {
"parameters": [{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123
}]
}}`),
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]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
},
},
},
},
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: &v1.ConfigMap{},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
},
Data: map[string][]byte{
"plugin.token": []byte("my-secret"),
},
},
inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
gotemplate: false,
content: []byte(`{"output": {
"parameters": [{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123
}]
}}`),
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]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
},
},
},
},
expectedError: fmt.Errorf("error getting plugin from generator: error fetching ConfigMap: configmaps \"\" not found"),
},
{
name: "no baseUrl",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"token": "$plugin.token",
},
},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
},
Data: map[string][]byte{
"plugin.token": []byte("my-secret"),
},
},
inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
gotemplate: false,
content: []byte(`{"output": {
"parameters": [{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123
}]
}}`),
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]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
},
},
},
},
expectedError: fmt.Errorf("error getting plugin from generator: error fetching ConfigMap: baseUrl not found in ConfigMap"),
},
{
name: "no token",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"baseUrl": "http://127.0.0.1",
},
},
secret: &v1.Secret{},
inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
gotemplate: false,
content: []byte(`{"output": {
"parameters": [{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123
}]
}}`),
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]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
},
},
},
},
expectedError: fmt.Errorf("error getting plugin from generator: error fetching ConfigMap: token not found in ConfigMap"),
},
}
ctx := context.Background()
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
generatorConfig := argoprojiov1alpha1.ApplicationSetGenerator{
Plugin: &argoprojiov1alpha1.PluginGenerator{
ConfigMapRef: argoprojiov1alpha1.PluginConfigMapRef{Name: testCase.configmap.Name},
Input: argoprojiov1alpha1.PluginInput{
Parameters: testCase.inputParameters,
},
Values: testCase.values,
},
}
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, "$", "")]
if authHeader != "Bearer "+string(expectedToken) {
w.WriteHeader(http.StatusUnauthorized)
return
}
w.Header().Set("Content-Type", "application/json")
_, err := w.Write(testCase.content)
if err != nil {
require.NoError(t, fmt.Errorf("Error Write %w", err))
}
})
fakeServer := httptest.NewServer(handler)
defer fakeServer.Close()
if _, ok := testCase.configmap.Data["baseUrl"]; ok {
testCase.configmap.Data["baseUrl"] = fakeServer.URL
}
fakeClient := kubefake.NewSimpleClientset(append([]runtime.Object{}, testCase.configmap, testCase.secret)...)
fakeClientWithCache := fake.NewClientBuilder().WithObjects([]client.Object{testCase.configmap, testCase.secret}...).Build()
pluginGenerator := NewPluginGenerator(fakeClientWithCache, ctx, fakeClient, "default")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
GoTemplate: testCase.gotemplate,
},
}
got, err := pluginGenerator.GenerateParams(&generatorConfig, &applicationSetInfo, nil)
if err != nil {
fmt.Println(err)
}
if testCase.expectedError != nil {
require.EqualError(t, err, testCase.expectedError.Error())
} else {
require.NoError(t, err)
expectedJson, err := json.Marshal(testCase.expected)
require.NoError(t, err)
gotJson, err := json.Marshal(got)
require.NoError(t, err)
assert.JSONEq(t, string(expectedJson), string(gotJson))
}
})
}
}

View File

@@ -6,12 +6,13 @@ import (
"strconv" "strconv"
"time" "time"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/gosimple/slug" "github.com/gosimple/slug"
"github.com/argoproj/argo-cd/v2/applicationset/services/pull_request"
pullrequest "github.com/argoproj/argo-cd/v2/applicationset/services/pull_request" pullrequest "github.com/argoproj/argo-cd/v2/applicationset/services/pull_request"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
@@ -24,13 +25,13 @@ const (
type PullRequestGenerator struct { type PullRequestGenerator struct {
client client.Client client client.Client
selectServiceProviderFunc func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) selectServiceProviderFunc func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error)
SCMConfig auth SCMAuthProviders
} }
func NewPullRequestGenerator(client client.Client, scmConfig SCMConfig) Generator { func NewPullRequestGenerator(client client.Client, auth SCMAuthProviders) Generator {
g := &PullRequestGenerator{ g := &PullRequestGenerator{
client: client, client: client,
SCMConfig: scmConfig, auth: auth,
} }
g.selectServiceProviderFunc = g.selectServiceProvider g.selectServiceProviderFunc = g.selectServiceProvider
return g return g
@@ -50,7 +51,7 @@ func (g *PullRequestGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.A
return &appSetGenerator.PullRequest.Template return &appSetGenerator.PullRequest.Template
} }
func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) { func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil { if appSetGenerator == nil {
return nil, EmptyAppSetGeneratorError return nil, EmptyAppSetGeneratorError
} }
@@ -62,12 +63,12 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
ctx := context.Background() ctx := context.Background()
svc, err := g.selectServiceProviderFunc(ctx, appSetGenerator.PullRequest, applicationSetInfo) svc, err := g.selectServiceProviderFunc(ctx, appSetGenerator.PullRequest, applicationSetInfo)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to select pull request service provider: %w", err) return nil, fmt.Errorf("failed to select pull request service provider: %v", err)
} }
pulls, err := pullrequest.ListPullRequests(ctx, svc, appSetGenerator.PullRequest.Filters) pulls, err := pull_request.ListPullRequests(ctx, svc, appSetGenerator.PullRequest.Filters)
if err != nil { 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]interface{}, 0, len(pulls)) params := make([]map[string]interface{}, 0, len(pulls))
@@ -83,145 +84,97 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
} }
var shortSHALength int var shortSHALength int
var shortSHALength7 int
for _, pull := range pulls { for _, pull := range pulls {
shortSHALength = 8 shortSHALength = 8
if len(pull.HeadSHA) < 8 { if len(pull.HeadSHA) < 8 {
shortSHALength = len(pull.HeadSHA) shortSHALength = len(pull.HeadSHA)
} }
shortSHALength7 = 7 params = append(params, map[string]interface{}{
if len(pull.HeadSHA) < 7 { "number": strconv.Itoa(pull.Number),
shortSHALength7 = len(pull.HeadSHA) "branch": pull.Branch,
} "branch_slug": slug.Make(pull.Branch),
"head_sha": pull.HeadSHA,
paramMap := map[string]interface{}{ "head_short_sha": pull.HeadSHA[:shortSHALength],
"number": strconv.Itoa(pull.Number), })
"title": pull.Title,
"branch": pull.Branch,
"branch_slug": slug.Make(pull.Branch),
"target_branch": pull.TargetBranch,
"target_branch_slug": slug.Make(pull.TargetBranch),
"head_sha": pull.HeadSHA,
"head_short_sha": pull.HeadSHA[:shortSHALength],
"head_short_sha_7": pull.HeadSHA[:shortSHALength7],
"author": pull.Author,
}
// PR lables will only be supported for Go Template appsets, since fasttemplate will be deprecated.
if applicationSetInfo != nil && applicationSetInfo.Spec.GoTemplate {
paramMap["labels"] = pull.Labels
}
params = append(params, paramMap)
} }
return params, nil return params, nil
} }
// selectServiceProvider selects the provider to get pull requests from the configuration // selectServiceProvider selects the provider to get pull requests from the configuration
func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, generatorConfig *argoprojiov1alpha1.PullRequestGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) { func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, generatorConfig *argoprojiov1alpha1.PullRequestGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
if !g.enableSCMProviders {
return nil, ErrSCMProvidersDisabled
}
if err := ScmProviderAllowed(applicationSetInfo, generatorConfig, g.allowedSCMProviders); err != nil {
return nil, fmt.Errorf("scm provider not allowed: %w", err)
}
if generatorConfig.Github != nil { if generatorConfig.Github != nil {
return g.github(ctx, generatorConfig.Github, applicationSetInfo) return g.github(ctx, generatorConfig.Github, applicationSetInfo)
} }
if generatorConfig.GitLab != nil { if generatorConfig.GitLab != nil {
providerConfig := generatorConfig.GitLab providerConfig := generatorConfig.GitLab
var caCerts []byte token, err := g.getSecretRef(ctx, providerConfig.TokenRef, applicationSetInfo.Namespace)
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)
if err != nil { 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(ctx, 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)
} }
if generatorConfig.Gitea != nil { if generatorConfig.Gitea != nil {
providerConfig := generatorConfig.Gitea 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 { 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(ctx, 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 { if generatorConfig.BitbucketServer != nil {
providerConfig := generatorConfig.BitbucketServer providerConfig := generatorConfig.BitbucketServer
var caCerts []byte if providerConfig.BasicAuth != nil {
var prErr error password, err := g.getSecretRef(ctx, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace)
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 err != nil { 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) return pullrequest.NewBitbucketServiceBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.Repo)
} 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)
} else { } else {
return pullrequest.NewBitbucketServiceNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.Repo, g.scmRootCAPath, providerConfig.Insecure, caCerts) return pullrequest.NewBitbucketServiceNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.Repo)
} }
} }
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)
if err != nil {
return nil, fmt.Errorf("error fetching Secret Bearer token: %w", 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)
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err)
}
return pullrequest.NewBitbucketCloudServiceBasicAuth(providerConfig.API, providerConfig.BasicAuth.Username, password, providerConfig.Owner, providerConfig.Repo)
} else {
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)
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err)
}
return pullrequest.NewAzureDevOpsService(ctx, token, providerConfig.API, providerConfig.Organization, providerConfig.Project, providerConfig.Repo, providerConfig.Labels)
}
return nil, fmt.Errorf("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) { func (g *PullRequestGenerator) github(ctx context.Context, cfg *argoprojiov1alpha1.PullRequestGeneratorGithub, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
// use an app if it was configured // use an app if it was configured
if cfg.AppSecretName != "" { if cfg.AppSecretName != "" {
auth, err := g.GitHubApps.GetAuthSecret(ctx, cfg.AppSecretName) auth, err := g.auth.GitHubApps.GetAuthSecret(ctx, cfg.AppSecretName)
if err != nil { 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) return pullrequest.NewGithubAppService(*auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
} }
// always default to token, even if not set (public access) // 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 { if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err) return nil, fmt.Errorf("error fetching Secret token: %v", err)
} }
return pullrequest.NewGithubService(ctx, token, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels) 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

@@ -6,8 +6,9 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
pullrequest "github.com/argoproj/argo-cd/v2/applicationset/services/pull_request" pullrequest "github.com/argoproj/argo-cd/v2/applicationset/services/pull_request"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
@@ -16,23 +17,19 @@ import (
func TestPullRequestGithubGenerateParams(t *testing.T) { func TestPullRequestGithubGenerateParams(t *testing.T) {
ctx := context.Background() ctx := context.Background()
cases := []struct { cases := []struct {
selectFunc func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) selectFunc func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error)
expected []map[string]interface{} expected []map[string]interface{}
expectedErr error expectedErr error
applicationSet argoprojiov1alpha1.ApplicationSet
}{ }{
{ {
selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) { selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
return pullrequest.NewFakeService( return pullrequest.NewFakeService(
ctx, ctx,
[]*pullrequest.PullRequest{ []*pullrequest.PullRequest{
{ &pullrequest.PullRequest{
Number: 1, Number: 1,
Title: "title1", Branch: "branch1",
Branch: "branch1", HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
TargetBranch: "master",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
Author: "testName",
}, },
}, },
nil, nil,
@@ -40,16 +37,11 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
}, },
expected: []map[string]interface{}{ expected: []map[string]interface{}{
{ {
"number": "1", "number": "1",
"title": "title1", "branch": "branch1",
"branch": "branch1", "branch_slug": "branch1",
"branch_slug": "branch1", "head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"target_branch": "master", "head_short_sha": "089d92cb",
"target_branch_slug": "master",
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb",
"head_short_sha_7": "089d92c",
"author": "testName",
}, },
}, },
expectedErr: nil, expectedErr: nil,
@@ -59,13 +51,10 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
return pullrequest.NewFakeService( return pullrequest.NewFakeService(
ctx, ctx,
[]*pullrequest.PullRequest{ []*pullrequest.PullRequest{
{ &pullrequest.PullRequest{
Number: 2, Number: 2,
Title: "title2", Branch: "feat/areally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
Branch: "feat/areally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature", HeadSHA: "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
TargetBranch: "feat/anotherreally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
HeadSHA: "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
Author: "testName",
}, },
}, },
nil, nil,
@@ -73,16 +62,11 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
}, },
expected: []map[string]interface{}{ expected: []map[string]interface{}{
{ {
"number": "2", "number": "2",
"title": "title2", "branch": "feat/areally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
"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",
"branch_slug": "feat-areally-long-pull-request-name-to-test-argo", "head_sha": "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
"target_branch": "feat/anotherreally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature", "head_short_sha": "9b34ff5b",
"target_branch_slug": "feat-anotherreally-long-pull-request-name-to-test",
"head_sha": "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
"head_short_sha": "9b34ff5b",
"head_short_sha_7": "9b34ff5",
"author": "testName",
}, },
}, },
expectedErr: nil, expectedErr: nil,
@@ -92,13 +76,10 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
return pullrequest.NewFakeService( return pullrequest.NewFakeService(
ctx, ctx,
[]*pullrequest.PullRequest{ []*pullrequest.PullRequest{
{ &pullrequest.PullRequest{
Number: 1, Number: 1,
Title: "title1", Branch: "a-very-short-sha",
Branch: "a-very-short-sha", HeadSHA: "abcd",
TargetBranch: "master",
HeadSHA: "abcd",
Author: "testName",
}, },
}, },
nil, nil,
@@ -106,16 +87,11 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
}, },
expected: []map[string]interface{}{ expected: []map[string]interface{}{
{ {
"number": "1", "number": "1",
"title": "title1", "branch": "a-very-short-sha",
"branch": "a-very-short-sha", "branch_slug": "a-very-short-sha",
"branch_slug": "a-very-short-sha", "head_sha": "abcd",
"target_branch": "master", "head_short_sha": "abcd",
"target_branch_slug": "master",
"head_sha": "abcd",
"head_short_sha": "abcd",
"head_short_sha_7": "abcd",
"author": "testName",
}, },
}, },
expectedErr: nil, expectedErr: nil,
@@ -131,87 +107,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
expected: nil, expected: nil,
expectedErr: fmt.Errorf("error listing repos: fake error"), expectedErr: fmt.Errorf("error listing repos: fake error"),
}, },
{
selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
return pullrequest.NewFakeService(
ctx,
[]*pullrequest.PullRequest{
{
Number: 1,
Title: "title1",
Branch: "branch1",
TargetBranch: "master",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
Labels: []string{"preview"},
Author: "testName",
},
},
nil,
)
},
expected: []map[string]interface{}{
{
"number": "1",
"title": "title1",
"branch": "branch1",
"branch_slug": "branch1",
"target_branch": "master",
"target_branch_slug": "master",
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb",
"head_short_sha_7": "089d92c",
"labels": []string{"preview"},
"author": "testName",
},
},
expectedErr: nil,
applicationSet: argoprojiov1alpha1.ApplicationSet{
Spec: argoprojiov1alpha1.ApplicationSetSpec{
// Application set is using Go Template.
GoTemplate: true,
},
},
},
{
selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
return pullrequest.NewFakeService(
ctx,
[]*pullrequest.PullRequest{
{
Number: 1,
Title: "title1",
Branch: "branch1",
TargetBranch: "master",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
Labels: []string{"preview"},
Author: "testName",
},
},
nil,
)
},
expected: []map[string]interface{}{
{
"number": "1",
"title": "title1",
"branch": "branch1",
"branch_slug": "branch1",
"target_branch": "master",
"target_branch_slug": "master",
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb",
"head_short_sha_7": "089d92c",
"author": "testName",
},
},
expectedErr: nil,
applicationSet: argoprojiov1alpha1.ApplicationSet{
Spec: argoprojiov1alpha1.ApplicationSetSpec{
// Application set is using fasttemplate.
GoTemplate: false,
},
},
},
} }
for _, c := range cases { for _, c := range cases {
@@ -222,107 +117,73 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
PullRequest: &argoprojiov1alpha1.PullRequestGenerator{}, PullRequest: &argoprojiov1alpha1.PullRequestGenerator{},
} }
got, gotErr := gen.GenerateParams(&generatorConfig, &c.applicationSet, nil) got, gotErr := gen.GenerateParams(&generatorConfig, nil)
if c.expectedErr != nil { assert.Equal(t, c.expectedErr, gotErr)
assert.Equal(t, c.expectedErr.Error(), gotErr.Error())
} else {
require.NoError(t, gotErr)
}
assert.ElementsMatch(t, c.expected, got) assert.ElementsMatch(t, c.expected, got)
} }
} }
func TestAllowedSCMProviderPullRequest(t *testing.T) { 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 { cases := []struct {
name string name, namespace, token string
providerConfig *argoprojiov1alpha1.PullRequestGenerator ref *argoprojiov1alpha1.SecretRef
hasError bool
}{ }{
{ {
name: "Error Github", name: "valid ref",
providerConfig: &argoprojiov1alpha1.PullRequestGenerator{ ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "my-token"},
Github: &argoprojiov1alpha1.PullRequestGeneratorGithub{ namespace: "test",
API: "https://myservice.mynamespace.svc.cluster.local", token: "secret",
}, hasError: false,
},
}, },
{ {
name: "Error Gitlab", name: "nil ref",
providerConfig: &argoprojiov1alpha1.PullRequestGenerator{ ref: nil,
GitLab: &argoprojiov1alpha1.PullRequestGeneratorGitLab{ namespace: "test",
API: "https://myservice.mynamespace.svc.cluster.local", token: "",
}, hasError: false,
},
}, },
{ {
name: "Error Gitea", name: "wrong name",
providerConfig: &argoprojiov1alpha1.PullRequestGenerator{ ref: &argoprojiov1alpha1.SecretRef{SecretName: "other", Key: "my-token"},
Gitea: &argoprojiov1alpha1.PullRequestGeneratorGitea{ namespace: "test",
API: "https://myservice.mynamespace.svc.cluster.local", token: "",
}, hasError: true,
},
}, },
{ {
name: "Error Bitbucket", name: "wrong key",
providerConfig: &argoprojiov1alpha1.PullRequestGenerator{ ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "other-token"},
BitbucketServer: &argoprojiov1alpha1.PullRequestGeneratorBitbucketServer{ namespace: "test",
API: "https://myservice.mynamespace.svc.cluster.local", token: "",
}, hasError: true,
}, },
{
name: "wrong namespace",
ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "my-token"},
namespace: "other",
token: "",
hasError: true,
}, },
} }
for _, testCase := range cases { for _, c := range cases {
testCaseCopy := testCase t.Run(c.name, func(t *testing.T) {
token, err := gen.getSecretRef(ctx, c.ref, c.namespace)
t.Run(testCaseCopy.name, func(t *testing.T) { if c.hasError {
t.Parallel() assert.NotNil(t, err)
} else {
pullRequestGenerator := NewPullRequestGenerator(nil, NewSCMConfig("", []string{ assert.Nil(t, err)
"github.myorg.com",
"gitlab.myorg.com",
"gitea.myorg.com",
"bitbucket.myorg.com",
"azuredevops.myorg.com",
}, true, nil, true))
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
PullRequest: testCaseCopy.providerConfig,
}},
},
} }
assert.Equal(t, c.token, token)
_, err := pullRequestGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
require.Error(t, err, "Must return an error")
var expectedError ErrDisallowedSCMProvider
assert.ErrorAs(t, err, &expectedError)
}) })
} }
} }
func TestSCMProviderDisabled_PRGenerator(t *testing.T) {
generator := NewPullRequestGenerator(nil, NewSCMConfig("", []string{}, false, nil, true))
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
PullRequest: &argoprojiov1alpha1.PullRequestGenerator{
Github: &argoprojiov1alpha1.PullRequestGeneratorGithub{
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
}},
},
}
_, err := generator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
assert.ErrorIs(t, err, ErrSCMProvidersDisabled)
}

View File

@@ -2,19 +2,16 @@ package generators
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings" "strings"
"time" "time"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v2/applicationset/services/github_app_auth" "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/services/scm_provider"
"github.com/argoproj/argo-cd/v2/applicationset/utils" "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" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
@@ -28,38 +25,23 @@ type SCMProviderGenerator struct {
client client.Client client client.Client
// Testing hooks. // Testing hooks.
overrideProvider scm_provider.SCMProviderService overrideProvider scm_provider.SCMProviderService
SCMConfig SCMAuthProviders
}
type SCMConfig struct {
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 { type SCMAuthProviders struct {
return SCMConfig{ GitHubApps github_app_auth.Credentials
scmRootCAPath: scmRootCAPath,
allowedSCMProviders: allowedSCMProviders,
enableSCMProviders: enableSCMProviders,
GitHubApps: gitHubApps,
tokenRefStrictMode: tokenRefStrictMode,
}
} }
func NewSCMProviderGenerator(client client.Client, scmConfig SCMConfig) Generator { func NewSCMProviderGenerator(client client.Client, providers SCMAuthProviders) Generator {
return &SCMProviderGenerator{ return &SCMProviderGenerator{
client: client, client: client,
SCMConfig: scmConfig, SCMAuthProviders: providers,
} }
} }
// Testing generator // Testing generator
func NewTestSCMProviderGenerator(overrideProvider scm_provider.SCMProviderService) Generator { func NewTestSCMProviderGenerator(overrideProvider scm_provider.SCMProviderService) Generator {
return &SCMProviderGenerator{overrideProvider: overrideProvider, SCMConfig: SCMConfig{ return &SCMProviderGenerator{overrideProvider: overrideProvider}
enableSCMProviders: true,
}}
} }
func (g *SCMProviderGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration { func (g *SCMProviderGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
@@ -76,47 +58,7 @@ func (g *SCMProviderGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.A
return &appSetGenerator.SCMProvider.Template return &appSetGenerator.SCMProvider.Template
} }
var ErrSCMProvidersDisabled = errors.New("scm providers are disabled") func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
type ErrDisallowedSCMProvider struct {
Provider string
Allowed []string
}
func NewErrDisallowedSCMProvider(provider string, allowed []string) ErrDisallowedSCMProvider {
return ErrDisallowedSCMProvider{
Provider: provider,
Allowed: allowed,
}
}
func (e ErrDisallowedSCMProvider) Error() string {
return fmt.Sprintf("scm provider %q not allowed, must use one of the following: %s", e.Provider, strings.Join(e.Allowed, ", "))
}
func ScmProviderAllowed(applicationSetInfo *argoprojiov1alpha1.ApplicationSet, generator SCMGeneratorWithCustomApiUrl, allowedScmProviders []string) error {
url := generator.CustomApiUrl()
if url == "" || len(allowedScmProviders) == 0 {
return nil
}
for _, allowedScmProvider := range allowedScmProviders {
if url == allowedScmProvider {
return nil
}
}
log.WithFields(log.Fields{
common.SecurityField: common.SecurityMedium,
"applicationset": applicationSetInfo.Name,
"appSetNamespace": applicationSetInfo.Namespace,
}).Debugf("attempted to use disallowed SCM %q, must use one of the following: %s", url, strings.Join(allowedScmProviders, ", "))
return NewErrDisallowedSCMProvider(url, allowedScmProviders)
}
func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
if appSetGenerator == nil { if appSetGenerator == nil {
return nil, EmptyAppSetGeneratorError return nil, EmptyAppSetGeneratorError
} }
@@ -125,18 +67,10 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
return nil, EmptyAppSetGeneratorError return nil, EmptyAppSetGeneratorError
} }
if !g.enableSCMProviders { ctx := context.Background()
return nil, ErrSCMProvidersDisabled
}
// Create the SCM provider helper. // Create the SCM provider helper.
providerConfig := appSetGenerator.SCMProvider providerConfig := appSetGenerator.SCMProvider
if err := ScmProviderAllowed(applicationSetInfo, providerConfig, g.allowedSCMProviders); err != nil {
return nil, fmt.Errorf("scm provider not allowed: %w", err)
}
ctx := context.Background()
var provider scm_provider.SCMProviderService var provider scm_provider.SCMProviderService
if g.overrideProvider != nil { if g.overrideProvider != nil {
provider = g.overrideProvider provider = g.overrideProvider
@@ -147,83 +81,55 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
return nil, fmt.Errorf("scm provider: %w", err) return nil, fmt.Errorf("scm provider: %w", err)
} }
} else if providerConfig.Gitlab != nil { } else if providerConfig.Gitlab != nil {
providerConfig := providerConfig.Gitlab token, err := g.getSecretRef(ctx, providerConfig.Gitlab.TokenRef, applicationSetInfo.Namespace)
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)
if err != nil { 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(ctx, 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)
if err != nil { if err != nil {
return nil, fmt.Errorf("error initializing Gitlab service: %w", err) return nil, fmt.Errorf("error initializing Gitlab service: %v", err)
} }
} else if providerConfig.Gitea != nil { } else if providerConfig.Gitea != nil {
token, err := utils.GetSecretRef(ctx, g.client, providerConfig.Gitea.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode) token, err := g.getSecretRef(ctx, providerConfig.Gitea.TokenRef, applicationSetInfo.Namespace)
if err != nil { 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(ctx, 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 { if err != nil {
return nil, fmt.Errorf("error initializing Gitea service: %w", err) return nil, fmt.Errorf("error initializing Gitea service: %v", err)
} }
} else if providerConfig.BitbucketServer != nil { } else if providerConfig.BitbucketServer != nil {
providerConfig := providerConfig.BitbucketServer providerConfig := providerConfig.BitbucketServer
var caCerts []byte
var scmError error var scmError error
if providerConfig.CARef != nil { if providerConfig.BasicAuth != nil {
caCerts, scmError = utils.GetConfigMapData(ctx, g.client, providerConfig.CARef, applicationSetInfo.Namespace) password, err := g.getSecretRef(ctx, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace)
if scmError != nil {
return nil, fmt.Errorf("error fetching CA certificates from ConfigMap: %w", scmError)
}
}
if providerConfig.BearerToken != nil {
appToken, err := utils.GetSecretRef(ctx, g.client, providerConfig.BearerToken.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
if err != nil { 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) provider, scmError = scm_provider.NewBitbucketServerProviderBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.AllBranches)
} 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)
}
provider, scmError = scm_provider.NewBitbucketServerProviderBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.AllBranches, g.scmRootCAPath, providerConfig.Insecure, caCerts)
} else { } else {
provider, scmError = scm_provider.NewBitbucketServerProviderNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.AllBranches, g.scmRootCAPath, providerConfig.Insecure, caCerts) provider, scmError = scm_provider.NewBitbucketServerProviderNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.AllBranches)
} }
if scmError != nil { 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)
} }
} else if providerConfig.AzureDevOps != nil { } else if providerConfig.AzureDevOps != nil {
token, err := utils.GetSecretRef(ctx, g.client, providerConfig.AzureDevOps.AccessTokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode) token, err := g.getSecretRef(ctx, providerConfig.AzureDevOps.AccessTokenRef, applicationSetInfo.Namespace)
if err != nil { 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(ctx, 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 { 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)
} }
} else if providerConfig.Bitbucket != nil { } else if providerConfig.Bitbucket != nil {
appPassword, err := utils.GetSecretRef(ctx, g.client, providerConfig.Bitbucket.AppPasswordRef, applicationSetInfo.Namespace, g.tokenRefStrictMode) appPassword, err := g.getSecretRef(ctx, providerConfig.Bitbucket.AppPasswordRef, applicationSetInfo.Namespace)
if err != nil { 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(ctx, 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 { 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)
}
} 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)
} }
} else { } else {
return nil, fmt.Errorf("no SCM provider implementation configured") return nil, fmt.Errorf("no SCM provider implementation configured")
@@ -232,49 +138,58 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
// Find all the available repos. // Find all the available repos.
repos, err := scm_provider.ListRepos(ctx, provider, providerConfig.Filters, providerConfig.CloneProtocol) repos, err := scm_provider.ListRepos(ctx, provider, providerConfig.Filters, providerConfig.CloneProtocol)
if err != nil { 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]interface{}, 0, len(repos)) params := make([]map[string]interface{}, 0, len(repos))
var shortSHALength int var shortSHALength int
var shortSHALength7 int
for _, repo := range repos { for _, repo := range repos {
shortSHALength = 8 shortSHALength = 8
if len(repo.SHA) < 8 { if len(repo.SHA) < 8 {
shortSHALength = len(repo.SHA) shortSHALength = len(repo.SHA)
} }
shortSHALength7 = 7 params = append(params, map[string]interface{}{
if len(repo.SHA) < 7 {
shortSHALength7 = len(repo.SHA)
}
params := map[string]interface{}{
"organization": repo.Organization, "organization": repo.Organization,
"repository": repo.Repository, "repository": repo.Repository,
"url": repo.URL, "url": repo.URL,
"branch": repo.Branch, "branch": repo.Branch,
"sha": repo.SHA, "sha": repo.SHA,
"short_sha": repo.SHA[:shortSHALength], "short_sha": repo.SHA[:shortSHALength],
"short_sha_7": repo.SHA[:shortSHALength7],
"labels": strings.Join(repo.Labels, ","), "labels": strings.Join(repo.Labels, ","),
"branchNormalized": utils.SanitizeName(repo.Branch), "branchNormalized": utils.SanitizeName(repo.Branch),
} })
err := appendTemplatedValues(appSetGenerator.SCMProvider.Values, params, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions)
if err != nil {
return nil, fmt.Errorf("failed to append templated values: %w", err)
}
paramsArray = append(paramsArray, params)
} }
return paramsArray, nil return params, 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) { func (g *SCMProviderGenerator) githubProvider(ctx context.Context, github *argoprojiov1alpha1.SCMProviderGeneratorGithub, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (scm_provider.SCMProviderService, error) {
if github.AppSecretName != "" { if github.AppSecretName != "" {
auth, err := g.GitHubApps.GetAuthSecret(ctx, github.AppSecretName) auth, err := g.GitHubApps.GetAuthSecret(ctx, github.AppSecretName)
if err != nil { 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( return scm_provider.NewGithubAppProviderFor(
@@ -285,9 +200,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 { 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(ctx, github.Organization, token, github.API, github.AllBranches) return scm_provider.NewGithubProvider(ctx, github.Organization, token, github.API, github.AllBranches)
} }

View File

@@ -1,241 +1,117 @@
package generators package generators
import ( import (
"context"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider" "github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
func TestSCMProviderGenerateParams(t *testing.T) { 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 { cases := []struct {
name string name, namespace, token string
repos []*scm_provider.Repository ref *argoprojiov1alpha1.SecretRef
values map[string]string hasError bool
expected []map[string]interface{}
expectedError error
}{ }{
{ {
name: "Multiple repos with labels", name: "valid ref",
repos: []*scm_provider.Repository{ ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "my-token"},
{ namespace: "test",
Organization: "myorg", token: "secret",
Repository: "repo1", hasError: false,
URL: "git@github.com:myorg/repo1.git",
Branch: "main",
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
Labels: []string{"prod", "staging"},
},
{
Organization: "myorg",
Repository: "repo2",
URL: "git@github.com:myorg/repo2.git",
Branch: "main",
SHA: "59d0",
},
},
expected: []map[string]interface{}{
{
"organization": "myorg",
"repository": "repo1",
"url": "git@github.com:myorg/repo1.git",
"branch": "main",
"branchNormalized": "main",
"sha": "0bc57212c3cbbec69d20b34c507284bd300def5b",
"short_sha": "0bc57212",
"short_sha_7": "0bc5721",
"labels": "prod,staging",
},
{
"organization": "myorg",
"repository": "repo2",
"url": "git@github.com:myorg/repo2.git",
"branch": "main",
"branchNormalized": "main",
"sha": "59d0",
"short_sha": "59d0",
"short_sha_7": "59d0",
"labels": "",
},
},
}, },
{ {
name: "Value interpolation", name: "nil ref",
repos: []*scm_provider.Repository{ ref: nil,
{ namespace: "test",
Organization: "myorg", token: "",
Repository: "repo3", hasError: false,
URL: "git@github.com:myorg/repo3.git", },
Branch: "main", {
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b", name: "wrong name",
Labels: []string{"prod", "staging"}, ref: &argoprojiov1alpha1.SecretRef{SecretName: "other", Key: "my-token"},
}, namespace: "test",
}, token: "",
values: map[string]string{ hasError: true,
"foo": "bar", },
"should_i_force_push_to": "{{ branch }}?", {
}, name: "wrong key",
expected: []map[string]interface{}{ ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "other-token"},
{ namespace: "test",
"organization": "myorg", token: "",
"repository": "repo3", hasError: true,
"url": "git@github.com:myorg/repo3.git", },
"branch": "main", {
"branchNormalized": "main", name: "wrong namespace",
"sha": "0bc57212c3cbbec69d20b34c507284bd300def5b", ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "my-token"},
"short_sha": "0bc57212", namespace: "other",
"short_sha_7": "0bc5721", token: "",
"labels": "prod,staging", hasError: true,
"values.foo": "bar",
"values.should_i_force_push_to": "main?",
},
},
}, },
} }
for _, testCase := range cases { for _, c := range cases {
testCaseCopy := testCase t.Run(c.name, func(t *testing.T) {
token, err := gen.getSecretRef(ctx, c.ref, c.namespace)
t.Run(testCaseCopy.name, func(t *testing.T) { if c.hasError {
t.Parallel() assert.NotNil(t, err)
mockProvider := &scm_provider.MockProvider{
Repos: testCaseCopy.repos,
}
scmGenerator := &SCMProviderGenerator{overrideProvider: mockProvider, SCMConfig: SCMConfig{enableSCMProviders: true}}
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
SCMProvider: &argoprojiov1alpha1.SCMProviderGenerator{
Values: testCaseCopy.values,
},
}},
},
}
got, err := scmGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
if testCaseCopy.expectedError != nil {
assert.EqualError(t, err, testCaseCopy.expectedError.Error())
} else { } else {
require.NoError(t, err) assert.Nil(t, err)
assert.Equal(t, testCaseCopy.expected, got)
} }
assert.Equal(t, c.token, token)
}) })
} }
} }
func TestAllowedSCMProvider(t *testing.T) { func TestSCMProviderGenerateParams(t *testing.T) {
cases := []struct { mockProvider := &scm_provider.MockProvider{
name string Repos: []*scm_provider.Repository{
providerConfig *argoprojiov1alpha1.SCMProviderGenerator {
}{ Organization: "myorg",
{ Repository: "repo1",
name: "Error Github", URL: "git@github.com:myorg/repo1.git",
providerConfig: &argoprojiov1alpha1.SCMProviderGenerator{ Branch: "main",
Github: &argoprojiov1alpha1.SCMProviderGeneratorGithub{ SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
API: "https://myservice.mynamespace.svc.cluster.local", Labels: []string{"prod", "staging"},
},
}, },
}, {
{ Organization: "myorg",
name: "Error Gitlab", Repository: "repo2",
providerConfig: &argoprojiov1alpha1.SCMProviderGenerator{ URL: "git@github.com:myorg/repo2.git",
Gitlab: &argoprojiov1alpha1.SCMProviderGeneratorGitlab{ Branch: "main",
API: "https://myservice.mynamespace.svc.cluster.local", SHA: "59d0",
},
},
},
{
name: "Error Gitea",
providerConfig: &argoprojiov1alpha1.SCMProviderGenerator{
Gitea: &argoprojiov1alpha1.SCMProviderGeneratorGitea{
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
},
{
name: "Error Bitbucket",
providerConfig: &argoprojiov1alpha1.SCMProviderGenerator{
BitbucketServer: &argoprojiov1alpha1.SCMProviderGeneratorBitbucketServer{
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
},
{
name: "Error AzureDevops",
providerConfig: &argoprojiov1alpha1.SCMProviderGenerator{
AzureDevOps: &argoprojiov1alpha1.SCMProviderGeneratorAzureDevOps{
API: "https://myservice.mynamespace.svc.cluster.local",
},
}, },
}, },
} }
gen := &SCMProviderGenerator{overrideProvider: mockProvider}
for _, testCase := range cases { params, err := gen.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
testCaseCopy := testCase SCMProvider: &argoprojiov1alpha1.SCMProviderGenerator{},
}, nil)
t.Run(testCaseCopy.name, func(t *testing.T) { assert.Nil(t, err)
t.Parallel() assert.Len(t, params, 2)
assert.Equal(t, "myorg", params[0]["organization"])
scmGenerator := &SCMProviderGenerator{ assert.Equal(t, "repo1", params[0]["repository"])
SCMConfig: SCMConfig{ assert.Equal(t, "git@github.com:myorg/repo1.git", params[0]["url"])
allowedSCMProviders: []string{ assert.Equal(t, "main", params[0]["branch"])
"github.myorg.com", assert.Equal(t, "0bc57212c3cbbec69d20b34c507284bd300def5b", params[0]["sha"])
"gitlab.myorg.com", assert.Equal(t, "0bc57212", params[0]["short_sha"])
"gitea.myorg.com", assert.Equal(t, "59d0", params[1]["short_sha"])
"bitbucket.myorg.com", assert.Equal(t, "prod,staging", params[0]["labels"])
"azuredevops.myorg.com", assert.Equal(t, "repo2", params[1]["repository"])
},
enableSCMProviders: true,
},
}
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
SCMProvider: testCaseCopy.providerConfig,
}},
},
}
_, err := scmGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
require.Error(t, err, "Must return an error")
var expectedError ErrDisallowedSCMProvider
assert.ErrorAs(t, err, &expectedError)
})
}
}
func TestSCMProviderDisabled_SCMGenerator(t *testing.T) {
generator := &SCMProviderGenerator{SCMConfig: SCMConfig{enableSCMProviders: false}}
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
SCMProvider: &argoprojiov1alpha1.SCMProviderGenerator{
Github: &argoprojiov1alpha1.SCMProviderGeneratorGithub{
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
}},
},
}
_, err := generator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
assert.ErrorIs(t, err, ErrSCMProvidersDisabled)
} }

View File

@@ -1,5 +0,0 @@
package generators
type SCMGeneratorWithCustomApiUrl interface {
CustomApiUrl() string
}

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