Compare commits

...

52 Commits

Author SHA1 Message Date
github-actions[bot]
c5ea5c4df5 Bump version to 2.9.2 (#16405)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: gdsoumya <gdsoumya@users.noreply.github.com>
2023-11-20 22:45:24 +05:30
gdsoumya
c2c9746050 feat: add support for ALL_PROXY (#10451) (#16401)
Signed-off-by: xniu <cnxuniu@gmail.com>
Co-authored-by: yushiwho <cnxuniu@gmail.com>
Co-authored-by: xniu <xniu@nvidia.com>
2023-11-20 10:32:39 -05:00
gcp-cherry-pick-bot[bot]
78cd50b2c7 fix: set max for max cookie number to math.MaxInt (#16388) (#16397) 2023-11-20 10:04:04 -05:00
gcp-cherry-pick-bot[bot]
dd86b08369 docs: fix documentation of ignoreApplicationDifferences (#16365) (#16380)
Signed-off-by: Talia Stocks <928827+taliastocks@users.noreply.github.com>
Co-authored-by: Talia Stocks <928827+taliastocks@users.noreply.github.com>
2023-11-17 15:25:08 -05:00
Michael Over
0ca43663c9 bump helm 3.13.2 (#16344)
Signed-off-by: Michael Over <michael.over@mimacom.com>
Co-authored-by: Michael Over <michael.over@mimacom.com>
2023-11-15 11:36:13 -05:00
Blake Pettersson
d4c37e2521 fix: check for double definition (#16335)
A simpler fix - don't add a managed namespace to the targetObjs list if
a namespace already exists in the application source.

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
2023-11-14 15:17:20 -05:00
Michael Crenshaw
ba60fadd94 docs: fix upgrade instructions (#16327)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-11-14 13:38:50 -05:00
github-actions[bot]
58b04e5e11 Bump version to 2.9.1 (#16325)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-11-14 10:07:30 -05:00
Ishita Sequeira
3acd5ee30d fix(appset): ignoreApplicationDifferences not working (#15965) (#16299)
* fix(appset): ignoreApplicationDifferences not working



* tests, docs



* link to enhancement request



* handle error



* Update docs/operator-manual/applicationset/Controlling-Resource-Modification.md




* fix bug, fix docs



* fix docs



* normalize empty syncPolicy field



---------

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: Hugues Peccatte <hugues.peccatte@gmail.com>
2023-11-13 15:21:36 -05:00
gcp-cherry-pick-bot[bot]
45d5de702e fix: check for double definition (#15670) (#16271)
* fix: check for double definition

As found in #13965 (and as a follow-up to #13999), we also need to
define what happens if _both_ managedNamespaceMetadata _and_ an
Application manifest are both defined for the same namespace.

The idea here is that if that happens, we emit an
`ApplicationConditionRepeatedResourceWarning`, and set the sync status
to `Unknown`, since it's unclear what is supposed to happen.

The user will then have the option of removing one of the two
definitions.



* fix: check for double definition

A simpler fix - don't add a managed namespace to the targetObjs list if
a namespace already exists in the application source.



* test: add test cases

This adds a test case showing that an ns manifest will override
`managedNamespaceMetadata` without failing horribly (as it currently
does on `HEAD`), as well as a "standard" mNMd diff vs live.



---------

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
2023-11-09 17:30:21 -05:00
gcp-cherry-pick-bot[bot]
b8efc8b1ab feat(appset): fromYaml, fromYamlArray toYaml functions (#15063) (#16289)
* feat(appset): fromYaml, fromYamlArray toYaml functions



* fix(11993): Document and error messages



---------

Signed-off-by: Geoffrey Muselli <geoffrey.muselli@gmail.com>
Signed-off-by: gmuselli <geoffrey.muselli@gmail.com>
Co-authored-by: Geoffrey MUSELLI <geoffrey.muselli@gmail.com>
2023-11-09 00:30:15 -05:00
github-actions[bot]
9cf0c69bbe Bump version to 2.9.0 (#16245)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-11-05 22:42:34 -06:00
gcp-cherry-pick-bot[bot]
871b045368 fix(docs): repo field name in GCP Cloud Source Repositories should be url (#16107) (#16211)
Signed-off-by: Gestalt LUR <1104224+nyanshell@users.noreply.github.com>
Co-authored-by: Gestalt LUR <1104224+nyanshell@users.noreply.github.com>
2023-11-01 21:09:23 -04:00
gcp-cherry-pick-bot[bot]
7f9be43b8b fix(server): appset list uses argocd's namespace instead of all (#15429) (#15432) (#16203)
* fix(server): appset list uses argocd's namespace instead of all



* use lister to scope the observed namespaces based on which namespaces monitors for apps



* apply feedback



* add missing change 🤦



* update generated manifests



---------

Signed-off-by: Jorge Turrado <jorge.turrado@scrm.lidl>
Co-authored-by: Jorge Turrado Ferrero <jorge.turrado@scrm.lidl>
2023-11-01 10:57:17 -04:00
gcp-cherry-pick-bot[bot]
78ad599120 fix(ui): prevent app panel to open on app direct link click (#15040) (#16201)
Signed-off-by: Zadkiel Aharonian <hello@zadkiel.fr>
Co-authored-by: Zadkiel Aharonian <hello@zadkiel.fr>
2023-11-01 10:53:01 -04:00
gcp-cherry-pick-bot[bot]
557871d223 docs(cmp): fix CMP param getter example (#16077) (#16190) (#16197)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-11-01 10:17:54 -04:00
gcp-cherry-pick-bot[bot]
f29e4fbea3 fix(ui): Missing data in Sync Status if application never been synced (#16184) (#16187)
Signed-off-by: Rafal Pelczar <rafal@akuity.io>
Co-authored-by: Rafal <rafal@akuity.io>
2023-10-31 17:02:27 -04:00
gcp-cherry-pick-bot[bot]
766316ef74 chore(deps): bump slsa-github-generator to 1.9.0 (#16188) (#16191)
* chore(deps): bump slsa-github-generator to 1.9.0



* catch more



---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-10-31 17:01:27 -04:00
gcp-cherry-pick-bot[bot]
eb0afcbc3d chore(ci): bump cosign version (#16182) (#16183)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-10-31 13:24:07 -04:00
github-actions[bot]
0083647b8b Bump version to 2.9.0-rc4 (#16180)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-10-31 10:15:12 -04:00
gcp-cherry-pick-bot[bot]
5289315c3f Set cert resolver in notifications-controller (#15394) (#16170)
Signed-off-by: Siddhesh Ghadi <sghadi1203@gmail.com>
Co-authored-by: Siddhesh Ghadi <61187612+svghadi@users.noreply.github.com>
2023-10-30 21:16:42 -04:00
gcp-cherry-pick-bot[bot]
83ec3bfbf7 fix(application-controller): convert defaultDeploymentInformerResyncDuration to reflect 10 seconds (#16163) (#16167)
Signed-off-by: ishitasequeira <ishiseq29@gmail.com>
Co-authored-by: Ishita Sequeira <46771830+ishitasequeira@users.noreply.github.com>
2023-10-30 18:12:59 -04:00
gcp-cherry-pick-bot[bot]
5c86f758c3 fix: argocd notification controller app cluster permission issue (#16057) (#16159)
* if applicationNamespaces is not provided as input parameter, then use namespaced appClient



* fix go lint error



---------

Signed-off-by: May Zhang <may_zhang@intuit.com>
Co-authored-by: May Zhang <may_zhang@intuit.com>
2023-10-30 16:42:26 -04:00
gcp-cherry-pick-bot[bot]
67e1e04afb fix(cli): Do not error out with no errors (#15688) (#16129)
Co-authored-by: Gergely Czuczy <gergely.czuczy@sap.com>
2023-10-26 15:40:33 -04:00
Michael Crenshaw
266e92e3a1 fix(notifications): Allow notifications controller to notify on all namespaces (cherry-pick 2.9) (#15854)
* fix(notifications): Allow notifications controller to notify on all namespaces (#15702)

* Allow notifications controller to notify on all namespaces

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

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

* Add SEEK to users.md

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

* Remove unused fields

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

* Revert changes to Procfile

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

* Fix unit tests

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

* - add argocd namespaces environment variable to notifications controller

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

* - add example cluster role rbac

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

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

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

* - update base manifest

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

* - skip app processing in notification controller

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

* added unit test and updated doc

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

* added unit test and updated doc

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

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

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

---------

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

* undo unnecessary manifest changes

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

* merge upstream

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

---------

Signed-off-by: Nikolas Skoufis <nskoufis@seek.com.au>
Signed-off-by: Nik Skoufis <n.skoufis@gmail.com>
Signed-off-by: Stewart Thomson <sthomson@wynshop.com>
Signed-off-by: May Zhang <may_zhang@intuit.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: May Zhang <may_zhang@intuit.com>
Co-authored-by: Nikolas Skoufis <nskoufis@seek.com.au>
Co-authored-by: Nik Skoufis <n.skoufis@gmail.com>
Co-authored-by: Stewart Thomson <sthomson@wynshop.com>
2023-10-26 11:33:37 -04:00
github-actions[bot]
6648d31671 Bump version to 2.9.0-rc3 (#16112)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-10-25 14:06:08 -04:00
gcp-cherry-pick-bot[bot]
82185106a2 fix(ui): log button behaviors (#15848) (#16098) (#16111)
* Fixed log button behaviors



* Fixed lint-ui issues



---------

Signed-off-by: Yi Cai <yicai@redhat.com>
Co-authored-by: Yi Cai <yicai@redhat.com>
2023-10-25 13:58:39 -04:00
gcp-cherry-pick-bot[bot]
b14837e58e fix(ui): Dark theme improvements (#15891) (#16096)
Signed-off-by: Rafal Pelczar <rafal@akuity.io>
Co-authored-by: Rafal <rafpelczar@gmail.com>
2023-10-24 13:44:43 -04:00
Victor Sollerhed
9c4a90af91 chore(deps): bump kustomize to v5.2.1 (cherry-pick 2.9) (#16082)
* kustomize v5.2.1 tool-versions

Signed-off-by: Victor Sollerhed <victor.sollerhed@pagero.com>

* kustomize v5.2.1 checksums

Signed-off-by: Victor Sollerhed <victor.sollerhed@pagero.com>

---------

Signed-off-by: Victor Sollerhed <victor.sollerhed@pagero.com>
2023-10-23 17:06:29 -04:00
gcp-cherry-pick-bot[bot]
3750adefa7 fix: auto-sync fails with 'another operation is already in progress' error (#15638) (#16078)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Co-authored-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2023-10-23 09:03:53 -07:00
gcp-cherry-pick-bot[bot]
dc3d08e626 feat(cmp): Print stderr output from command even on success (#15921) (#15973) (#16075)
* feat(cmp): Print stderr output from command even on success



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



---------

Signed-off-by: Mathias Petermann <mathias.petermann@gmail.com>
Co-authored-by: Mathias Petermann <mathias.petermann@gmail.com>
2023-10-23 10:01:15 -04:00
gcp-cherry-pick-bot[bot]
eaa9af21d7 fix: ensure appset don't attempt to remove application kind in patch requests (#16056) (#16073)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Co-authored-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2023-10-23 09:32:57 -04:00
gcp-cherry-pick-bot[bot]
55e5d6bf3e fix: helm set parameter to allow passing list parameters (#15978) (#15993)
Signed-off-by: kkk777-7 <kota.kimura0725@gmail.com>
Co-authored-by: Kota Kimura <86363983+kkk777-7@users.noreply.github.com>
2023-10-16 16:47:22 -04:00
gcp-cherry-pick-bot[bot]
85422bbb17 fix(appset): performProgressiveSyncs only when the applicationset is using it (#15299) (#15990)
Signed-off-by: Eric Blackburn <eblackburn@indeed.com>
Co-authored-by: ericblackburn <eblackburn@indeed.com>
2023-10-16 16:44:24 -04:00
gcp-cherry-pick-bot[bot]
79901a4e84 fix(ui): pod count tooltip (#15928) (#15959)
Signed-off-by: Smriti Prakash <smriti_prakash@intuit.com>
Co-authored-by: smriti0710 <smriti3prakash7@gmail.com>
Co-authored-by: Smriti Prakash <smriti_prakash@intuit.com>
2023-10-13 13:07:49 -04:00
gcp-cherry-pick-bot[bot]
5506e8520c chore(deps): bump library/golang in /test/container (#15894) (#15905)
Bumps library/golang from 1.21.1 to 1.21.3.

---
updated-dependencies:
- dependency-name: library/golang
  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-10-11 11:47:01 -04:00
gcp-cherry-pick-bot[bot]
0a97e150d8 chore(deps): bump library/golang from 1.21.1 to 1.21.3 (#15895) (#15904)
Bumps library/golang from 1.21.1 to 1.21.3.

---
updated-dependencies:
- dependency-name: library/golang
  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-10-11 11:41:34 -04:00
Michael Crenshaw
e1ac2f6071 test: bump k8s versions for e2e tests (#15766) (#15792)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-10-03 22:29:49 -04:00
gcp-cherry-pick-bot[bot]
8c3f38a97d docs: fix list format (#15798) (#15804)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-10-03 16:21:38 -04:00
gcp-cherry-pick-bot[bot]
8b9c448786 fix(ui): responsive topbar, filter button in application details page (#11188) (#15789) (#15796)
Signed-off-by: Julien Fuix <julienfuix@epitech.eu>
Co-authored-by: Julien <57724344+JulienFuix@users.noreply.github.com>
Co-authored-by: Julien Fuix <julienfuix@epitech.eu>
2023-10-03 11:59:16 -04:00
gcp-cherry-pick-bot[bot]
80baeb8a6c fix(plugin): remove git environment variables unavailable to plugin execution (#14998) (#15104) (#15793)
* remove git creds environment variables unavailable to plugin execution



* remove integration test asserting askpass is forwarded



---------

Signed-off-by: jmcshane <james.mcshane@superorbital.io>
Co-authored-by: James McShane <jmcshan1@gmail.com>
2023-10-03 11:48:44 -04:00
github-actions[bot]
72f7b14594 Bump version to 2.9.0-rc2 (#15791)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-10-03 10:00:04 -04:00
gcp-cherry-pick-bot[bot]
b9bf46dfb9 fix: address nil pointer when controller runs with sts and replicas > 1 (#15770) (#15778)
* fix: address nil pointer when controller runs with sts and replicas > 1



* fix: lint



* fix: better handle errors



---------

Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>
Co-authored-by: Leonardo Luz Almeida <leoluz@users.noreply.github.com>
2023-10-03 09:34:47 -04:00
gcp-cherry-pick-bot[bot]
d7c489b9cc fix: only enable dynamic cluster sharding feature explicitly (#15734) (#15772)
* fix: only enable dynamic cluster sharding feature explicitly



---------

Signed-off-by: Remington Breeze <remington@breeze.software>
Co-authored-by: Remington Breeze <remington@breeze.software>
2023-10-02 15:00:18 -04:00
gcp-cherry-pick-bot[bot]
38eb17a027 fix(application-controller): Fix panic error when trying to scale application controller shards (#15725) (#15732)
* Added error checking to determine if application controller deployment is found or not



* Fixed the informer to list deployments in namespace scope



* Fixed readiness check probe for application controller when running as deployment



---------

Signed-off-by: Anand Francis Joseph <anjoseph@redhat.com>
Co-authored-by: Anand Francis Joseph <anjoseph@redhat.com>
2023-09-29 13:45:34 -04:00
gcp-cherry-pick-bot[bot]
ea3402962f fix(action): populate all fields of Job from CronJob (#15259) (#15727) (#15730)
Signed-off-by: sergey.ladutko <sergey.ladutko@vizor-games.com>
Co-authored-by: SergeyLadutko <40435115+SergeyLadutko@users.noreply.github.com>
Co-authored-by: sergey.ladutko <sergey.ladutko@vizor-games.com>
2023-09-29 11:45:05 -04:00
gcp-cherry-pick-bot[bot]
b3fabc23cd fix(ci): free up disk space (#15683) (#15684)
* fix(ci): free up disk space



* Update .github/workflows/image-reuse.yaml




* Update .github/workflows/image-reuse.yaml



---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Justin Marquis <76892343+34fathombelow@users.noreply.github.com>
2023-09-26 19:37:56 -04:00
gcp-cherry-pick-bot[bot]
69d6d1064b [fix] sidebar style (#15652) (#15682)
Signed-off-by: ymktmk <ymktmk.tt@gmail.com>
Co-authored-by: ymktmk <73768462+ymktmk@users.noreply.github.com>
Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
2023-09-26 18:13:25 -04:00
gcp-cherry-pick-bot[bot]
d09621d36b fix: add a not found check for application controller deployment (#15678) (#15679)
Signed-off-by: ishitasequeira <ishiseq29@gmail.com>
Co-authored-by: Ishita Sequeira <46771830+ishitasequeira@users.noreply.github.com>
2023-09-26 14:35:08 -04:00
gcp-cherry-pick-bot[bot]
728205618e chore(ci): free up disk space (#15674) (#15677)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-09-26 12:52:24 -04:00
gcp-cherry-pick-bot[bot]
912a2db05c fix(applicationset): cannot validate inherited project permissions (#9298) (#15026) (#15664)
* fix(applicationset): cannot validate inherited project permissions



* update tests to reflect behavior



---------

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>
Co-authored-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>
2023-09-25 18:58:03 -04:00
github-actions[bot]
d105196075 Bump version to 2.9.0-rc1 (#15663)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-09-25 18:54:33 -04:00
90 changed files with 1635 additions and 568 deletions

View File

@@ -361,7 +361,7 @@ jobs:
runs-on: ubuntu-22.04
strategy:
matrix:
k3s-version: [v1.27.2, v1.26.0, v1.25.4, v1.24.3]
k3s-version: [v1.28.2, v1.27.6, v1.26.9, v1.25.14]
needs:
- build-go
env:

View File

@@ -76,7 +76,7 @@ jobs:
- name: Install cosign
uses: sigstore/cosign-installer@11086d25041f77fe8fe7b9ea4e48e3b9192b8f19 # v3.1.2
with:
cosign-release: 'v2.0.0'
cosign-release: 'v2.0.2'
- uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
- uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
@@ -135,6 +135,14 @@ jobs:
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
echo "GIT_TREE_STATE=$(if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)" >> $GITHUB_ENV
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@4d9e71b726748f254fe64fa44d273194bd18ec91
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@2eb1c1961a95fc15694676618e422e8ba1d63825 #v4.1.1

View File

@@ -38,7 +38,7 @@ jobs:
packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
if: github.repository == 'argoproj/argo-cd'
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.7.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0
with:
image: quay.io/argoproj/argocd
digest: ${{ needs.argocd-image.outputs.image-digest }}
@@ -120,7 +120,7 @@ jobs:
contents: write # Needed for release uploads
if: github.repository == 'argoproj/argo-cd'
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.7.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0
with:
base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
provenance-name: "argocd-cli.intoto.jsonl"
@@ -204,7 +204,7 @@ jobs:
contents: write # Needed for release uploads
if: github.repository == 'argoproj/argo-cd'
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.7.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0
with:
base64-subjects: "${{ needs.generate-sbom.outputs.hashes }}"
provenance-name: "argocd-sbom.intoto.jsonl"

View File

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

View File

@@ -1 +1 @@
2.9.0
2.9.2

View File

@@ -16,7 +16,6 @@ package controllers
import (
"context"
"encoding/json"
"fmt"
"reflect"
"time"
@@ -25,7 +24,6 @@ import (
corev1 "k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
@@ -46,7 +44,6 @@ import (
"github.com/argoproj/argo-cd/v2/applicationset/generators"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
"github.com/argoproj/argo-cd/v2/common"
argodiff "github.com/argoproj/argo-cd/v2/util/argo/diff"
"github.com/argoproj/argo-cd/v2/util/db"
"github.com/argoproj/argo-cd/v2/util/glob"
@@ -163,13 +160,15 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
if r.EnableProgressiveSyncs {
if applicationSetInfo.Spec.Strategy == nil && len(applicationSetInfo.Status.ApplicationStatus) > 0 {
// If appset used progressive sync but stopped, clean up the progressive sync application statuses
log.Infof("Removing %v unnecessary AppStatus entries from ApplicationSet %v", len(applicationSetInfo.Status.ApplicationStatus), applicationSetInfo.Name)
err := r.setAppSetApplicationStatus(ctx, &applicationSetInfo, []argov1alpha1.ApplicationSetApplicationStatus{})
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to clear previous AppSet application statuses for %v: %w", applicationSetInfo.Name, err)
}
} else {
} else if applicationSetInfo.Spec.Strategy != nil {
// appset uses progressive sync
applications, err := r.getCurrentApplications(ctx, applicationSetInfo)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to get current applications for application set: %w", err)
@@ -439,8 +438,7 @@ func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Con
errorsByIndex[i] = fmt.Errorf("ApplicationSet %s contains applications with duplicate name: %s", applicationSetInfo.Name, app.Name)
continue
}
proj, err := r.ArgoAppClientset.ArgoprojV1alpha1().AppProjects(r.ArgoCDNamespace).Get(ctx, app.Spec.GetProject(), metav1.GetOptions{})
_, err := r.ArgoAppClientset.ArgoprojV1alpha1().AppProjects(r.ArgoCDNamespace).Get(ctx, app.Spec.GetProject(), metav1.GetOptions{})
if err != nil {
if apierr.IsNotFound(err) {
errorsByIndex[i] = fmt.Errorf("application references project %s which does not exist", app.Spec.Project)
@@ -454,15 +452,6 @@ func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Con
continue
}
conditions, err := argoutil.ValidatePermissions(ctx, &app.Spec, proj, r.ArgoDB)
if err != nil {
return nil, fmt.Errorf("error validating permissions: %s", err)
}
if len(conditions) > 0 {
errorsByIndex[i] = fmt.Errorf("application spec is invalid: %s", argoutil.FormatAppConditions(conditions))
continue
}
}
return errorsByIndex, nil
@@ -634,7 +623,7 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
},
}
action, err := utils.CreateOrUpdate(ctx, r.Client, found, func() error {
action, err := utils.CreateOrUpdate(ctx, appLog, r.Client, applicationSet.Spec.IgnoreApplicationDifferences, found, func() error {
// Copy only the Application/ObjectMeta fields that are significant, from the generatedApp
found.Spec = generatedApp.Spec
@@ -687,13 +676,6 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
found.ObjectMeta.Finalizers = generatedApp.Finalizers
found.ObjectMeta.Labels = generatedApp.Labels
if found != nil && len(found.Spec.IgnoreDifferences) > 0 {
err := applyIgnoreDifferences(applicationSet.Spec.IgnoreApplicationDifferences, found, generatedApp)
if err != nil {
return fmt.Errorf("failed to apply ignore differences: %w", err)
}
}
return controllerutil.SetControllerReference(&applicationSet, found, r.Scheme)
})
@@ -711,54 +693,6 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
return firstError
}
// applyIgnoreDifferences applies the ignore differences rules to the found application. It modifies the found application in place.
func applyIgnoreDifferences(applicationSetIgnoreDifferences argov1alpha1.ApplicationSetIgnoreDifferences, found *argov1alpha1.Application, generatedApp argov1alpha1.Application) error {
diffConfig, err := argodiff.NewDiffConfigBuilder().
WithDiffSettings(applicationSetIgnoreDifferences.ToApplicationIgnoreDifferences(), nil, false).
WithNoCache().
Build()
if err != nil {
return fmt.Errorf("failed to build diff config: %w", err)
}
unstructuredFound, err := appToUnstructured(found)
if err != nil {
return fmt.Errorf("failed to convert found application to unstructured: %w", err)
}
unstructuredGenerated, err := appToUnstructured(&generatedApp)
if err != nil {
return fmt.Errorf("failed to convert found application to unstructured: %w", err)
}
result, err := argodiff.Normalize([]*unstructured.Unstructured{unstructuredFound}, []*unstructured.Unstructured{unstructuredGenerated}, diffConfig)
if err != nil {
return fmt.Errorf("failed to normalize application spec: %w", err)
}
if len(result.Targets) != 1 {
return fmt.Errorf("expected 1 normalized application, got %d", len(result.Targets))
}
jsonNormalized, err := json.Marshal(result.Targets[0].Object)
if err != nil {
return fmt.Errorf("failed to marshal normalized app to json: %w", err)
}
err = json.Unmarshal(jsonNormalized, &found)
if err != nil {
return fmt.Errorf("failed to unmarshal normalized app json to structured app: %w", err)
}
// Prohibit jq queries from mutating silly things.
found.TypeMeta = generatedApp.TypeMeta
found.Name = generatedApp.Name
found.Namespace = generatedApp.Namespace
found.Operation = generatedApp.Operation
return nil
}
func appToUnstructured(app *argov1alpha1.Application) (*unstructured.Unstructured, error) {
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(app)
if err != nil {
return nil, fmt.Errorf("failed to convert app object to unstructured: %w", err)
}
return &unstructured.Unstructured{Object: u}, nil
}
// createInCluster will filter from the desiredApplications only the application that needs to be created
// Then it will call createOrUpdateInCluster to do the actual create
func (r *ApplicationSetReconciler) createInCluster(ctx context.Context, applicationSet argov1alpha1.ApplicationSet, desiredApplications []argov1alpha1.Application) error {
@@ -912,7 +846,11 @@ func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx conte
if len(newFinalizers) != len(app.Finalizers) {
updated := app.DeepCopy()
updated.Finalizers = newFinalizers
if err := r.Client.Patch(ctx, updated, client.MergeFrom(app)); err != nil {
patch := client.MergeFrom(app)
if log.IsLevelEnabled(log.DebugLevel) {
utils.LogPatch(appLog, patch, updated)
}
if err := r.Client.Patch(ctx, updated, patch); err != nil {
return fmt.Errorf("error updating finalizers: %w", err)
}
r.updateCache(ctx, updated, appLog)

View File

@@ -12,8 +12,6 @@ import (
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -981,6 +979,296 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
},
},
},
}, {
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1191138278
name: "Ensure that ignored targetRevision difference doesn't cause an update, even if another field changes",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
Spec: v1alpha1.ApplicationSetSpec{
IgnoreApplicationDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{".spec.source.targetRevision"}},
},
Template: v1alpha1.ApplicationSetTemplate{
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://git.example.com/test-org/test-repo.git",
TargetRevision: "foo",
},
},
},
},
},
existingApps: []v1alpha1.Application{
{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
Namespace: "namespace",
ResourceVersion: "2",
},
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://git.example.com/test-org/test-repo.git",
TargetRevision: "bar",
},
},
},
},
desiredApps: []v1alpha1.Application{
{
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
},
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://git.example.com/test-org/test-repo.git",
// The targetRevision is ignored, so this should not be updated.
TargetRevision: "foo",
// This should be updated.
Helm: &v1alpha1.ApplicationSourceHelm{
Parameters: []v1alpha1.HelmParameter{
{Name: "hi", Value: "there"},
},
},
},
},
},
},
expected: []v1alpha1.Application{
{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
Namespace: "namespace",
ResourceVersion: "3",
},
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://git.example.com/test-org/test-repo.git",
// This is the existing value from the cluster, which should not be updated because the field is ignored.
TargetRevision: "bar",
// This was missing on the cluster, so it should be added.
Helm: &v1alpha1.ApplicationSourceHelm{
Parameters: []v1alpha1.HelmParameter{
{Name: "hi", Value: "there"},
},
},
},
},
},
},
}, {
// For this use case: https://github.com/argoproj/argo-cd/pull/14743#issuecomment-1761954799
name: "ignore parameters added to a multi-source app in the cluster",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
Spec: v1alpha1.ApplicationSetSpec{
IgnoreApplicationDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{`.spec.sources[] | select(.repoURL | contains("test-repo")).helm.parameters`}},
},
Template: v1alpha1.ApplicationSetTemplate{
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Sources: []v1alpha1.ApplicationSource{
{
RepoURL: "https://git.example.com/test-org/test-repo.git",
Helm: &v1alpha1.ApplicationSourceHelm{
Values: "foo: bar",
},
},
},
},
},
},
},
existingApps: []v1alpha1.Application{
{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
Namespace: "namespace",
ResourceVersion: "2",
},
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Sources: []v1alpha1.ApplicationSource{
{
RepoURL: "https://git.example.com/test-org/test-repo.git",
Helm: &v1alpha1.ApplicationSourceHelm{
Values: "foo: bar",
Parameters: []v1alpha1.HelmParameter{
{Name: "hi", Value: "there"},
},
},
},
},
},
},
},
desiredApps: []v1alpha1.Application{
{
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
},
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Sources: []v1alpha1.ApplicationSource{
{
RepoURL: "https://git.example.com/test-org/test-repo.git",
Helm: &v1alpha1.ApplicationSourceHelm{
Values: "foo: bar",
},
},
},
},
},
},
expected: []v1alpha1.Application{
{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
Namespace: "namespace",
// This should not be updated, because reconciliation shouldn't modify the App.
ResourceVersion: "2",
},
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Sources: []v1alpha1.ApplicationSource{
{
RepoURL: "https://git.example.com/test-org/test-repo.git",
Helm: &v1alpha1.ApplicationSourceHelm{
Values: "foo: bar",
Parameters: []v1alpha1.HelmParameter{
// This existed only in the cluster, but it shouldn't be removed, because the field is ignored.
{Name: "hi", Value: "there"},
},
},
},
},
},
},
},
}, {
name: "Demonstrate limitation of MergePatch", // Maybe we can fix this in Argo CD 3.0: https://github.com/argoproj/argo-cd/issues/15975
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
Spec: v1alpha1.ApplicationSetSpec{
IgnoreApplicationDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{`.spec.sources[] | select(.repoURL | contains("test-repo")).helm.parameters`}},
},
Template: v1alpha1.ApplicationSetTemplate{
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Sources: []v1alpha1.ApplicationSource{
{
RepoURL: "https://git.example.com/test-org/test-repo.git",
Helm: &v1alpha1.ApplicationSourceHelm{
Values: "new: values",
},
},
},
},
},
},
},
existingApps: []v1alpha1.Application{
{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
Namespace: "namespace",
ResourceVersion: "2",
},
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Sources: []v1alpha1.ApplicationSource{
{
RepoURL: "https://git.example.com/test-org/test-repo.git",
Helm: &v1alpha1.ApplicationSourceHelm{
Values: "foo: bar",
Parameters: []v1alpha1.HelmParameter{
{Name: "hi", Value: "there"},
},
},
},
},
},
},
},
desiredApps: []v1alpha1.Application{
{
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
},
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Sources: []v1alpha1.ApplicationSource{
{
RepoURL: "https://git.example.com/test-org/test-repo.git",
Helm: &v1alpha1.ApplicationSourceHelm{
Values: "new: values",
},
},
},
},
},
},
expected: []v1alpha1.Application{
{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
Namespace: "namespace",
ResourceVersion: "3",
},
Spec: v1alpha1.ApplicationSpec{
Project: "project",
Sources: []v1alpha1.ApplicationSource{
{
RepoURL: "https://git.example.com/test-org/test-repo.git",
Helm: &v1alpha1.ApplicationSourceHelm{
Values: "new: values",
// The Parameters field got blown away, because the values field changed. MergePatch
// doesn't merge list items, it replaces the whole list if an item changes.
// If we eventually add a `name` field to Sources, we can use StrategicMergePatch.
},
},
},
},
},
},
},
} {
@@ -1014,7 +1302,6 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
}, got)
err = controllerutil.SetControllerReference(&c.appSet, &obj, r.Scheme)
assert.Nil(t, err)
assert.Equal(t, obj, *got)
}
})
@@ -1953,7 +2240,7 @@ func TestValidateGeneratedApplications(t *testing.T) {
}
}
func TestReconcilerValidationErrorBehaviour(t *testing.T) {
func TestReconcilerValidationProjectErrorBehaviour(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
@@ -1961,9 +2248,8 @@ func TestReconcilerValidationErrorBehaviour(t *testing.T) {
err = v1alpha1.AddToScheme(scheme)
assert.Nil(t, err)
defaultProject := v1alpha1.AppProject{
ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "argocd"},
Spec: v1alpha1.AppProjectSpec{SourceRepos: []string{"*"}, Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "https://good-cluster"}}},
project := v1alpha1.AppProject{
ObjectMeta: metav1.ObjectMeta{Name: "good-project", Namespace: "argocd"},
}
appSet := v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -1976,22 +2262,22 @@ func TestReconcilerValidationErrorBehaviour(t *testing.T) {
{
List: &v1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{{
Raw: []byte(`{"cluster": "good-cluster","url": "https://good-cluster"}`),
Raw: []byte(`{"project": "good-project"}`),
}, {
Raw: []byte(`{"cluster": "bad-cluster","url": "https://bad-cluster"}`),
Raw: []byte(`{"project": "bad-project"}`),
}},
},
},
},
Template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "{{.cluster}}",
Name: "{{.project}}",
Namespace: "argocd",
},
Spec: v1alpha1.ApplicationSpec{
Source: &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"},
Project: "default",
Destination: v1alpha1.ApplicationDestination{Server: "{{.url}}"},
Project: "{{.project}}",
Destination: v1alpha1.ApplicationDestination{Server: "https://kubernetes.default.svc"},
},
},
},
@@ -1999,17 +2285,9 @@ func TestReconcilerValidationErrorBehaviour(t *testing.T) {
kubeclientset := kubefake.NewSimpleClientset()
argoDBMock := dbmocks.ArgoDB{}
argoObjs := []runtime.Object{&defaultProject}
argoObjs := []runtime.Object{&project}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).Build()
goodCluster := v1alpha1.Cluster{Server: "https://good-cluster", Name: "good-cluster"}
badCluster := v1alpha1.Cluster{Server: "https://bad-cluster", Name: "bad-cluster"}
argoDBMock.On("GetCluster", mock.Anything, "https://good-cluster").Return(&goodCluster, nil)
argoDBMock.On("GetCluster", mock.Anything, "https://bad-cluster").Return(&badCluster, nil)
argoDBMock.On("ListClusters", mock.Anything).Return(&v1alpha1.ClusterList{Items: []v1alpha1.Cluster{
goodCluster,
}}, nil)
r := ApplicationSetReconciler{
Client: client,
Scheme: scheme,
@@ -2041,12 +2319,12 @@ func TestReconcilerValidationErrorBehaviour(t *testing.T) {
var app v1alpha1.Application
// make sure good app got created
err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-cluster"}, &app)
err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-project"}, &app)
assert.NoError(t, err)
assert.Equal(t, app.Name, "good-cluster")
assert.Equal(t, app.Name, "good-project")
// make sure bad app was not created
err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "bad-cluster"}, &app)
err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "bad-project"}, &app)
assert.Error(t, err)
}
@@ -5728,173 +6006,3 @@ func TestOwnsHandler(t *testing.T) {
})
}
}
func Test_applyIgnoreDifferences(t *testing.T) {
appMeta := metav1.TypeMeta{
APIVersion: v1alpha1.ApplicationSchemaGroupVersionKind.GroupVersion().String(),
Kind: v1alpha1.ApplicationSchemaGroupVersionKind.Kind,
}
testCases := []struct {
name string
ignoreDifferences v1alpha1.ApplicationSetIgnoreDifferences
foundApp string
generatedApp string
expectedApp string
}{
{
name: "empty ignoreDifferences",
foundApp: `
spec: {}`,
generatedApp: `
spec: {}`,
expectedApp: `
spec: {}`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1191138278
name: "ignore target revision with jq",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{".spec.source.targetRevision"}},
},
foundApp: `
spec:
source:
targetRevision: foo`,
generatedApp: `
spec:
source:
targetRevision: bar`,
expectedApp: `
spec:
source:
targetRevision: foo`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1103593714
name: "ignore helm parameter with jq",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{`.spec.source.helm.parameters | select(.name == "image.tag")`}},
},
foundApp: `
spec:
source:
helm:
parameters:
- name: image.tag
value: test
- name: another
value: value`,
generatedApp: `
spec:
source:
helm:
parameters:
- name: image.tag
value: v1.0.0
- name: another
value: value`,
expectedApp: `
spec:
source:
helm:
parameters:
- name: image.tag
value: test
- name: another
value: value`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1191138278
name: "ignore auto-sync with jq",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{".spec.syncPolicy.automated"}},
},
foundApp: `
spec:
syncPolicy:
retry:
limit: 5`,
generatedApp: `
spec:
syncPolicy:
automated:
selfHeal: true
retry:
limit: 5`,
expectedApp: `
spec:
syncPolicy:
retry:
limit: 5`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1420656537
name: "ignore a one-off annotation with jq",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{`.metadata.annotations | select(.["foo.bar"] == "baz")`}},
},
foundApp: `
metadata:
annotations:
foo.bar: baz
some.other: annotation`,
generatedApp: `
metadata:
annotations:
some.other: annotation`,
expectedApp: `
metadata:
annotations:
foo.bar: baz
some.other: annotation`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1515672638
name: "ignore the source.plugin field with a json pointer",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JSONPointers: []string{"/spec/source/plugin"}},
},
foundApp: `
spec:
source:
plugin:
parameters:
- name: url
string: https://example.com`,
generatedApp: `
spec:
source:
plugin:
parameters:
- name: url
string: https://example.com/wrong`,
expectedApp: `
spec:
source:
plugin:
parameters:
- name: url
string: https://example.com`,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
foundApp := v1alpha1.Application{TypeMeta: appMeta}
err := yaml.Unmarshal([]byte(tc.foundApp), &foundApp)
require.NoError(t, err, tc.foundApp)
generatedApp := v1alpha1.Application{TypeMeta: appMeta}
err = yaml.Unmarshal([]byte(tc.generatedApp), &generatedApp)
require.NoError(t, err, tc.generatedApp)
err = applyIgnoreDifferences(tc.ignoreDifferences, &foundApp, generatedApp)
require.NoError(t, err)
jsonFound, err := json.Marshal(tc.foundApp)
require.NoError(t, err)
jsonExpected, err := json.Marshal(tc.expectedApp)
require.NoError(t, err)
assert.Equal(t, string(jsonExpected), string(jsonFound))
})
}
}

View File

@@ -2,18 +2,24 @@ package utils
import (
"context"
"encoding/json"
"fmt"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/argo"
argodiff "github.com/argoproj/argo-cd/v2/util/argo/diff"
)
// CreateOrUpdate overrides "sigs.k8s.io/controller-runtime" function
@@ -29,7 +35,7 @@ import (
// The MutateFn is called regardless of creating or updating an object.
//
// It returns the executed operation and an error.
func CreateOrUpdate(ctx context.Context, c client.Client, obj client.Object, f controllerutil.MutateFn) (controllerutil.OperationResult, error) {
func CreateOrUpdate(ctx context.Context, logCtx *log.Entry, c client.Client, ignoreAppDifferences argov1alpha1.ApplicationSetIgnoreDifferences, obj *argov1alpha1.Application, f controllerutil.MutateFn) (controllerutil.OperationResult, error) {
key := client.ObjectKeyFromObject(obj)
if err := c.Get(ctx, key, obj); err != nil {
@@ -45,15 +51,24 @@ func CreateOrUpdate(ctx context.Context, c client.Client, obj client.Object, f c
return controllerutil.OperationResultCreated, nil
}
existingObj := obj.DeepCopyObject()
existing, ok := existingObj.(client.Object)
if !ok {
panic(fmt.Errorf("existing object is not a client.Object"))
}
normalizedLive := obj.DeepCopy()
// Mutate the live object to match the desired state.
if err := mutate(f, key, obj); err != nil {
return controllerutil.OperationResultNone, err
}
// Apply ignoreApplicationDifferences rules to remove ignored fields from both the live and the desired state. This
// prevents those differences from appearing in the diff and therefore in the patch.
err := applyIgnoreDifferences(ignoreAppDifferences, normalizedLive, obj)
if err != nil {
return controllerutil.OperationResultNone, fmt.Errorf("failed to apply ignore differences: %w", err)
}
// Normalize to avoid diffing on unimportant differences.
normalizedLive.Spec = *argo.NormalizeApplicationSpec(&normalizedLive.Spec)
obj.Spec = *argo.NormalizeApplicationSpec(&obj.Spec)
equality := conversion.EqualitiesOrDie(
func(a, b resource.Quantity) bool {
// Ignore formatting, only care that numeric value stayed the same.
@@ -79,16 +94,34 @@ func CreateOrUpdate(ctx context.Context, c client.Client, obj client.Object, f c
},
)
if equality.DeepEqual(existing, obj) {
if equality.DeepEqual(normalizedLive, obj) {
return controllerutil.OperationResultNone, nil
}
if err := c.Patch(ctx, obj, client.MergeFrom(existing)); err != nil {
patch := client.MergeFrom(normalizedLive)
if log.IsLevelEnabled(log.DebugLevel) {
LogPatch(logCtx, patch, obj)
}
if err := c.Patch(ctx, obj, patch); err != nil {
return controllerutil.OperationResultNone, err
}
return controllerutil.OperationResultUpdated, nil
}
func LogPatch(logCtx *log.Entry, patch client.Patch, obj *argov1alpha1.Application) {
patchBytes, err := patch.Data(obj)
if err != nil {
logCtx.Errorf("failed to generate patch: %v", err)
}
// Get the patch as a plain object so it is easier to work with in json logs.
var patchObj map[string]interface{}
err = json.Unmarshal(patchBytes, &patchObj)
if err != nil {
logCtx.Errorf("failed to unmarshal patch: %v", err)
}
logCtx.WithField("patch", patchObj).Debug("patching application")
}
// mutate wraps a MutateFn and applies validation to its result
func mutate(f controllerutil.MutateFn, key client.ObjectKey, obj client.Object) error {
if err := f(); err != nil {
@@ -99,3 +132,71 @@ func mutate(f controllerutil.MutateFn, key client.ObjectKey, obj client.Object)
}
return nil
}
// applyIgnoreDifferences applies the ignore differences rules to the found application. It modifies the applications in place.
func applyIgnoreDifferences(applicationSetIgnoreDifferences argov1alpha1.ApplicationSetIgnoreDifferences, found *argov1alpha1.Application, generatedApp *argov1alpha1.Application) error {
if len(applicationSetIgnoreDifferences) == 0 {
return nil
}
generatedAppCopy := generatedApp.DeepCopy()
diffConfig, err := argodiff.NewDiffConfigBuilder().
WithDiffSettings(applicationSetIgnoreDifferences.ToApplicationIgnoreDifferences(), nil, false).
WithNoCache().
Build()
if err != nil {
return fmt.Errorf("failed to build diff config: %w", err)
}
unstructuredFound, err := appToUnstructured(found)
if err != nil {
return fmt.Errorf("failed to convert found application to unstructured: %w", err)
}
unstructuredGenerated, err := appToUnstructured(generatedApp)
if err != nil {
return fmt.Errorf("failed to convert found application to unstructured: %w", err)
}
result, err := argodiff.Normalize([]*unstructured.Unstructured{unstructuredFound}, []*unstructured.Unstructured{unstructuredGenerated}, diffConfig)
if err != nil {
return fmt.Errorf("failed to normalize application spec: %w", err)
}
if len(result.Lives) != 1 {
return fmt.Errorf("expected 1 normalized application, got %d", len(result.Lives))
}
foundJsonNormalized, err := json.Marshal(result.Lives[0].Object)
if err != nil {
return fmt.Errorf("failed to marshal normalized app to json: %w", err)
}
foundNormalized := &argov1alpha1.Application{}
err = json.Unmarshal(foundJsonNormalized, &foundNormalized)
if err != nil {
return fmt.Errorf("failed to unmarshal normalized app to json: %w", err)
}
if len(result.Targets) != 1 {
return fmt.Errorf("expected 1 normalized application, got %d", len(result.Targets))
}
foundNormalized.DeepCopyInto(found)
generatedJsonNormalized, err := json.Marshal(result.Targets[0].Object)
if err != nil {
return fmt.Errorf("failed to marshal normalized app to json: %w", err)
}
generatedAppNormalized := &argov1alpha1.Application{}
err = json.Unmarshal(generatedJsonNormalized, &generatedAppNormalized)
if err != nil {
return fmt.Errorf("failed to unmarshal normalized app json to structured app: %w", err)
}
generatedAppNormalized.DeepCopyInto(generatedApp)
// Prohibit jq queries from mutating silly things.
generatedApp.TypeMeta = generatedAppCopy.TypeMeta
generatedApp.Name = generatedAppCopy.Name
generatedApp.Namespace = generatedAppCopy.Namespace
generatedApp.Operation = generatedAppCopy.Operation
return nil
}
func appToUnstructured(app client.Object) (*unstructured.Unstructured, error) {
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(app)
if err != nil {
return nil, fmt.Errorf("failed to convert app object to unstructured: %w", err)
}
return &unstructured.Unstructured{Object: u}, nil
}

View File

@@ -0,0 +1,234 @@
package utils
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func Test_applyIgnoreDifferences(t *testing.T) {
appMeta := metav1.TypeMeta{
APIVersion: v1alpha1.ApplicationSchemaGroupVersionKind.GroupVersion().String(),
Kind: v1alpha1.ApplicationSchemaGroupVersionKind.Kind,
}
testCases := []struct {
name string
ignoreDifferences v1alpha1.ApplicationSetIgnoreDifferences
foundApp string
generatedApp string
expectedApp string
}{
{
name: "empty ignoreDifferences",
foundApp: `
spec: {}`,
generatedApp: `
spec: {}`,
expectedApp: `
spec: {}`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1191138278
name: "ignore target revision with jq",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{".spec.source.targetRevision"}},
},
foundApp: `
spec:
source:
targetRevision: foo`,
generatedApp: `
spec:
source:
targetRevision: bar`,
expectedApp: `
spec:
source:
targetRevision: foo`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1103593714
name: "ignore helm parameter with jq",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{`.spec.source.helm.parameters | select(.name == "image.tag")`}},
},
foundApp: `
spec:
source:
helm:
parameters:
- name: image.tag
value: test
- name: another
value: value`,
generatedApp: `
spec:
source:
helm:
parameters:
- name: image.tag
value: v1.0.0
- name: another
value: value`,
expectedApp: `
spec:
source:
helm:
parameters:
- name: image.tag
value: test
- name: another
value: value`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1191138278
name: "ignore auto-sync in appset when it's not in the cluster with jq",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{".spec.syncPolicy.automated"}},
},
foundApp: `
spec:
syncPolicy:
retry:
limit: 5`,
generatedApp: `
spec:
syncPolicy:
automated:
selfHeal: true
retry:
limit: 5`,
expectedApp: `
spec:
syncPolicy:
retry:
limit: 5`,
},
{
name: "ignore auto-sync in the cluster when it's not in the appset with jq",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{".spec.syncPolicy.automated"}},
},
foundApp: `
spec:
syncPolicy:
automated:
selfHeal: true
retry:
limit: 5`,
generatedApp: `
spec:
syncPolicy:
retry:
limit: 5`,
expectedApp: `
spec:
syncPolicy:
automated:
selfHeal: true
retry:
limit: 5`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1420656537
name: "ignore a one-off annotation with jq",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{`.metadata.annotations | select(.["foo.bar"] == "baz")`}},
},
foundApp: `
metadata:
annotations:
foo.bar: baz
some.other: annotation`,
generatedApp: `
metadata:
annotations:
some.other: annotation`,
expectedApp: `
metadata:
annotations:
foo.bar: baz
some.other: annotation`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1515672638
name: "ignore the source.plugin field with a json pointer",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JSONPointers: []string{"/spec/source/plugin"}},
},
foundApp: `
spec:
source:
plugin:
parameters:
- name: url
string: https://example.com`,
generatedApp: `
spec:
source:
plugin:
parameters:
- name: url
string: https://example.com/wrong`,
expectedApp: `
spec:
source:
plugin:
parameters:
- name: url
string: https://example.com`,
},
{
// For this use case: https://github.com/argoproj/argo-cd/pull/14743#issuecomment-1761954799
name: "ignore parameters added to a multi-source app in the cluster",
ignoreDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
{JQPathExpressions: []string{`.spec.sources[] | select(.repoURL | contains("test-repo")).helm.parameters`}},
},
foundApp: `
spec:
sources:
- repoURL: https://git.example.com/test-org/test-repo
helm:
parameters:
- name: test
value: hi`,
generatedApp: `
spec:
sources:
- repoURL: https://git.example.com/test-org/test-repo`,
expectedApp: `
spec:
sources:
- repoURL: https://git.example.com/test-org/test-repo
helm:
parameters:
- name: test
value: hi`,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
foundApp := v1alpha1.Application{TypeMeta: appMeta}
err := yaml.Unmarshal([]byte(tc.foundApp), &foundApp)
require.NoError(t, err, tc.foundApp)
generatedApp := v1alpha1.Application{TypeMeta: appMeta}
err = yaml.Unmarshal([]byte(tc.generatedApp), &generatedApp)
require.NoError(t, err, tc.generatedApp)
err = applyIgnoreDifferences(tc.ignoreDifferences, &foundApp, &generatedApp)
require.NoError(t, err)
yamlFound, err := yaml.Marshal(tc.foundApp)
require.NoError(t, err)
yamlExpected, err := yaml.Marshal(tc.expectedApp)
require.NoError(t, err)
assert.Equal(t, string(yamlExpected), string(yamlFound))
})
}
}

View File

@@ -0,0 +1,71 @@
package utils
import (
"regexp"
"strings"
"sigs.k8s.io/yaml"
)
// SanitizeName sanitizes the name in accordance with the below rules
// 1. contain no more than 253 characters
// 2. contain only lowercase alphanumeric characters, '-' or '.'
// 3. start and end with an alphanumeric character
func SanitizeName(name string) string {
invalidDNSNameChars := regexp.MustCompile("[^-a-z0-9.]")
maxDNSNameLength := 253
name = strings.ToLower(name)
name = invalidDNSNameChars.ReplaceAllString(name, "-")
if len(name) > maxDNSNameLength {
name = name[:maxDNSNameLength]
}
return strings.Trim(name, "-.")
}
// This has been copied from helm and may be removed as soon as it is retrofited in sprig
// toYAML takes an interface, marshals it to yaml, and returns a string. It will
// always return a string, even on marshal error (empty string).
//
// This is designed to be called from a template.
func toYAML(v interface{}) (string, error) {
data, err := yaml.Marshal(v)
if err != nil {
// Swallow errors inside of a template.
return "", err
}
return strings.TrimSuffix(string(data), "\n"), nil
}
// This has been copied from helm and may be removed as soon as it is retrofited in sprig
// fromYAML converts a YAML document into a map[string]interface{}.
//
// This is not a general-purpose YAML parser, and will not parse all valid
// YAML documents. Additionally, because its intended use is within templates
// it tolerates errors. It will insert the returned error message string into
// m["Error"] in the returned map.
func fromYAML(str string) (map[string]interface{}, error) {
m := map[string]interface{}{}
if err := yaml.Unmarshal([]byte(str), &m); err != nil {
return nil, err
}
return m, nil
}
// This has been copied from helm and may be removed as soon as it is retrofited in sprig
// fromYAMLArray converts a YAML array into a []interface{}.
//
// This is not a general-purpose YAML parser, and will not parse all valid
// YAML documents. Additionally, because its intended use is within templates
// it tolerates errors. It will insert the returned error message string as
// the first and only item in the returned array.
func fromYAMLArray(str string) ([]interface{}, error) {
a := []interface{}{}
if err := yaml.Unmarshal([]byte(str), &a); err != nil {
return nil, err
}
return a, nil
}

View File

@@ -32,6 +32,9 @@ func init() {
delete(sprigFuncMap, "expandenv")
delete(sprigFuncMap, "getHostByName")
sprigFuncMap["normalize"] = SanitizeName
sprigFuncMap["toYaml"] = toYAML
sprigFuncMap["fromYaml"] = fromYAML
sprigFuncMap["fromYamlArray"] = fromYAMLArray
}
type Renderer interface {
@@ -431,23 +434,6 @@ func NormalizeBitbucketBasePath(basePath string) string {
return basePath
}
// SanitizeName sanitizes the name in accordance with the below rules
// 1. contain no more than 253 characters
// 2. contain only lowercase alphanumeric characters, '-' or '.'
// 3. start and end with an alphanumeric character
func SanitizeName(name string) string {
invalidDNSNameChars := regexp.MustCompile("[^-a-z0-9.]")
maxDNSNameLength := 253
name = strings.ToLower(name)
name = invalidDNSNameChars.ReplaceAllString(name, "-")
if len(name) > maxDNSNameLength {
name = name[:maxDNSNameLength]
}
return strings.Trim(name, "-.")
}
func getTlsConfigWithCACert(scmRootCAPath string) *tls.Config {
tlsConfig := &tls.Config{}

View File

@@ -555,6 +555,64 @@ func TestRenderTemplateParamsGoTemplate(t *testing.T) {
templateOptions: []string{"missingkey=error"},
errorMessage: `failed to execute go template --> {{.doesnotexist}} <--: template: :1:6: executing "" at <.doesnotexist>: map has no entry for key "doesnotexist"`,
},
{
name: "toYaml",
fieldVal: `{{ toYaml . | indent 2 }}`,
expectedVal: " foo:\n bar:\n bool: true\n number: 2\n str: Hello world",
params: map[string]interface{}{
"foo": map[string]interface{}{
"bar": map[string]interface{}{
"bool": true,
"number": 2,
"str": "Hello world",
},
},
},
},
{
name: "toYaml Error",
fieldVal: `{{ toYaml . | indent 2 }}`,
expectedVal: " foo:\n bar:\n bool: true\n number: 2\n str: Hello world",
errorMessage: "failed to execute go template {{ toYaml . | indent 2 }}: template: :1:3: executing \"\" at <toYaml .>: error calling toYaml: error marshaling into JSON: json: unsupported type: func(*string)",
params: map[string]interface{}{
"foo": func(test *string) {
},
},
},
{
name: "fromYaml",
fieldVal: `{{ get (fromYaml .value) "hello" }}`,
expectedVal: "world",
params: map[string]interface{}{
"value": "hello: world",
},
},
{
name: "fromYaml error",
fieldVal: `{{ get (fromYaml .value) "hello" }}`,
expectedVal: "world",
errorMessage: "failed to execute go template {{ get (fromYaml .value) \"hello\" }}: template: :1:8: executing \"\" at <fromYaml .value>: error calling fromYaml: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type map[string]interface {}",
params: map[string]interface{}{
"value": "non\n compliant\n yaml",
},
},
{
name: "fromYamlArray",
fieldVal: `{{ fromYamlArray .value | last }}`,
expectedVal: "bonjour tout le monde",
params: map[string]interface{}{
"value": "- hello world\n- bonjour tout le monde",
},
},
{
name: "fromYamlArray error",
fieldVal: `{{ fromYamlArray .value | last }}`,
expectedVal: "bonjour tout le monde",
errorMessage: "failed to execute go template {{ fromYamlArray .value | last }}: template: :1:3: executing \"\" at <fromYamlArray .value>: error calling fromYamlArray: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type []interface {}",
params: map[string]interface{}{
"value": "non\n compliant\n yaml",
},
},
}
for _, test := range tests {

View File

@@ -45,28 +45,29 @@ const (
func NewCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
appResyncPeriod int64
appHardResyncPeriod int64
repoServerAddress string
repoServerTimeoutSeconds int
selfHealTimeoutSeconds int
statusProcessors int
operationProcessors int
glogLevel int
metricsPort int
metricsCacheExpiration time.Duration
metricsAplicationLabels []string
kubectlParallelismLimit int64
cacheSource func() (*appstatecache.Cache, error)
redisClient *redis.Client
repoServerPlaintext bool
repoServerStrictTLS bool
otlpAddress string
otlpAttrs []string
applicationNamespaces []string
persistResourceHealth bool
shardingAlgorithm string
clientConfig clientcmd.ClientConfig
appResyncPeriod int64
appHardResyncPeriod int64
repoServerAddress string
repoServerTimeoutSeconds int
selfHealTimeoutSeconds int
statusProcessors int
operationProcessors int
glogLevel int
metricsPort int
metricsCacheExpiration time.Duration
metricsAplicationLabels []string
kubectlParallelismLimit int64
cacheSource func() (*appstatecache.Cache, error)
redisClient *redis.Client
repoServerPlaintext bool
repoServerStrictTLS bool
otlpAddress string
otlpAttrs []string
applicationNamespaces []string
persistResourceHealth bool
shardingAlgorithm string
enableDynamicClusterDistribution bool
)
var command = cobra.Command{
Use: cliName,
@@ -139,7 +140,7 @@ func NewCommand() *cobra.Command {
appController.InvalidateProjectsCache()
}))
kubectl := kubeutil.NewKubectl()
clusterFilter := getClusterFilter(kubeClient, settingsMgr, shardingAlgorithm)
clusterFilter := getClusterFilter(kubeClient, settingsMgr, shardingAlgorithm, enableDynamicClusterDistribution)
errors.CheckError(err)
appController, err = controller.NewApplicationController(
namespace,
@@ -204,21 +205,27 @@ func NewCommand() *cobra.Command {
command.Flags().StringSliceVar(&applicationNamespaces, "application-namespaces", env.StringsFromEnv("ARGOCD_APPLICATION_NAMESPACES", []string{}, ","), "List of additional namespaces that applications are allowed to be reconciled from")
command.Flags().BoolVar(&persistResourceHealth, "persist-resource-health", env.ParseBoolFromEnv("ARGOCD_APPLICATION_CONTROLLER_PERSIST_RESOURCE_HEALTH", true), "Enables storing the managed resources health in the Application CRD")
command.Flags().StringVar(&shardingAlgorithm, "sharding-method", env.StringFromEnv(common.EnvControllerShardingAlgorithm, common.DefaultShardingAlgorithm), "Enables choice of sharding method. Supported sharding methods are : [legacy, round-robin] ")
command.Flags().BoolVar(&enableDynamicClusterDistribution, "dynamic-cluster-distribution-enabled", env.ParseBoolFromEnv(common.EnvEnableDynamicClusterDistribution, false), "Enables dynamic cluster distribution.")
cacheSource = appstatecache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {
redisClient = client
})
return &command
}
func getClusterFilter(kubeClient *kubernetes.Clientset, settingsMgr *settings.SettingsManager, shardingAlgorithm string) sharding.ClusterFilterFunction {
func getClusterFilter(kubeClient *kubernetes.Clientset, settingsMgr *settings.SettingsManager, shardingAlgorithm string, enableDynamicClusterDistribution bool) sharding.ClusterFilterFunction {
var replicas int
shard := env.ParseNumFromEnv(common.EnvControllerShard, -1, -math.MaxInt32, math.MaxInt32)
applicationControllerName := env.StringFromEnv(common.EnvAppControllerName, common.DefaultApplicationControllerName)
appControllerDeployment, _ := kubeClient.AppsV1().Deployments(settingsMgr.GetNamespace()).Get(context.Background(), applicationControllerName, metav1.GetOptions{})
appControllerDeployment, err := kubeClient.AppsV1().Deployments(settingsMgr.GetNamespace()).Get(context.Background(), applicationControllerName, metav1.GetOptions{})
if appControllerDeployment != nil && appControllerDeployment.Spec.Replicas != nil {
// if the application controller deployment was not found, the Get() call returns an empty Deployment object. So, set the variable to nil explicitly
if err != nil && kubeerrors.IsNotFound(err) {
appControllerDeployment = nil
}
if enableDynamicClusterDistribution && appControllerDeployment != nil && appControllerDeployment.Spec.Replicas != nil {
replicas = int(*appControllerDeployment.Spec.Replicas)
} else {
replicas = env.ParseNumFromEnv(common.EnvControllerReplicas, 0, 0, math.MaxInt32)
@@ -228,7 +235,7 @@ func getClusterFilter(kubeClient *kubernetes.Clientset, settingsMgr *settings.Se
if replicas > 1 {
// check for shard mapping using configmap if application-controller is a deployment
// else use existing logic to infer shard from pod name if application-controller is a statefulset
if appControllerDeployment != nil {
if enableDynamicClusterDistribution && appControllerDeployment != nil {
var err error
// retry 3 times if we find a conflict while updating shard mapping configMap.

View File

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

View File

@@ -142,7 +142,10 @@ func testAPI(ctx context.Context, clientOpts *apiclient.ClientOptions) error {
}
defer io.Close(closer)
_, err = versionClient.Version(ctx, &empty.Empty{})
return fmt.Errorf("failed to get version: %w", err)
if err != nil {
return fmt.Errorf("failed to get version: %w", err)
}
return nil
}
// StartLocalServer allows executing command in a headless mode: on the fly starts Argo CD API server and
@@ -243,7 +246,10 @@ func StartLocalServer(ctx context.Context, clientOpts *apiclient.ClientOptions,
}
time.Sleep(time.Second)
}
return fmt.Errorf("all retries failed: %w", err)
if err != nil {
return fmt.Errorf("all retries failed: %w", err)
}
return nil
}
// NewClientOrDie creates a new API client from a set of config options, or fails fatally if the new client creation fails.

View File

@@ -120,11 +120,16 @@ func runCommand(ctx context.Context, command Command, path string, env []string)
logCtx.Error(err.Error())
return strings.TrimSuffix(output, "\n"), err
}
logCtx = logCtx.WithFields(log.Fields{
"stderr": stderr.String(),
"command": command,
})
if len(output) == 0 {
log.WithFields(log.Fields{
"stderr": stderr.String(),
"command": command,
}).Warn("Plugin command returned zero output")
logCtx.Warn("Plugin command returned zero output")
} else {
// Log stderr even on successfull commands to help develop plugins
logCtx.Info("Plugin command successfull")
}
return strings.TrimSuffix(output, "\n"), nil

View File

@@ -224,6 +224,8 @@ const (
EnvControllerShard = "ARGOCD_CONTROLLER_SHARD"
// EnvControllerShardingAlgorithm is the distribution sharding algorithm to be used: legacy or round-robin
EnvControllerShardingAlgorithm = "ARGOCD_CONTROLLER_SHARDING_ALGORITHM"
//EnvEnableDynamicClusterDistribution enables dynamic sharding (ALPHA)
EnvEnableDynamicClusterDistribution = "ARGOCD_ENABLE_DYNAMIC_CLUSTER_DISTRIBUTION"
// EnvEnableGRPCTimeHistogramEnv enables gRPC metrics collection
EnvEnableGRPCTimeHistogramEnv = "ARGOCD_ENABLE_GRPC_TIME_HISTOGRAM"
// EnvGithubAppCredsExpirationDuration controls the caching of Github app credentials. This value is in minutes (default: 60)

View File

@@ -3,6 +3,7 @@ package controller
import (
"context"
"encoding/json"
goerrors "errors"
"fmt"
"math"
"net/http"
@@ -67,7 +68,7 @@ import (
const (
updateOperationStateTimeout = 1 * time.Second
defaultDeploymentInformerResyncDuration = 10
defaultDeploymentInformerResyncDuration = 10 * time.Second
// orphanedIndex contains application which monitor orphaned resources by namespace
orphanedIndex = "orphaned"
)
@@ -208,14 +209,18 @@ func NewApplicationController(
},
})
factory := informers.NewSharedInformerFactory(ctrl.kubeClientset, defaultDeploymentInformerResyncDuration)
factory := informers.NewSharedInformerFactoryWithOptions(ctrl.kubeClientset, defaultDeploymentInformerResyncDuration, informers.WithNamespace(settingsMgr.GetNamespace()))
deploymentInformer := factory.Apps().V1().Deployments()
readinessHealthCheck := func(r *http.Request) error {
applicationControllerName := env.StringFromEnv(common.EnvAppControllerName, common.DefaultApplicationControllerName)
appControllerDeployment, err := deploymentInformer.Lister().Deployments(settingsMgr.GetNamespace()).Get(applicationControllerName)
if !kubeerrors.IsNotFound(err) {
return fmt.Errorf("error retrieving Application Controller Deployment: %s", err)
if err != nil {
if kubeerrors.IsNotFound(err) {
appControllerDeployment = nil
} else {
return fmt.Errorf("error retrieving Application Controller Deployment: %s", err)
}
}
if appControllerDeployment != nil {
if appControllerDeployment.Spec.Replicas != nil && int(*appControllerDeployment.Spec.Replicas) <= 0 {
@@ -1783,6 +1788,13 @@ func (ctrl *ApplicationController) autoSync(app *appv1.Application, syncStatus *
_, err := argo.SetAppOperation(appIf, app.Name, &op)
setOpTime := time.Since(start)
if err != nil {
if goerrors.Is(err, argo.ErrAnotherOperationInProgress) {
// skipping auto-sync because another operation is in progress and was not noticed due to stale data in informer
// it is safe to skip auto-sync because it is already running
logCtx.Warnf("Failed to initiate auto-sync to %s: %v", desiredCommitSHA, err)
return nil, 0
}
logCtx.Errorf("Failed to initiate auto-sync to %s: %v", desiredCommitSHA, err)
return &appv1.ApplicationCondition{Type: appv1.ApplicationConditionSyncError, Message: err.Error()}, setOpTime
}

View File

@@ -391,6 +391,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1
now := metav1.Now()
var manifestInfos []*apiclient.ManifestResponse
targetNsExists := false
if len(localManifests) == 0 {
// If the length of revisions is not same as the length of sources,
@@ -453,6 +454,13 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1
LastTransitionTime: &now,
})
}
// If we reach this path, this means that a namespace has been both defined in Git, as well in the
// application's managedNamespaceMetadata. We want to ensure that this manifest is the one being used instead
// of what is present in managedNamespaceMetadata.
if isManagedNamespace(targetObj, app) {
targetNsExists = true
}
}
ts.AddCheckpoint("dedup_ms")
@@ -511,7 +519,10 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1
// entry in source control. In order for the namespace not to risk being pruned, we'll need to generate a
// namespace which we can compare the live namespace with. For that, we'll do the same as is done in
// gitops-engine, the difference here being that we create a managed namespace which is only used for comparison.
if isManagedNamespace(liveObj, app) {
//
// targetNsExists == true implies that it already exists as a target, so no need to add the namespace to the
// targetObjs array.
if isManagedNamespace(liveObj, app) && !targetNsExists {
nsSpec := &v1.Namespace{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: kubeutil.NamespaceKind}, ObjectMeta: metav1.ObjectMeta{Name: liveObj.GetName()}}
managedNs, err := kubeutil.ToUnstructured(nsSpec)

View File

@@ -89,6 +89,122 @@ func TestCompareAppStateNamespaceMetadataDiffers(t *testing.T) {
assert.Len(t, app.Status.Conditions, 0)
}
// TestCompareAppStateNamespaceMetadataDiffers tests comparison when managed namespace metadata differs to live and manifest ns
func TestCompareAppStateNamespaceMetadataDiffersToManifest(t *testing.T) {
ns := NewNamespace()
ns.SetName(test.FakeDestNamespace)
ns.SetNamespace(test.FakeDestNamespace)
ns.SetAnnotations(map[string]string{"bar": "bat"})
app := newFakeApp()
app.Spec.SyncPolicy.ManagedNamespaceMetadata = &argoappv1.ManagedNamespaceMetadata{
Labels: map[string]string{
"foo": "bar",
},
Annotations: map[string]string{
"foo": "bar",
},
}
app.Status.OperationState = &argoappv1.OperationState{
SyncResult: &argoappv1.SyncOperationResult{},
}
liveNs := ns.DeepCopy()
liveNs.SetAnnotations(nil)
data := fakeData{
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{toJSON(t, liveNs)},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(ns): ns,
},
}
ctrl := newFakeController(&data)
sources := make([]argoappv1.ApplicationSource, 0)
sources = append(sources, app.Spec.GetSource())
revisions := make([]string, 0)
revisions = append(revisions, "")
compRes := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
assert.NotNil(t, compRes)
assert.NotNil(t, compRes.syncStatus)
assert.Equal(t, argoappv1.SyncStatusCodeOutOfSync, compRes.syncStatus.Status)
assert.Len(t, compRes.resources, 1)
assert.Len(t, compRes.managedResources, 1)
assert.NotNil(t, compRes.diffResultList)
assert.Len(t, compRes.diffResultList.Diffs, 1)
result := NewNamespace()
assert.NoError(t, json.Unmarshal(compRes.diffResultList.Diffs[0].PredictedLive, result))
labels := result.GetLabels()
delete(labels, "kubernetes.io/metadata.name")
assert.Equal(t, map[string]string{}, labels)
// Manifests override definitions in managedNamespaceMetadata
assert.Equal(t, map[string]string{"bar": "bat"}, result.GetAnnotations())
assert.Len(t, app.Status.Conditions, 0)
}
// TestCompareAppStateNamespaceMetadata tests comparison when managed namespace metadata differs to live
func TestCompareAppStateNamespaceMetadata(t *testing.T) {
ns := NewNamespace()
ns.SetName(test.FakeDestNamespace)
ns.SetNamespace(test.FakeDestNamespace)
ns.SetAnnotations(map[string]string{"bar": "bat"})
app := newFakeApp()
app.Spec.SyncPolicy.ManagedNamespaceMetadata = &argoappv1.ManagedNamespaceMetadata{
Labels: map[string]string{
"foo": "bar",
},
Annotations: map[string]string{
"foo": "bar",
},
}
app.Status.OperationState = &argoappv1.OperationState{
SyncResult: &argoappv1.SyncOperationResult{},
}
data := fakeData{
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(ns): ns,
},
}
ctrl := newFakeController(&data)
sources := make([]argoappv1.ApplicationSource, 0)
sources = append(sources, app.Spec.GetSource())
revisions := make([]string, 0)
revisions = append(revisions, "")
compRes := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
assert.NotNil(t, compRes)
assert.NotNil(t, compRes.syncStatus)
assert.Equal(t, argoappv1.SyncStatusCodeOutOfSync, compRes.syncStatus.Status)
assert.Len(t, compRes.resources, 1)
assert.Len(t, compRes.managedResources, 1)
assert.NotNil(t, compRes.diffResultList)
assert.Len(t, compRes.diffResultList.Diffs, 1)
result := NewNamespace()
assert.NoError(t, json.Unmarshal(compRes.diffResultList.Diffs[0].PredictedLive, result))
labels := result.GetLabels()
delete(labels, "kubernetes.io/metadata.name")
assert.Equal(t, map[string]string{"foo": "bar"}, labels)
assert.Equal(t, map[string]string{"argocd.argoproj.io/sync-options": "ServerSideApply=true", "bar": "bat", "foo": "bar"}, result.GetAnnotations())
assert.Len(t, app.Status.Conditions, 0)
}
// TestCompareAppStateNamespaceMetadataIsTheSame tests comparison when managed namespace metadata is the same
func TestCompareAppStateNamespaceMetadataIsTheSame(t *testing.T) {
app := newFakeApp()

View File

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

View File

@@ -33,6 +33,6 @@ spec:
- jsonPointers:
- /spec/source/targetRevision
- name: some-app
jqExpressions:
jqPathExpressions:
- .spec.source.helm.values

View File

@@ -6,7 +6,7 @@ These settings allow you to exert control over when, and how, changes are made t
Here are some of the controller settings that may be modified to alter the ApplicationSet controller's resource-handling behaviour.
### Dry run: prevent ApplicationSet from creating, modifying, or deleting all Applications
## Dry run: prevent ApplicationSet from creating, modifying, or deleting all Applications
To prevent the ApplicationSet controller from creating, modifying, or deleting any `Application` resources, you may enable `dry-run` mode. This essentially switches the controller into a "read only" mode, where the controller Reconcile loop will run, but no resources will be modified.
@@ -14,7 +14,7 @@ To enable dry-run, add `--dryrun true` to the ApplicationSet Deployment's contai
See 'How to modify ApplicationSet container parameters' below for detailed steps on how to add this parameter to the controller.
### Managed Applications modification Policies
## Managed Applications modification Policies
The ApplicationSet controller supports a parameter `--policy`, which is specified on launch (within the controller Deployment container), and which restricts what types of modifications will be made to managed Argo CD `Application` resources.
@@ -41,7 +41,7 @@ If the controller parameter `--policy` is set, it takes precedence on the field
This does not prevent deletion of Applications if the ApplicationSet is deleted
#### Controller parameter
### Controller parameter
To allow the ApplicationSet controller to *create* `Application` resources, but prevent any further modification, such as deletion, or modification of Application fields, add this parameter in the ApplicationSet controller:
```
@@ -59,7 +59,7 @@ spec:
applicationsSync: create-only
```
### Policy - `create-update`: Prevent ApplicationSet controller from deleting Applications
## Policy - `create-update`: Prevent ApplicationSet controller from deleting Applications
To allow the ApplicationSet controller to create or modify `Application` resources, but prevent Applications from being deleted, add the following parameter to the ApplicationSet controller `Deployment`:
```
@@ -79,7 +79,7 @@ spec:
applicationsSync: create-update
```
### Ignore certain changes to Applications
## Ignore certain changes to Applications
The ApplicationSet spec includes an `ignoreApplicationDifferences` field, which allows you to specify which fields of
the ApplicationSet should be ignored when comparing Applications.
@@ -98,11 +98,94 @@ spec:
- jsonPointers:
- /spec/source/targetRevision
- name: some-app
jqExpressions:
jqPathExpressions:
- .spec.source.helm.values
```
### Prevent an `Application`'s child resources from being deleted, when the parent Application is deleted
### Allow temporarily toggling auto-sync
One of the most common use cases for ignoring differences is to allow temporarily toggling auto-sync for an Application.
For example, if you have an ApplicationSet that is configured to automatically sync Applications, you may want to temporarily
disable auto-sync for a specific Application. You can do this by adding an ignore rule for the `spec.syncPolicy.automated` field.
```yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
spec:
ignoreApplicationDifferences:
- jsonPointers:
- /spec/syncPolicy
```
### Limitations of `ignoreApplicationDifferences`
When an ApplicationSet is reconciled, the controller will compare the ApplicationSet spec with the spec of each Application
that it manages. If there are any differences, the controller will generate a patch to update the Application to match the
ApplicationSet spec.
The generated patch is a MergePatch. According to the MergePatch documentation, "existing lists will be completely
replaced by new lists" when there is a change to the list.
This limits the effectiveness of `ignoreApplicationDifferences` when the ignored field is in a list. For example, if you
have an application with multiple sources, and you want to ignore changes to the `targetRevision` of one of the sources,
changes in other fields or in other sources will cause the entire `sources` list to be replaced, and the `targetRevision`
field will be reset to the value defined in the ApplicationSet.
For example, consider this ApplicationSet:
```yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
spec:
ignoreApplicationDifferences:
- jqPathExpressions:
- .spec.sources[] | select(.repoURL == "https://git.example.com/org/repo1").targetRevision
template:
spec:
sources:
- repoURL: https://git.example.com/org/repo1
targetRevision: main
- repoURL: https://git.example.com/org/repo2
targetRevision: main
```
You can freely change the `targetRevision` of the `repo1` source, and the ApplicationSet controller will not overwrite
your change.
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
spec:
sources:
- repoURL: https://git.example.com/org/repo1
targetRevision: fix/bug-123
- repoURL: https://git.example.com/org/repo2
targetRevision: main
```
However, if you change the `targetRevision` of the `repo2` source, the ApplicationSet controller will overwrite the entire
`sources` field.
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
spec:
sources:
- repoURL: https://git.example.com/org/repo1
targetRevision: main
- repoURL: https://git.example.com/org/repo2
targetRevision: main
```
!!! note
[Future improvements](https://github.com/argoproj/argo-cd/issues/15975) to the ApplicationSet controller may
eliminate this problem. For example, the `ref` field might be made a merge key, allowing the ApplicationSet
controller to generate and use a StrategicMergePatch instead of a MergePatch. You could then target a specific
source by `ref`, ignore changes to a field in that source, and changes to other sources would not cause the ignored
field to be overwritten.
## Prevent an `Application`'s child resources from being deleted, when the parent Application is deleted
By default, when an `Application` resource is deleted by the ApplicationSet controller, all of the child resources of the Application will be deleted as well (such as, all of the Application's `Deployments`, `Services`, etc).
@@ -119,7 +202,7 @@ spec:
More information on the specific behaviour of `preserveResourcesOnDeletion`, and deletion in ApplicationSet controller and Argo CD in general, can be found on the [Application Deletion](Application-Deletion.md) page.
### Prevent an Application's child resources from being modified
## Prevent an Application's child resources from being modified
Changes made to the ApplicationSet will propagate to the Applications managed by the ApplicationSet, and then Argo CD will propagate the Application changes to the underlying cluster resources (as per [Argo CD Integration](Argo-CD-Integration.md)).
@@ -185,6 +268,11 @@ kubectl apply -n argocd -f install.yaml
## Preserving changes made to an Applications annotations and labels
!!! note
The same behavior can be achieved on a per-app basis using the [`ignoreApplicationDifferences`](#ignore-certain-changes-to-applications)
feature described above. However, preserved fields may be configured globally, a feature that is not yet available
for `ignoreApplicationDifferences`.
It is common practice in Kubernetes to store state in annotations, operators will often make use of this. To allow for this, it is possible to configure a list of annotations that the ApplicationSet should preserve when reconciling.
For example, imagine that we have an Application created from an ApplicationSet, but a custom annotation and label has since been added (to the Application) that does not exist in the `ApplicationSet` resource:
@@ -220,3 +308,18 @@ By default, the Argo CD notifications and the Argo CD refresh type annotations a
!!!note
One can also set global preserved fields for the controller by passing a comma separated list of annotations and labels to
`ARGOCD_APPLICATIONSET_CONTROLLER_GLOBAL_PRESERVED_ANNOTATIONS` and `ARGOCD_APPLICATIONSET_CONTROLLER_GLOBAL_PRESERVED_LABELS` respectively.
## Debugging unexpected changes to Applications
When the ApplicationSet controller makes a change to an application, it logs the patch at the debug level. To see these
logs, set the log level to debug in the `argocd-cmd-params-cm` ConfigMap in the `argocd` namespace:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cmd-params-cm
namespace: argocd
data:
applicationsetcontroller.log.level: debug
```

View File

@@ -174,6 +174,18 @@ It is also possible to use Sprig functions to construct the path variables manua
| `{{path.filenameNormalized}}` | `{{.path.filenameNormalized}}` | `{{normalize .path.filename}}` |
| `{{path[N]}}` | `-` | `{{index .path.segments N}}` |
## Available template functions
ApplicationSet controller provides:
- all [sprig](http://masterminds.github.io/sprig/) Go templates function except `env`, `expandenv` and `getHostByName`
- `normalize`: sanitizes the input so that it complies with the following rules:
1. contains no more than 253 characters
2. contains only lowercase alphanumeric characters, '-' or '.'
3. starts and ends with an alphanumeric character
- `toYaml` / `fromYaml` / `fromYamlArray` helm like functions
## Examples
### Basic Go template usage

View File

@@ -44,6 +44,7 @@ spec:
args: [-c, 'echo "Initializing..."']
# The generate command runs in the Application source directory each time manifests are generated. Standard output
# must be ONLY valid Kubernetes Objects in either YAML or JSON. A non-zero exit code will fail manifest generation.
# To write log messages from the command, write them to stderr, it will always be displayed.
# Error output will be sent to the UI, so avoid printing sensitive information (such as secrets).
generate:
command: [sh, -c]
@@ -333,6 +334,7 @@ If you are actively developing a sidecar-installed CMP, keep a few things in min
3. CMP errors are cached by the repo-server in Redis. Restarting the repo-server Pod will not clear the cache. Always
do a "Hard Refresh" when actively developing a CMP so you have the latest output.
4. Verify your sidecar has started properly by viewing the Pod and seeing that two containers are running `kubectl get pod -l app.kubernetes.io/component=repo-server -n argocd`
5. Write log message to stderr and set the `--loglevel=info` flag in the sidecar. This will print everything written to stderr, even on successfull command execution.
### Other Common Errors

View File

@@ -266,7 +266,7 @@ metadata:
argocd.argoproj.io/secret-type: repository
stringData:
type: git
repo: https://source.developers.google.com/p/my-google-project/r/my-repo
url: https://source.developers.google.com/p/my-google-project/r/my-repo
gcpServiceAccountKey: |
{
"type": "service_account",

View File

@@ -17,6 +17,8 @@ which does not require a restart of the application controller pods.
## Enabling Dynamic Distribution of Clusters
This feature is disabled by default while it is in alpha. To enable it, you must set the environment `ARGOCD_ENABLE_DYNAMIC_CLUSTER_DISTRIBUTION` to true when running the Application Controller.
In order to utilize the feature, the manifests `manifests/ha/base/controller-deployment/` can be applied as a Kustomize
overlay. This overlay sets the StatefulSet replicas to `0` and deploys the application controller as a Deployment. The
dynamic distribution code automatically kicks in when the controller is deployed as a Deployment.

View File

@@ -15,58 +15,59 @@ argocd-application-controller [flags]
### Options
```
--app-hard-resync int Time period in seconds for application hard resync.
--app-resync int Time period in seconds for application resync. (default 180)
--app-state-cache-expiration duration Cache expiration for app state (default 1h0m0s)
--application-namespaces strings List of additional namespaces that applications are allowed to be reconciled from
--as string Username to impersonate for the operation
--as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
--as-uid string UID to impersonate for the operation
--certificate-authority string Path to a cert file for the certificate authority
--client-certificate string Path to a client certificate file for TLS
--client-key string Path to a client key file for TLS
--cluster string The name of the kubeconfig cluster to use
--context string The name of the kubeconfig context to use
--default-cache-expiration duration Cache expiration default (default 24h0m0s)
--gloglevel int Set the glog logging level
-h, --help help for argocd-application-controller
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
--kubeconfig string Path to a kube config. Only required if out-of-cluster
--kubectl-parallelism-limit int Number of allowed concurrent kubectl fork/execs. Any value less than 1 means no limit. (default 20)
--logformat string Set the logging format. One of: text|json (default "text")
--loglevel string Set the logging level. One of: debug|info|warn|error (default "info")
--metrics-application-labels strings List of Application labels that will be added to the argocd_application_labels metric
--metrics-cache-expiration duration Prometheus metrics cache expiration (disabled by default. e.g. 24h0m0s)
--metrics-port int Start metrics server on given port (default 8082)
-n, --namespace string If present, the namespace scope for this CLI request
--operation-processors int Number of application operation processors (default 10)
--otlp-address string OpenTelemetry collector address to send traces to
--otlp-attrs strings List of OpenTelemetry collector extra attrs when send traces, each attribute is separated by a colon(e.g. key:value)
--password string Password for basic authentication to the API server
--persist-resource-health Enables storing the managed resources health in the Application CRD (default true)
--proxy-url string If provided, this URL will be used to connect via proxy
--redis string Redis server hostname and port (e.g. argocd-redis:6379).
--redis-ca-certificate string Path to Redis server CA certificate (e.g. /etc/certs/redis/ca.crt). If not specified, system trusted CAs will be used for server certificate validation.
--redis-client-certificate string Path to Redis client certificate (e.g. /etc/certs/redis/client.crt).
--redis-client-key string Path to Redis client key (e.g. /etc/certs/redis/client.crt).
--redis-compress string Enable compression for data sent to Redis with the required compression algorithm. (possible values: gzip, none) (default "gzip")
--redis-insecure-skip-tls-verify Skip Redis server certificate validation.
--redis-use-tls Use TLS when connecting to Redis.
--redisdb int Redis database.
--repo-server string Repo server address. (default "argocd-repo-server:8081")
--repo-server-plaintext Disable TLS on connections to repo server
--repo-server-strict-tls Whether to use strict validation of the TLS cert presented by the repo server
--repo-server-timeout-seconds int Repo server RPC call timeout seconds. (default 60)
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
--self-heal-timeout-seconds int Specifies timeout between application self heal attempts (default 5)
--sentinel stringArray Redis sentinel hostname and port (e.g. argocd-redis-ha-announce-0:6379).
--sentinelmaster string Redis sentinel master group name. (default "master")
--server string The address and port of the Kubernetes API server
--sharding-method string Enables choice of sharding method. Supported sharding methods are : [legacy, round-robin] (default "legacy")
--status-processors int Number of application status processors (default 20)
--tls-server-name string If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used.
--token string Bearer token for authentication to the API server
--user string The name of the kubeconfig user to use
--username string Username for basic authentication to the API server
--app-hard-resync int Time period in seconds for application hard resync.
--app-resync int Time period in seconds for application resync. (default 180)
--app-state-cache-expiration duration Cache expiration for app state (default 1h0m0s)
--application-namespaces strings List of additional namespaces that applications are allowed to be reconciled from
--as string Username to impersonate for the operation
--as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
--as-uid string UID to impersonate for the operation
--certificate-authority string Path to a cert file for the certificate authority
--client-certificate string Path to a client certificate file for TLS
--client-key string Path to a client key file for TLS
--cluster string The name of the kubeconfig cluster to use
--context string The name of the kubeconfig context to use
--default-cache-expiration duration Cache expiration default (default 24h0m0s)
--dynamic-cluster-distribution-enabled Enables dynamic cluster distribution.
--gloglevel int Set the glog logging level
-h, --help help for argocd-application-controller
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
--kubeconfig string Path to a kube config. Only required if out-of-cluster
--kubectl-parallelism-limit int Number of allowed concurrent kubectl fork/execs. Any value less than 1 means no limit. (default 20)
--logformat string Set the logging format. One of: text|json (default "text")
--loglevel string Set the logging level. One of: debug|info|warn|error (default "info")
--metrics-application-labels strings List of Application labels that will be added to the argocd_application_labels metric
--metrics-cache-expiration duration Prometheus metrics cache expiration (disabled by default. e.g. 24h0m0s)
--metrics-port int Start metrics server on given port (default 8082)
-n, --namespace string If present, the namespace scope for this CLI request
--operation-processors int Number of application operation processors (default 10)
--otlp-address string OpenTelemetry collector address to send traces to
--otlp-attrs strings List of OpenTelemetry collector extra attrs when send traces, each attribute is separated by a colon(e.g. key:value)
--password string Password for basic authentication to the API server
--persist-resource-health Enables storing the managed resources health in the Application CRD (default true)
--proxy-url string If provided, this URL will be used to connect via proxy
--redis string Redis server hostname and port (e.g. argocd-redis:6379).
--redis-ca-certificate string Path to Redis server CA certificate (e.g. /etc/certs/redis/ca.crt). If not specified, system trusted CAs will be used for server certificate validation.
--redis-client-certificate string Path to Redis client certificate (e.g. /etc/certs/redis/client.crt).
--redis-client-key string Path to Redis client key (e.g. /etc/certs/redis/client.crt).
--redis-compress string Enable compression for data sent to Redis with the required compression algorithm. (possible values: gzip, none) (default "gzip")
--redis-insecure-skip-tls-verify Skip Redis server certificate validation.
--redis-use-tls Use TLS when connecting to Redis.
--redisdb int Redis database.
--repo-server string Repo server address. (default "argocd-repo-server:8081")
--repo-server-plaintext Disable TLS on connections to repo server
--repo-server-strict-tls Whether to use strict validation of the TLS cert presented by the repo server
--repo-server-timeout-seconds int Repo server RPC call timeout seconds. (default 60)
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
--self-heal-timeout-seconds int Specifies timeout between application self heal attempts (default 5)
--sentinel stringArray Redis sentinel hostname and port (e.g. argocd-redis-ha-announce-0:6379).
--sentinelmaster string Redis sentinel master group name. (default "master")
--server string The address and port of the Kubernetes API server
--sharding-method string Enables choice of sharding method. Supported sharding methods are : [legacy, round-robin] (default "legacy")
--status-processors int Number of application status processors (default 20)
--tls-server-name string If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used.
--token string Bearer token for authentication to the API server
--user string The name of the kubeconfig user to use
--username string Username for basic authentication to the API server
```

View File

@@ -1,6 +1,5 @@
| Argo CD version | Kubernetes versions |
|-----------------|---------------------|
| 2.9 | v1.28, v1.27, v1.26, v1.25 |
| 2.8 | v1.27, v1.26, v1.25, v1.24 |
| 2.7 | v1.26, v1.25, v1.24, v1.23 |
| 2.6 | v1.24, v1.23, v1.22 |
| 2.5 | v1.24, v1.23, v1.22 |

View File

@@ -0,0 +1,5 @@
# 2.8 to 2.9
## Upgraded Kustomize Version
Note that bundled Kustomize version has been upgraded from 5.1.0 to 5.2.1.

View File

@@ -37,6 +37,7 @@ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/<v
<hr/>
* [v2.8 to v2.9](./2.8-2.9.md)
* [v2.7 to v2.8](./2.7-2.8.md)
* [v2.6 to v2.7](./2.6-2.7.md)
* [v2.5 to v2.6](./2.5-2.6.md)

View File

@@ -31,6 +31,7 @@ This proposal is experimental, meaning after trying a single bounty, we will rev
#### Creating a Bounty
A bounty is a special proposal created under `docs/proposals/feature-bounties`.
* A bounty proposal may only be created by an existing Argo maintainer.
* The proposal document must be reviewed in regular maintainer meetings and an invitation for feedback will provide 7-days to comment.
* Bounty should have approval with [lazy-consensus](https://community.apache.org/committers/lazyConsensus.html)

View File

@@ -0,0 +1,19 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: argocd-notifications-controller-cluster-apps
app.kubernetes.io/part-of: argocd
app.kubernetes.io/component: notifications-controller
name: argocd-notifications-controller-cluster-apps
rules:
- apiGroups:
- "argoproj.io"
resources:
- "applications"
verbs:
- get
- list
- watch
- update
- patch

View File

@@ -0,0 +1,16 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
app.kubernetes.io/name: argocd-notifications-controller-cluster-apps
app.kubernetes.io/part-of: argocd
app.kubernetes.io/component: notifications-controller
name: argocd-notifications-controller-cluster-apps
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: argocd-notifications-controller-cluster-apps
subjects:
- kind: ServiceAccount
name: argocd-notifications-controller
namespace: argocd

View File

@@ -3,3 +3,5 @@ kind: Kustomization
resources:
- argocd-server-rbac-clusterrole.yaml
- argocd-server-rbac-clusterrolebinding.yaml
- argocd-notifications-controller-rbac-clusterrole.yaml
- argocd-notifications-controller-rbac-clusterrolebinding.yaml

View File

@@ -1,8 +1,8 @@
#!/bin/sh
yq e -o=json values.yaml | jq '{
yq e -o=json values.yaml | jq '[{
name: "helm-parameters",
title: "Helm Parameters",
collectionType: "map",
map: [leaf_paths as $path | {"key": $path | join("."), "value": getpath($path)|tostring}] | from_entries
}'
}]'

8
go.mod
View File

@@ -77,11 +77,11 @@ require (
go.opentelemetry.io/otel v1.16.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0
go.opentelemetry.io/otel/sdk v1.16.0
golang.org/x/crypto v0.13.0
golang.org/x/crypto v0.14.0
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
golang.org/x/oauth2 v0.11.0
golang.org/x/sync v0.3.0
golang.org/x/term v0.12.0
golang.org/x/term v0.13.0
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc
google.golang.org/grpc v1.56.2
google.golang.org/protobuf v1.31.0
@@ -261,8 +261,8 @@ require (
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.15.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/net v0.17.0
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.7.0 // indirect

12
go.sum
View File

@@ -2005,8 +2005,9 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -2158,8 +2159,9 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -2351,8 +2353,9 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -2365,8 +2368,9 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -0,0 +1 @@
55a8e6dce87a1e52c61e0ce7a89bf85b38725ba3e8deb51d4a08ade8a2c70b2d helm-v3.13.2-linux-amd64.tar.gz

View File

@@ -0,0 +1 @@
f5654aaed63a0da72852776e1d3f851b2ea9529cb5696337202703c2e1ed2321 helm-v3.13.2-linux-arm64.tar.gz

View File

@@ -0,0 +1 @@
11d96134cc4ec106c23cd8c163072e9aed6cd73e36a3da120e5876d426203f37 helm-v3.13.2-linux-ppc64le.tar.gz

View File

@@ -0,0 +1 @@
3ffc5b4a041e5306dc00905ebe5dfea449e34ada268a713d34c69709afd6a9a2 helm-v3.13.2-linux-s390x.tar.gz

View File

@@ -0,0 +1 @@
b7aba749da75d33e6fea49a5098747d379abc45583ff5cd16e2356127a396549 kustomize_5.2.1_darwin_amd64.tar.gz

View File

@@ -0,0 +1 @@
f6a5f3cffd45bac585a0c80b5ed855c2b72d932a1d6e8e7c87aae3be4eba5750 kustomize_5.2.1_darwin_arm64.tar.gz

View File

@@ -0,0 +1 @@
88346543206b889f9287c0b92c70708040ecd5aad54dd33019c4d6579cd24de8 kustomize_5.2.1_linux_amd64.tar.gz

View File

@@ -0,0 +1 @@
5566f7badece5a72d42075d8dffa6296a228966dd6ac2390de7afbb9675c3aaa kustomize_5.2.1_linux_arm64.tar.gz

View File

@@ -0,0 +1 @@
82d732cf624b6fa67dfabe751e9a1510e2d08605996b1b130b4c0f5b835b130e kustomize_5.2.1_linux_ppc64le.tar.gz

View File

@@ -0,0 +1 @@
d94cb97a2776b4685ab41233dfd5f0b426f399d2fce87d2b69e1ce4907f3aad2 kustomize_5.2.1_linux_s390x.tar.gz

View File

@@ -11,8 +11,8 @@
# Use ./hack/installers/checksums/add-helm-checksums.sh and
# add-kustomize-checksums.sh to help download checksums.
###############################################################################
helm3_version=3.12.1
helm3_version=3.13.2
kubectl_version=1.17.8
kubectx_version=0.6.3
kustomize5_version=5.1.0
kustomize5_version=5.2.1
protoc_version=3.17.3

View File

@@ -5,7 +5,7 @@ kind: Kustomization
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: latest
newTag: v2.9.2
resources:
- ./application-controller
- ./dex

View File

@@ -48,6 +48,12 @@ spec:
key: notificationscontroller.log.level
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_NAMESPACES
valueFrom:
configMapKeyRef:
key: application.namespaces
name: argocd-cmd-params-cm
optional: true
workingDir: /app
livenessProbe:
tcpSocket:

View File

@@ -32,6 +32,7 @@ rules:
- "argoproj.io"
resources:
- "applications"
- "applicationsets"
verbs:
- get
- list

View File

@@ -20742,7 +20742,7 @@ spec:
key: applicationsetcontroller.allowed.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -21042,7 +21042,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -21094,7 +21094,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -21313,7 +21313,7 @@ spec:
key: controller.kubectl.parallelism.limit
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -12,4 +12,4 @@ resources:
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: latest
newTag: v2.9.2

View File

@@ -12,7 +12,7 @@ patches:
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: latest
newTag: v2.9.2
resources:
- ../../base/application-controller
- ../../base/applicationset-controller

View File

@@ -1094,7 +1094,13 @@ spec:
args:
- /readonly/haproxy_init.sh
securityContext:
null
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: config-volume
mountPath: /readonly
@@ -1106,7 +1112,13 @@ spec:
image: haproxy:2.6.14-alpine
imagePullPolicy: IfNotPresent
securityContext:
null
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
livenessProbe:
httpGet:
path: /healthz
@@ -1204,7 +1216,14 @@ spec:
args:
- /readonly-config/init.sh
securityContext:
null
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
env:
- name: SENTINEL_ID_0
value: 3c0d9c0320bb34888c2df5757c718ce6ca992ce6
@@ -1229,7 +1248,14 @@ spec:
args:
- /data/conf/redis.conf
securityContext:
null
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
livenessProbe:
initialDelaySeconds: 30
periodSeconds: 15
@@ -1279,7 +1305,14 @@ spec:
args:
- /data/conf/sentinel.conf
securityContext:
null
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
livenessProbe:
initialDelaySeconds: 30
periodSeconds: 15
@@ -1323,7 +1356,14 @@ spec:
args:
- /readonly-config/fix-split-brain.sh
securityContext:
null
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
env:
- name: SENTINEL_ID_0
value: 3c0d9c0320bb34888c2df5757c718ce6ca992ce6

View File

@@ -20609,6 +20609,7 @@ rules:
- argoproj.io
resources:
- applications
- applicationsets
verbs:
- get
- list
@@ -21998,7 +21999,7 @@ spec:
key: applicationsetcontroller.allowed.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -22121,7 +22122,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -22191,7 +22192,13 @@ spec:
key: notificationscontroller.log.level
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
- name: ARGOCD_APPLICATION_NAMESPACES
valueFrom:
configMapKeyRef:
key: application.namespaces
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -22522,7 +22529,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -22574,7 +22581,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -22863,7 +22870,7 @@ spec:
key: server.enable.proxy.extension
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -23109,7 +23116,7 @@ spec:
key: controller.kubectl.parallelism.limit
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -1654,7 +1654,7 @@ spec:
key: applicationsetcontroller.allowed.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -1777,7 +1777,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -1847,7 +1847,13 @@ spec:
key: notificationscontroller.log.level
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
- name: ARGOCD_APPLICATION_NAMESPACES
valueFrom:
configMapKeyRef:
key: application.namespaces
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -2178,7 +2184,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -2230,7 +2236,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -2519,7 +2525,7 @@ spec:
key: server.enable.proxy.extension
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2765,7 +2771,7 @@ spec:
key: controller.kubectl.parallelism.limit
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -20568,6 +20568,7 @@ rules:
- argoproj.io
resources:
- applications
- applicationsets
verbs:
- get
- list
@@ -21093,7 +21094,7 @@ spec:
key: applicationsetcontroller.allowed.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -21216,7 +21217,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -21286,7 +21287,13 @@ spec:
key: notificationscontroller.log.level
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
- name: ARGOCD_APPLICATION_NAMESPACES
valueFrom:
configMapKeyRef:
key: application.namespaces
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -21568,7 +21575,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -21620,7 +21627,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -21907,7 +21914,7 @@ spec:
key: server.enable.proxy.extension
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -22153,7 +22160,7 @@ spec:
key: controller.kubectl.parallelism.limit
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -749,7 +749,7 @@ spec:
key: applicationsetcontroller.allowed.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -872,7 +872,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -942,7 +942,13 @@ spec:
key: notificationscontroller.log.level
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
- name: ARGOCD_APPLICATION_NAMESPACES
valueFrom:
configMapKeyRef:
key: application.namespaces
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -1224,7 +1230,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -1276,7 +1282,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -1563,7 +1569,7 @@ spec:
key: server.enable.proxy.extension
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -1809,7 +1815,7 @@ spec:
key: controller.kubectl.parallelism.limit
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.9.2
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -123,6 +123,7 @@ nav:
- operator-manual/server-commands/additional-configuration-method.md
- Upgrading:
- operator-manual/upgrading/overview.md
- operator-manual/upgrading/2.8-2.9.md
- operator-manual/upgrading/2.7-2.8.md
- operator-manual/upgrading/2.6-2.7.md
- operator-manual/upgrading/2.5-2.6.md

View File

@@ -6,10 +6,14 @@ import (
"fmt"
"time"
"github.com/argoproj/argo-cd/v2/util/glob"
"github.com/argoproj/argo-cd/v2/util/notification/k8s"
service "github.com/argoproj/argo-cd/v2/util/notification/argocd"
argocert "github.com/argoproj/argo-cd/v2/util/cert"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/argoproj/argo-cd/v2/util/notification/settings"
@@ -19,6 +23,7 @@ import (
"github.com/argoproj/notifications-engine/pkg/controller"
"github.com/argoproj/notifications-engine/pkg/services"
"github.com/argoproj/notifications-engine/pkg/subscriptions"
httputil "github.com/argoproj/notifications-engine/pkg/util/http"
log "github.com/sirupsen/logrus"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -53,14 +58,21 @@ func NewController(
client dynamic.Interface,
argocdService service.Service,
namespace string,
applicationNamespaces []string,
appLabelSelector string,
registry *controller.MetricsRegistry,
secretName string,
configMapName string,
) *notificationController {
appClient := client.Resource(applications)
appInformer := newInformer(appClient.Namespace(namespace), appLabelSelector)
appProjInformer := newInformer(newAppProjClient(client, namespace), "")
var appClient dynamic.ResourceInterface
namespaceableAppClient := client.Resource(applications)
appClient = namespaceableAppClient
if len(applicationNamespaces) == 0 {
appClient = namespaceableAppClient.Namespace(namespace)
}
appInformer := newInformer(appClient, namespace, applicationNamespaces, appLabelSelector)
appProjInformer := newInformer(newAppProjClient(client, namespace), namespace, []string{namespace}, "")
secretInformer := k8s.NewSecretInformer(k8sClient, namespace, secretName)
configMapInformer := k8s.NewConfigMapInformer(k8sClient, namespace, configMapName)
apiFactory := api.NewFactory(settings.GetFactorySettings(argocdService, secretName, configMapName), namespace, secretInformer, configMapInformer)
@@ -71,12 +83,15 @@ func NewController(
appInformer: appInformer,
appProjInformer: appProjInformer,
apiFactory: apiFactory}
res.ctrl = controller.NewController(appClient, appInformer, apiFactory,
res.ctrl = controller.NewController(namespaceableAppClient, appInformer, apiFactory,
controller.WithSkipProcessing(func(obj v1.Object) (bool, string) {
app, ok := (obj).(*unstructured.Unstructured)
if !ok {
return false, ""
}
if checkAppNotInAdditionalNamespaces(app, namespace, applicationNamespaces) {
return true, "app is not in one of the application-namespaces, nor the notification controller namespace"
}
return !isAppSyncStatusRefreshed(app, log.WithField("app", obj.GetName())), "sync status out of date"
}),
controller.WithMetricsRegistry(registry),
@@ -84,6 +99,11 @@ func NewController(
return res
}
// Check if app is not in the namespace where the controller is in, and also app is not in one of the applicationNamespaces
func checkAppNotInAdditionalNamespaces(app *unstructured.Unstructured, namespace string, applicationNamespaces []string) bool {
return namespace != app.GetNamespace() && !glob.MatchStringInList(applicationNamespaces, app.GetNamespace(), false)
}
func (c *notificationController) alterDestinations(obj v1.Object, destinations services.Destinations, cfg api.Config) services.Destinations {
app, ok := (obj).(*unstructured.Unstructured)
if !ok {
@@ -97,21 +117,38 @@ func (c *notificationController) alterDestinations(obj v1.Object, destinations s
return destinations
}
func newInformer(resClient dynamic.ResourceInterface, selector string) cache.SharedIndexInformer {
func newInformer(resClient dynamic.ResourceInterface, controllerNamespace string, applicationNamespaces []string, selector string) cache.SharedIndexInformer {
informer := cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (object runtime.Object, err error) {
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
// We are only interested in apps that exist in namespaces the
// user wants to be enabled.
options.LabelSelector = selector
return resClient.List(context.Background(), options)
appList, err := resClient.List(context.TODO(), options)
if err != nil {
return nil, fmt.Errorf("failed to list applications: %w", err)
}
newItems := []unstructured.Unstructured{}
for _, res := range appList.Items {
if controllerNamespace == res.GetNamespace() || glob.MatchStringInList(applicationNamespaces, res.GetNamespace(), false) {
newItems = append(newItems, res)
}
}
appList.Items = newItems
return appList, nil
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
options.LabelSelector = selector
return resClient.Watch(context.Background(), options)
return resClient.Watch(context.TODO(), options)
},
},
&unstructured.Unstructured{},
resyncPeriod,
cache.Indexers{},
cache.Indexers{
cache.NamespaceIndex: func(obj interface{}) ([]string, error) {
return cache.MetaNamespaceIndexFunc(obj)
},
},
)
return informer
}
@@ -126,6 +163,9 @@ type notificationController struct {
}
func (c *notificationController) Init(ctx context.Context) error {
// resolve certificates using injected "argocd-tls-certs-cm" ConfigMap
httputil.SetCertResolver(argocert.GetCertificateForConnect)
go c.appInformer.Run(ctx.Done())
go c.appProjInformer.Run(ctx.Done())
go c.secretInformer.Run(ctx.Done())

View File

@@ -115,6 +115,7 @@ func TestInit(t *testing.T) {
dynamicClient,
nil,
"default",
[]string{},
appLabelSelector,
nil,
"my-secret",
@@ -146,6 +147,7 @@ func TestInitTimeout(t *testing.T) {
dynamicClient,
nil,
"default",
[]string{},
appLabelSelector,
nil,
"my-secret",
@@ -164,3 +166,27 @@ func TestInitTimeout(t *testing.T) {
assert.Error(t, err)
assert.Equal(t, "Timed out waiting for caches to sync", err.Error())
}
func TestCheckAppNotInAdditionalNamespaces(t *testing.T) {
app := &unstructured.Unstructured{
Object: map[string]interface{}{
"spec": map[string]interface{}{},
},
}
namespace := "argocd"
var applicationNamespaces []string
applicationNamespaces = append(applicationNamespaces, "namespace1")
applicationNamespaces = append(applicationNamespaces, "namespace2")
// app is in same namespace as controller's namespace
app.SetNamespace(namespace)
assert.False(t, checkAppNotInAdditionalNamespaces(app, namespace, applicationNamespaces))
// app is not in the namespace as controller's namespace, but it is in one of the applicationNamespaces
app.SetNamespace("namespace2")
assert.False(t, checkAppNotInAdditionalNamespaces(app, "", applicationNamespaces))
// app is not in the namespace as controller's namespace, and it is not in any of the applicationNamespaces
app.SetNamespace("namespace3")
assert.True(t, checkAppNotInAdditionalNamespaces(app, "", applicationNamespaces))
}

View File

@@ -1135,11 +1135,12 @@ type SyncPolicy struct {
Retry *RetryStrategy `json:"retry,omitempty" protobuf:"bytes,3,opt,name=retry"`
// ManagedNamespaceMetadata controls metadata in the given namespace (if CreateNamespace=true)
ManagedNamespaceMetadata *ManagedNamespaceMetadata `json:"managedNamespaceMetadata,omitempty" protobuf:"bytes,4,opt,name=managedNamespaceMetadata"`
// If you add a field here, be sure to update IsZero.
}
// IsZero returns true if the sync policy is empty
func (p *SyncPolicy) IsZero() bool {
return p == nil || (p.Automated == nil && len(p.SyncOptions) == 0 && p.Retry == nil)
return p == nil || (p.Automated == nil && len(p.SyncOptions) == 0 && p.Retry == nil && p.ManagedNamespaceMetadata == nil)
}
// RetryStrategy contains information about the strategy to apply when a sync failed

View File

@@ -1381,7 +1381,7 @@ func GenerateManifests(ctx context.Context, appPath, repoRoot, revision string,
pluginName = q.ApplicationSource.Plugin.Name
}
// if pluginName is provided it has to be `<metadata.name>-<spec.version>` or just `<metadata.name>` if plugin version is empty
targetObjs, err = runConfigManagementPluginSidecars(ctx, appPath, repoRoot, pluginName, env, q, q.Repo.GetGitCreds(gitCredsStore), opt.cmpTarDoneCh, opt.cmpTarExcludedGlobs)
targetObjs, err = runConfigManagementPluginSidecars(ctx, appPath, repoRoot, pluginName, env, q, opt.cmpTarDoneCh, opt.cmpTarExcludedGlobs)
if err != nil {
err = fmt.Errorf("plugin sidecar failed. %s", err.Error())
}
@@ -1845,25 +1845,17 @@ func makeJsonnetVm(appPath string, repoRoot string, sourceJsonnet v1alpha1.Appli
return vm, nil
}
func getPluginEnvs(env *v1alpha1.Env, q *apiclient.ManifestRequest, creds git.Creds) ([]string, error) {
func getPluginEnvs(env *v1alpha1.Env, q *apiclient.ManifestRequest) ([]string, error) {
envVars := env.Environ()
envVars = append(envVars, "KUBE_VERSION="+text.SemVer(q.KubeVersion))
envVars = append(envVars, "KUBE_API_VERSIONS="+strings.Join(q.ApiVersions, ","))
return getPluginParamEnvs(envVars, q.ApplicationSource.Plugin, creds)
return getPluginParamEnvs(envVars, q.ApplicationSource.Plugin)
}
// getPluginParamEnvs gets environment variables for plugin parameter announcement generation.
func getPluginParamEnvs(envVars []string, plugin *v1alpha1.ApplicationSourcePlugin, creds git.Creds) ([]string, error) {
func getPluginParamEnvs(envVars []string, plugin *v1alpha1.ApplicationSourcePlugin) ([]string, error) {
env := envVars
if creds != nil {
closer, environ, err := creds.Environ()
if err != nil {
return nil, err
}
defer func() { _ = closer.Close() }()
env = append(env, environ...)
}
parsedEnv := make(v1alpha1.Env, len(env))
for i, v := range env {
@@ -1890,9 +1882,9 @@ func getPluginParamEnvs(envVars []string, plugin *v1alpha1.ApplicationSourcePlug
return env, nil
}
func runConfigManagementPluginSidecars(ctx context.Context, appPath, repoPath, pluginName string, envVars *v1alpha1.Env, q *apiclient.ManifestRequest, creds git.Creds, tarDoneCh chan<- bool, tarExcludedGlobs []string) ([]*unstructured.Unstructured, error) {
func runConfigManagementPluginSidecars(ctx context.Context, appPath, repoPath, pluginName string, envVars *v1alpha1.Env, q *apiclient.ManifestRequest, tarDoneCh chan<- bool, tarExcludedGlobs []string) ([]*unstructured.Unstructured, error) {
// compute variables.
env, err := getPluginEnvs(envVars, q, creds)
env, err := getPluginEnvs(envVars, q)
if err != nil {
return nil, err
}
@@ -2134,8 +2126,6 @@ func populateKustomizeAppDetails(res *apiclient.RepoAppDetailsResponse, q *apicl
func populatePluginAppDetails(ctx context.Context, res *apiclient.RepoAppDetailsResponse, appPath string, repoPath string, q *apiclient.RepoServerAppDetailsQuery, store git.CredsStore, tarExcludedGlobs []string) error {
res.Plugin = &apiclient.PluginAppSpec{}
creds := q.Repo.GetGitCreds(store)
envVars := []string{
fmt.Sprintf("ARGOCD_APP_NAME=%s", q.AppName),
fmt.Sprintf("ARGOCD_APP_SOURCE_REPO_URL=%s", q.Repo.Repo),
@@ -2143,7 +2133,7 @@ func populatePluginAppDetails(ctx context.Context, res *apiclient.RepoAppDetails
fmt.Sprintf("ARGOCD_APP_SOURCE_TARGET_REVISION=%s", q.Source.TargetRevision),
}
env, err := getPluginParamEnvs(envVars, q.Source.Plugin, creds)
env, err := getPluginParamEnvs(envVars, q.Source.Plugin)
if err != nil {
return fmt.Errorf("failed to get env vars for plugin: %w", err)
}

View File

@@ -47,11 +47,7 @@ ownerRef.uid = obj.metadata.uid
job.metadata.ownerReferences = {}
job.metadata.ownerReferences[1] = ownerRef
job.spec = {}
job.spec.suspend = false
job.spec.template = {}
job.spec.template.metadata = deepCopy(obj.spec.jobTemplate.spec.template.metadata)
job.spec.template.spec = deepCopy(obj.spec.jobTemplate.spec.template.spec)
job.spec = deepCopy(obj.spec.jobTemplate.spec)
local impactedResource = {}
impactedResource.operation = "create"

View File

@@ -13,6 +13,7 @@ spec:
annotations:
my: annotation
spec:
ttlSecondsAfterFinished: 100
template:
metadata:
labels:

View File

@@ -10,6 +10,7 @@
annotations:
my: annotation
spec:
ttlSecondsAfterFinished: 100
template:
metadata:
labels:

View File

@@ -25,7 +25,6 @@ import (
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
applisters "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1"
servercache "github.com/argoproj/argo-cd/v2/server/cache"
"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
"github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/argo-cd/v2/util/collections"
@@ -40,11 +39,9 @@ type Server struct {
ns string
db db.ArgoDB
enf *rbac.Enforcer
cache *servercache.Cache
appclientset appclientset.Interface
appLister applisters.ApplicationLister
appsetInformer cache.SharedIndexInformer
appsetLister applisters.ApplicationSetNamespaceLister
appsetLister applisters.ApplicationSetLister
projLister applisters.AppProjectNamespaceLister
auditLogger *argo.AuditLogger
settings *settings.SettingsManager
@@ -57,11 +54,9 @@ func NewServer(
db db.ArgoDB,
kubeclientset kubernetes.Interface,
enf *rbac.Enforcer,
cache *servercache.Cache,
appclientset appclientset.Interface,
appLister applisters.ApplicationLister,
appsetInformer cache.SharedIndexInformer,
appsetLister applisters.ApplicationSetNamespaceLister,
appsetLister applisters.ApplicationSetLister,
projLister applisters.AppProjectNamespaceLister,
settings *settings.SettingsManager,
namespace string,
@@ -70,11 +65,9 @@ func NewServer(
) applicationset.ApplicationSetServiceServer {
s := &Server{
ns: namespace,
cache: cache,
db: db,
enf: enf,
appclientset: appclientset,
appLister: appLister,
appsetInformer: appsetInformer,
appsetLister: appsetLister,
projLister: projLister,
@@ -94,7 +87,7 @@ func (s *Server) Get(ctx context.Context, q *applicationset.ApplicationSetGetQue
return nil, security.NamespaceNotPermittedError(namespace)
}
a, err := s.appclientset.ArgoprojV1alpha1().ApplicationSets(namespace).Get(ctx, q.Name, metav1.GetOptions{})
a, err := s.appsetLister.ApplicationSets(namespace).Get(q.Name)
if err != nil {
return nil, fmt.Errorf("error getting ApplicationSet: %w", err)
@@ -113,14 +106,19 @@ func (s *Server) List(ctx context.Context, q *applicationset.ApplicationSetListQ
return nil, fmt.Errorf("error parsing the selector: %w", err)
}
appIf := s.appclientset.ArgoprojV1alpha1().ApplicationSets(q.AppsetNamespace)
appsetList, err := appIf.List(ctx, metav1.ListOptions{LabelSelector: selector.String()})
var appsets []*v1alpha1.ApplicationSet
if q.AppsetNamespace == "" {
appsets, err = s.appsetLister.List(selector)
} else {
appsets, err = s.appsetLister.ApplicationSets(q.AppsetNamespace).List(selector)
}
if err != nil {
return nil, fmt.Errorf("error listing ApplicationSets with selectors: %w", err)
}
newItems := make([]v1alpha1.ApplicationSet, 0)
for _, a := range appsetList.Items {
for _, a := range appsets {
// Skip any application that is neither in the conrol plane's namespace
// nor in the list of enabled namespaces.
@@ -129,7 +127,7 @@ func (s *Server) List(ctx context.Context, q *applicationset.ApplicationSetListQ
}
if s.enf.Enforce(ctx.Value("claims"), rbacpolicy.ResourceApplicationSets, rbacpolicy.ActionGet, a.RBACName(s.ns)) {
newItems = append(newItems, a)
newItems = append(newItems, *a)
}
}
@@ -140,7 +138,7 @@ func (s *Server) List(ctx context.Context, q *applicationset.ApplicationSetListQ
return newItems[i].Name < newItems[j].Name
})
appsetList = &v1alpha1.ApplicationSetList{
appsetList := &v1alpha1.ApplicationSetList{
ListMeta: metav1.ListMeta{
ResourceVersion: s.appsetInformer.LastSyncResourceVersion(),
},
@@ -336,7 +334,7 @@ func (s *Server) waitSync(appset *v1alpha1.ApplicationSet) {
return
}
for {
if currAppset, err := s.appsetLister.Get(appset.Name); err == nil {
if currAppset, err := s.appsetLister.ApplicationSets(appset.Namespace).Get(appset.Name); err == nil {
currVersion, err := strconv.Atoi(currAppset.ResourceVersion)
if err == nil && currVersion >= minVersion {
return

View File

@@ -50,10 +50,21 @@ func newTestAppSetServer(objects ...runtime.Object) *Server {
_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
enf.SetDefaultRole("role:admin")
}
return newTestAppSetServerWithEnforcerConfigure(f, objects...)
scopedNamespaces := ""
return newTestAppSetServerWithEnforcerConfigure(f, scopedNamespaces, objects...)
}
func newTestAppSetServerWithEnforcerConfigure(f func(*rbac.Enforcer), objects ...runtime.Object) *Server {
// return an ApplicationServiceServer which returns fake data
func newTestNamespacedAppSetServer(objects ...runtime.Object) *Server {
f := func(enf *rbac.Enforcer) {
_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
enf.SetDefaultRole("role:admin")
}
scopedNamespaces := "argocd"
return newTestAppSetServerWithEnforcerConfigure(f, scopedNamespaces, objects...)
}
func newTestAppSetServerWithEnforcerConfigure(f func(*rbac.Enforcer), namespace string, objects ...runtime.Object) *Server {
kubeclientset := fake.NewSimpleClientset(&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNamespace,
@@ -97,7 +108,7 @@ func newTestAppSetServerWithEnforcerConfigure(f func(*rbac.Enforcer), objects ..
objects = append(objects, defaultProj, myProj)
fakeAppsClientset := apps.NewSimpleClientset(objects...)
factory := appinformer.NewSharedInformerFactoryWithOptions(fakeAppsClientset, 0, appinformer.WithNamespace(""), appinformer.WithTweakListOptions(func(options *metav1.ListOptions) {}))
factory := appinformer.NewSharedInformerFactoryWithOptions(fakeAppsClientset, 0, appinformer.WithNamespace(namespace), appinformer.WithTweakListOptions(func(options *metav1.ListOptions) {}))
fakeProjLister := factory.Argoproj().V1alpha1().AppProjects().Lister().AppProjects(testNamespace)
enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil)
@@ -114,6 +125,12 @@ func newTestAppSetServerWithEnforcerConfigure(f func(*rbac.Enforcer), objects ..
if !k8scache.WaitForCacheSync(ctx.Done(), appInformer.HasSynced) {
panic("Timed out waiting for caches to sync")
}
// populate the appset informer with the fake objects
appsetInformer := factory.Argoproj().V1alpha1().ApplicationSets().Informer()
go appsetInformer.Run(ctx.Done())
if !k8scache.WaitForCacheSync(ctx.Done(), appsetInformer.HasSynced) {
panic("Timed out waiting for caches to sync")
}
projInformer := factory.Argoproj().V1alpha1().AppProjects().Informer()
go projInformer.Run(ctx.Done())
@@ -125,11 +142,9 @@ func newTestAppSetServerWithEnforcerConfigure(f func(*rbac.Enforcer), objects ..
db,
kubeclientset,
enforcer,
nil,
fakeAppsClientset,
factory.Argoproj().V1alpha1().Applications().Lister(),
appInformer,
factory.Argoproj().V1alpha1().ApplicationSets().Lister().ApplicationSets(testNamespace),
factory.Argoproj().V1alpha1().ApplicationSets().Lister(),
fakeProjLister,
settingsMgr,
testNamespace,
@@ -223,21 +238,22 @@ func testListAppsetsWithLabels(t *testing.T, appsetQuery applicationset.Applicat
}
func TestListAppSetsInNamespaceWithLabels(t *testing.T) {
testNamespace := "test-namespace"
appSetServer := newTestAppSetServer(newTestAppSet(func(appset *appsv1.ApplicationSet) {
appset.Name = "AppSet1"
appset.ObjectMeta.Namespace = "test-namespace"
appset.ObjectMeta.Namespace = testNamespace
appset.SetLabels(map[string]string{"key1": "value1", "key2": "value1"})
}), newTestAppSet(func(appset *appsv1.ApplicationSet) {
appset.Name = "AppSet2"
appset.ObjectMeta.Namespace = "test-namespace"
appset.ObjectMeta.Namespace = testNamespace
appset.SetLabels(map[string]string{"key1": "value2"})
}), newTestAppSet(func(appset *appsv1.ApplicationSet) {
appset.Name = "AppSet3"
appset.ObjectMeta.Namespace = "test-namespace"
appset.ObjectMeta.Namespace = testNamespace
appset.SetLabels(map[string]string{"key1": "value3"})
}))
appSetServer.ns = "test-namespace"
appsetQuery := applicationset.ApplicationSetListQuery{AppsetNamespace: "test-namespace"}
appSetServer.enabledNamespaces = []string{testNamespace}
appsetQuery := applicationset.ApplicationSetListQuery{AppsetNamespace: testNamespace}
testListAppsetsWithLabels(t, appsetQuery, appSetServer)
}
@@ -258,6 +274,32 @@ func TestListAppSetsInDefaultNSWithLabels(t *testing.T) {
testListAppsetsWithLabels(t, appsetQuery, appSetServer)
}
// This test covers https://github.com/argoproj/argo-cd/issues/15429
// If the namespace isn't provided during listing action, argocd's
// default namespace must be used and not all the namespaces
func TestListAppSetsWithoutNamespace(t *testing.T) {
testNamespace := "test-namespace"
appSetServer := newTestNamespacedAppSetServer(newTestAppSet(func(appset *appsv1.ApplicationSet) {
appset.Name = "AppSet1"
appset.ObjectMeta.Namespace = testNamespace
appset.SetLabels(map[string]string{"key1": "value1", "key2": "value1"})
}), newTestAppSet(func(appset *appsv1.ApplicationSet) {
appset.Name = "AppSet2"
appset.ObjectMeta.Namespace = testNamespace
appset.SetLabels(map[string]string{"key1": "value2"})
}), newTestAppSet(func(appset *appsv1.ApplicationSet) {
appset.Name = "AppSet3"
appset.ObjectMeta.Namespace = testNamespace
appset.SetLabels(map[string]string{"key1": "value3"})
}))
appSetServer.enabledNamespaces = []string{testNamespace}
appsetQuery := applicationset.ApplicationSetListQuery{}
res, err := appSetServer.List(context.Background(), &appsetQuery)
assert.NoError(t, err)
assert.Equal(t, 0, len(res.Items))
}
func TestCreateAppSet(t *testing.T) {
testAppSet := newTestAppSet()
appServer := newTestAppSetServer()

View File

@@ -178,7 +178,7 @@ type ArgoCDServer struct {
appInformer cache.SharedIndexInformer
appLister applisters.ApplicationLister
appsetInformer cache.SharedIndexInformer
appsetLister applisters.ApplicationSetNamespaceLister
appsetLister applisters.ApplicationSetLister
db db.ArgoDB
// stopCh is the channel which when closed, will shutdown the Argo CD server
@@ -264,7 +264,7 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts) *ArgoCDServer {
appLister := appFactory.Argoproj().V1alpha1().Applications().Lister()
appsetInformer := appFactory.Argoproj().V1alpha1().ApplicationSets().Informer()
appsetLister := appFactory.Argoproj().V1alpha1().ApplicationSets().Lister().ApplicationSets(opts.Namespace)
appsetLister := appFactory.Argoproj().V1alpha1().ApplicationSets().Lister()
userStateStorage := util_session.NewUserStateStorage(opts.RedisClient)
sessionMgr := util_session.NewSessionManager(settingsMgr, projLister, opts.DexServerAddr, opts.DexTLSConfig, userStateStorage)
@@ -471,6 +471,7 @@ func (a *ArgoCDServer) Listen() (*Listeners, error) {
func (a *ArgoCDServer) Init(ctx context.Context) {
go a.projInformer.Run(ctx.Done())
go a.appInformer.Run(ctx.Done())
go a.appsetInformer.Run(ctx.Done())
go a.configMapInformer.Run(ctx.Done())
go a.secretInformer.Run(ctx.Done())
}
@@ -852,9 +853,7 @@ func newArgoCDServiceSet(a *ArgoCDServer) *ArgoCDServiceSet {
a.db,
a.KubeClientset,
a.enf,
a.Cache,
a.AppClientset,
a.appLister,
a.appsetInformer,
a.appsetLister,
a.projLister,

View File

@@ -8,7 +8,7 @@ RUN ln -s /usr/lib/$(uname -m)-linux-gnu /usr/lib/linux-gnu
# Please make sure to also check the contained yarn version and update the references below when upgrading this image's version
FROM docker.io/library/node:20.7.0@sha256:f08c20b9f9c55dd47b1841793f0ee480c5395aa165cd02edfd68b068ed64bfb5 as node
FROM docker.io/library/golang:1.21.1@sha256:2270a408c4cb38f8459839082d89afa4a2870773c509adf7641e9558167d0030 as golang
FROM docker.io/library/golang:1.21.3@sha256:02d7116222536a5cf0fcf631f90b507758b669648e0f20186d2dc94a9b419a9b as golang
FROM docker.io/library/registry:2.8@sha256:41f413c22d6156587e2a51f3e80c09808b8c70e82be149b82b5e0196a88d49b4 as registry

View File

@@ -39,12 +39,7 @@ func TestCustomToolWithGitCreds(t *testing.T) {
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy)).
And(func(app *Application) {
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", ctx.AppName(), "-o", "jsonpath={.metadata.annotations.GitAskpass}")
assert.NoError(t, err)
assert.Equal(t, "argocd", output)
})
Expect(HealthIs(health.HealthStatusHealthy))
}
// make sure we can echo back the Git creds
@@ -70,11 +65,6 @@ func TestCustomToolWithGitCredsTemplate(t *testing.T) {
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy)).
And(func(app *Application) {
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", ctx.AppName(), "-o", "jsonpath={.metadata.annotations.GitAskpass}")
assert.NoError(t, err)
assert.Equal(t, "argocd", output)
}).
And(func(app *Application) {
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", ctx.AppName(), "-o", "jsonpath={.metadata.annotations.GitUsername}")
assert.NoError(t, err)

View File

@@ -217,7 +217,9 @@ export class App extends React.Component<
</Helmet>
<PageContext.Provider value={{title: 'Argo CD'}}>
<Provider value={{history, popup: this.popupManager, notifications: this.notificationsManager, navigation: this.navigationManager, baseHref: base}}>
{this.state.popupProps && <Popup {...this.state.popupProps} />}
<DataLoader load={() => services.viewPreferences.getPreferences()}>
{pref => <div className={pref.theme ? 'theme-' + pref.theme : 'theme-light'}>{this.state.popupProps && <Popup {...this.state.popupProps} />}</div>}
</DataLoader>
<AuthSettingsCtx.Provider value={this.state.authSettings}>
<Router history={history}>
<Switch>

View File

@@ -1,5 +1,6 @@
@import 'node_modules/argo-ui/src/styles/config';
@import 'node_modules/foundation-sites/scss/util/util';
@import 'node_modules/argo-ui/src/styles/theme';
@import '../../../shared/config.scss';
$header: 120px;
@@ -154,7 +155,7 @@ $header: 120px;
}
}
@media screen and (max-width: map-get($breakpoints, xlarge)) {
@media screen and (max-width: map-get($breakpoints, xxlarge)) {
.page__content-wrapper {
min-height: calc(100vh - 3 * 50px);
}
@@ -211,9 +212,14 @@ $header: 120px;
z-index: 1;
padding: 5px;
display: inline-block;
background-color: $argo-color-gray-1;
box-shadow: 1px 1px 3px $argo-color-gray-5;
position: absolute;
@include themify($themes) {
background: themed('background-2');
}
a {
padding: 5px;
margin: 2px;
@@ -255,7 +261,9 @@ $header: 120px;
}
.separator {
border-right: 1px solid $argo-color-gray-4;
@include themify($themes) {
border-right: 1px solid themed('border');
}
padding-top: 6px;
padding-bottom: 6px;
}

View File

@@ -459,7 +459,8 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{app
<Tooltip
content={AppUtils.userMsgsList[showToolTip?.msgKey] || 'Group Nodes'}
visible={pref.groupNodes && showToolTip !== undefined && !showToolTip?.display}
duration={showToolTip?.duration}>
duration={showToolTip?.duration}
zIndex={1}>
<a
className={`group-nodes-button group-nodes-button${!pref.groupNodes ? '' : '-on'}`}
title={pref.view === 'tree' ? 'Group Nodes' : 'Collapse Pods'}

View File

@@ -85,7 +85,10 @@ export const ApplicationResourceList = ({
<Consumer>
{ctx => (
<span className='application-details__external_link'>
<a href={ctx.baseHref + 'applications/' + res.namespace + '/' + res.name} title='Open application'>
<a
href={ctx.baseHref + 'applications/' + res.namespace + '/' + res.name}
onClick={e => e.stopPropagation()}
title='Open application'>
<i className='fa fa-external-link-alt' />
</a>
</span>

View File

@@ -79,6 +79,10 @@
border: 1px solid transparent;
cursor: pointer;
.theme-dark & {
box-shadow: 1px 1px 1px $argo-color-gray-7;
}
.icon {
font-size: 2em;
}
@@ -120,6 +124,10 @@
}
margin-top: 9px;
margin-left: 215px;
.theme-dark & {
box-shadow: 1px 1px 1px $argo-color-gray-7;
}
}
&--podgroup--expansion {
@@ -131,6 +139,10 @@
box-shadow: 1px 1px 1px $argo-color-gray-4;
background-color: white;
margin-left: 215px;
.theme-dark & {
box-shadow: 1px 1px 1px $argo-color-gray-7;
}
}
&--pod {
@@ -348,8 +360,12 @@
border-radius: 33px;
left: -20px;
top: -8px;
border: 4px solid white;
text-align: center;
@include themify($themes) {
border: 4px solid themed('background-2');
}
i {
color: $white-color;
line-height: 56px;

View File

@@ -101,7 +101,9 @@
}
&:not(:first-child) {
border-left: 1px solid $argo-color-gray-3;
@include themify($themes) {
border-left: 1px solid themed('border');
}
}
& {

View File

@@ -89,20 +89,18 @@ export const ApplicationStatusPanel = ({application, showDiff, showOperation, sh
hasMultipleSources,
() => showMetadataInfo(application.status.sync ? application.status.sync.revision : '')
)}
{appOperationState && (
<div className={`application-status-panel__item-value application-status-panel__item-value--${appOperationState.phase}`}>
<div>
{application.status.sync.status === models.SyncStatuses.OutOfSync ? (
<a onClick={() => showDiff && showDiff()}>
<ComparisonStatusIcon status={application.status.sync.status} label={true} />
</a>
) : (
<div className={`application-status-panel__item-value${appOperationState?.phase ? ` application-status-panel__item-value--${appOperationState.phase}` : ''}`}>
<div>
{application.status.sync.status === models.SyncStatuses.OutOfSync ? (
<a onClick={() => showDiff && showDiff()}>
<ComparisonStatusIcon status={application.status.sync.status} label={true} />
)}
</div>
<div className='application-status-panel__item-value__revision show-for-large'>{syncStatusMessage(application)}</div>
</a>
) : (
<ComparisonStatusIcon status={application.status.sync.status} label={true} />
)}
</div>
)}
<div className='application-status-panel__item-value__revision show-for-large'>{syncStatusMessage(application)}</div>
</div>
<div className='application-status-panel__item-name' style={{marginBottom: '0.5em'}}>
{application.spec.syncPolicy?.automated ? 'Auto sync is enabled.' : 'Auto sync is not enabled.'}
</div>

View File

@@ -118,9 +118,9 @@
}
&__search {
border: 1px solid $argo-color-gray-4;
@include themify($themes) {
background-color: themed('light-argo-gray-2');
border: 1px solid themed('border');
}
border-radius: 7px;
position: relative;

View File

@@ -1,7 +1,7 @@
import {DataLoader} from 'argo-ui';
import * as classNames from 'classnames';
import * as React from 'react';
import {useEffect, useState} from 'react';
import {useEffect, useState, useRef} from 'react';
import {bufferTime, delay, retryWhen} from 'rxjs/operators';
import {LogEntry} from '../../../shared/models';
@@ -83,6 +83,7 @@ export const PodsLogsViewer = (props: PodLogsProps) => {
const [highlight, setHighlight] = useState<RegExp>(matchNothing);
const [scrollToBottom, setScrollToBottom] = useState(true);
const [logs, setLogs] = useState<LogEntry[]>([]);
const logsContainerRef = useRef(null);
useEffect(() => {
if (viewPodNames) {
@@ -102,6 +103,15 @@ export const PodsLogsViewer = (props: PodLogsProps) => {
useEffect(() => setScrollToBottom(true), [follow]);
useEffect(() => {
if (scrollToBottom) {
const element = logsContainerRef.current;
if (element) {
element.scrollTop = element.scrollHeight;
}
}
}, [logs, scrollToBottom]);
useEffect(() => {
setLogs([]);
const logsSource = services.applications
@@ -125,6 +135,10 @@ export const PodsLogsViewer = (props: PodLogsProps) => {
return () => logsSource.unsubscribe();
}, [applicationName, applicationNamespace, namespace, podName, group, kind, name, containerName, tail, follow, sinceSeconds, filter, previous]);
const handleScroll = (event: React.WheelEvent<HTMLDivElement>) => {
if (event.deltaY < 0) setScrollToBottom(false);
};
const renderLog = (log: LogEntry, lineNum: number) =>
// show the pod name if there are multiple pods, pad with spaces to align
(viewPodNames ? (lineNum === 0 || logs[lineNum - 1].podName !== log.podName ? podColor(podName) + log.podName + reset : ' '.repeat(log.podName.length)) + ' ' : '') +
@@ -133,7 +147,7 @@ export const PodsLogsViewer = (props: PodLogsProps) => {
// show the log content, highlight the filter text
log.content?.replace(highlight, (substring: string) => whiteOnYellow + substring + reset);
const logsContent = (width: number, height: number, isWrapped: boolean) => (
<div style={{width, height, overflow: 'scroll'}}>
<div ref={logsContainerRef} onScroll={handleScroll} style={{width, height, overflow: 'scroll'}}>
{logs.map((log, lineNum) => (
<pre key={lineNum} style={{whiteSpace: isWrapped ? 'normal' : 'pre'}} className='noscroll'>
<Ansi>{renderLog(log, lineNum)}</Ansi>
@@ -177,11 +191,7 @@ export const PodsLogsViewer = (props: PodLogsProps) => {
<FullscreenButton {...props} />
</span>
</div>
<div
className={classNames('pod-logs-viewer', {'pod-logs-viewer--inverted': prefs.appDetails.darkMode})}
onWheel={e => {
if (e.deltaY < 0) setScrollToBottom(false);
}}>
<div className={classNames('pod-logs-viewer', {'pod-logs-viewer--inverted': prefs.appDetails.darkMode})} onWheel={handleScroll}>
<AutoSizer>{({width, height}: {width: number; height: number}) => logsContent(width, height, prefs.appDetails.wrapLines)}</AutoSizer>
</div>
</React.Fragment>

View File

@@ -1,3 +1,5 @@
@import 'node_modules/argo-ui/src/styles/theme';
.propagation-policy-list {
display: flex;
justify-content: left;
@@ -9,9 +11,12 @@
padding-right: 2em;
label {
color: #6D7F8B;
font-size: 15px;
cursor: pointer;
@include themify($themes) {
color: themed('light-argo-gray-6');
}
}
input {

View File

@@ -15,7 +15,7 @@ $deselected-text: #818d94;
color: white;
background-color: #0f2733;
overflow: auto;
z-index: 0;
z-index: 2;
&__container {
padding: 0 5px;

View File

@@ -35,6 +35,10 @@ const (
errDestinationMissing = "Destination server missing from app spec"
)
var (
ErrAnotherOperationInProgress = status.Errorf(codes.FailedPrecondition, "another operation is already in progress")
)
// AugmentSyncMsg enrich the K8s message with user-relevant information
func AugmentSyncMsg(res common.ResourceSyncResult, apiResourceInfoGetter func() ([]kube.APIResourceInfo, error)) (string, error) {
switch res.Message {
@@ -800,7 +804,7 @@ func SetAppOperation(appIf v1alpha1.ApplicationInterface, appName string, op *ar
return nil, fmt.Errorf("error getting application %q: %w", appName, err)
}
if a.Operation != nil {
return nil, status.Errorf(codes.FailedPrecondition, "another operation is already in progress")
return nil, ErrAnotherOperationInProgress
}
a.Operation = op
a.Status.OperationState = nil
@@ -851,7 +855,9 @@ func NormalizeApplicationSpec(spec *argoappv1.ApplicationSpec) *argoappv1.Applic
if spec.Project == "" {
spec.Project = argoappv1.DefaultAppProjectName
}
if spec.SyncPolicy.IsZero() {
spec.SyncPolicy = nil
}
if spec.Sources != nil && len(spec.Sources) > 0 {
for _, source := range spec.Sources {
NormalizeSource(&source)

View File

@@ -6,6 +6,7 @@ import (
"strings"
v1 "k8s.io/api/core/v1"
kubeerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
@@ -13,6 +14,7 @@ import (
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/env"
"github.com/argoproj/argo-cd/v2/util/settings"
log "github.com/sirupsen/logrus"
)
// SecretMaperValidation determine whether the secret should be transformed(i.e. trailing CRLF characters trimmed)
@@ -152,8 +154,14 @@ func StripCRLFCharacter(input string) string {
func (db *db) GetApplicationControllerReplicas() int {
// get the replicas from application controller deployment, if the application controller deployment does not exist, check for environment variable
applicationControllerName := env.StringFromEnv(common.EnvAppControllerName, common.DefaultApplicationControllerName)
appControllerDeployment, _ := db.kubeclientset.AppsV1().Deployments(db.settingsMgr.GetNamespace()).Get(context.Background(), applicationControllerName, metav1.GetOptions{})
if appControllerDeployment != nil {
appControllerDeployment, err := db.kubeclientset.AppsV1().Deployments(db.settingsMgr.GetNamespace()).Get(context.Background(), applicationControllerName, metav1.GetOptions{})
if err != nil {
appControllerDeployment = nil
if !kubeerrors.IsNotFound(err) {
log.Warnf("error retrieveing Argo CD controller deployment: %s", err)
}
}
if appControllerDeployment != nil && appControllerDeployment.Spec.Replicas != nil {
return int(*appControllerDeployment.Spec.Replicas)
}
return env.ParseNumFromEnv(common.EnvControllerReplicas, 0, 0, math.MaxInt32)

View File

@@ -10,6 +10,7 @@ import (
"github.com/argoproj/argo-cd/v2/common"
"github.com/sirupsen/logrus"
"golang.org/x/net/proxy"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
@@ -63,7 +64,7 @@ func BlockingDial(ctx context.Context, network, address string, creds credential
dialer := func(ctx context.Context, address string) (net.Conn, error) {
conn, err := (&net.Dialer{Cancel: ctx.Done()}).Dial(network, address)
conn, err := proxy.Dial(ctx, network, address)
if err != nil {
writeResult(err)
return nil, err

View File

@@ -274,6 +274,10 @@ var (
)
func cleanSetParameters(val string) string {
// `{}` equal helm list parameters format, so don't escape `,`.
if strings.HasPrefix(val, `{`) && strings.HasSuffix(val, `}`) {
return val
}
return re.ReplaceAllString(val, `$1\,`)
}

View File

@@ -165,6 +165,7 @@ func TestHelmArgCleaner(t *testing.T) {
`bar`: `bar`,
`not, clean`: `not\, clean`,
`a\,b,c`: `a\,b\,c`,
`{a,b,c}`: `{a,b,c}`,
} {
cleaned := cleanSetParameters(input)
assert.Equal(t, expected, cleaned)

View File

@@ -19,7 +19,7 @@ const maxCookieLength = 4093
// max number of chunks a cookie can be broken into. To be compatible with
// widest range of browsers, you shouldn't create more than 30 cookies per domain
var maxCookieNumber = env.ParseNumFromEnv(common.EnvMaxCookieNumber, 20, 0, math.MaxInt64)
var maxCookieNumber = env.ParseNumFromEnv(common.EnvMaxCookieNumber, 20, 0, math.MaxInt)
// MakeCookieMetadata generates a string representing a Web cookie. Yum!
func MakeCookieMetadata(key, value string, flags ...string) ([]string, error) {