Compare commits

...

30 Commits

Author SHA1 Message Date
github-actions[bot]
37b1cf5306 Bump version to 2.10.8 (#17990)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: pasha-codefresh <39732895+pasha-codefresh@users.noreply.github.com>
2024-04-26 16:17:41 +03:00
pasha-codefresh
15865b9a04 fix: codegen after security fix - 2.10 (#17985)
* fix: codegen after security fix

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

* fix: codegen after security fix

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

---------

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

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

* feat: ignore normalizer jq execution timeout as env variable

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

* feat: customize error message and add doc section

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

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

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

* chore: fix import`s order

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

* chore: rename variable inside sts

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

* chore: fix import order

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

* chore: fix import`s order

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

---------

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

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

* feat: ignore normalizer jq execution timeout as env variable

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

* feat: customize error message and add doc section

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

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

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

* chore: fix import`s order

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

* chore: rename variable inside sts

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

* chore: fix import order

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

* chore: fix import`s order

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

---------

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

CVE-2023-41056

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

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

* fix: codegen and e2e tests

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

---------

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

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

* test: simplify unit test

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

* fix: fix calculating patch for respect ignore diff feature

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

---------

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



* Add rebuy to users.md



---------

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

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

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

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

* sec: limit helm index max size

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

* feat: fix tests and linter

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

---------

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

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

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

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

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

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

* chore: fix linter issues

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

---------

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

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

* feat: increase default max cache size

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

* add nil protection

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

* Update util/session/sessionmanager.go

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

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

* chore: fix linter issues

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

---------

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



* fix linters



---------

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



* chore(deps): upgrade helm to 3.14.3



---------

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

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

* fix(ui): remove invalid external-link

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

* linting

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

---------

Signed-off-by: Ry0taK <49341894+Ry0taK@users.noreply.github.com>
Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
Co-authored-by: Ry0taK <49341894+Ry0taK@users.noreply.github.com>
2024-03-13 14:26:47 -04:00
gcp-cherry-pick-bot[bot]
37da5e2ae5 fix: multi-source app breaks application parameters UI (#16910) (#17033) (#17332)
Signed-off-by: Keith Chong <kykchong@redhat.com>
Co-authored-by: Keith Chong <kykchong@redhat.com>
2024-03-04 18:22:56 -05:00
gcp-cherry-pick-bot[bot]
12886657ac fix!: disable rate limiting completely by default (#17355) (#17393)
Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>
Co-authored-by: Soumya Ghosh Dastidar <44349253+gdsoumya@users.noreply.github.com>
2024-03-04 11:51:15 -05:00
103 changed files with 2605 additions and 1385 deletions

View File

@@ -429,7 +429,7 @@ jobs:
run: |
docker pull ghcr.io/dexidp/dex:v2.37.0
docker pull argoproj/argo-cd-ci-builder:v1.0.0
docker pull redis:7.0.14-alpine
docker pull redis:7.0.15-alpine
- name: Create target directory for binaries in the build-process
run: |
mkdir -p dist

View File

@@ -74,9 +74,7 @@ jobs:
go-version: ${{ inputs.go-version }}
- name: Install cosign
uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 # v3.2.0
with:
cosign-release: 'v2.2.1'
uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 # v3.4.0
- uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
- uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0

View File

@@ -86,7 +86,7 @@ jobs:
packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.7.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.10.0
with:
image: ghcr.io/argoproj/argo-cd/argocd
digest: ${{ needs.build-and-publish.outputs.image-digest }}

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.9.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.10.0
with:
image: quay.io/argoproj/argocd
digest: ${{ needs.argocd-image.outputs.image-digest }}
@@ -128,7 +128,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.9.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0
with:
base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
provenance-name: "argocd-cli.intoto.jsonl"
@@ -212,7 +212,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.9.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0
with:
base64-subjects: "${{ needs.generate-sbom.outputs.hashes }}"
provenance-name: "argocd-sbom.intoto.jsonl"

View File

@@ -6,7 +6,7 @@ ARG BASE_IMAGE=docker.io/library/ubuntu:22.04@sha256:0bced47fffa3361afa981854fca
####################################################################################################
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
RUN echo 'deb http://archive.debian.org/debian buster-backports main' >> /etc/apt/sources.list
RUN apt-get update && apt-get install --no-install-recommends -y \
openssh-server \

View File

@@ -234,6 +234,7 @@ Currently, the following organizations are **officially** using Argo CD:
1. [QuintoAndar](https://quintoandar.com.br)
1. [Quipper](https://www.quipper.com/)
1. [RapidAPI](https://www.rapidapi.com/)
1. [rebuy](https://www.rebuy.de/)
1. [Recreation.gov](https://www.recreation.gov/)
1. [Red Hat](https://www.redhat.com/)
1. [Redpill Linpro](https://www.redpill-linpro.com/)

View File

@@ -1 +1 @@
2.10.2
2.10.8

View File

@@ -50,6 +50,7 @@ import (
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
argoutil "github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
"github.com/argoproj/argo-cd/v2/pkg/apis/application"
)
@@ -666,7 +667,7 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
},
}
action, err := utils.CreateOrUpdate(ctx, appLog, r.Client, applicationSet.Spec.IgnoreApplicationDifferences, found, func() error {
action, err := utils.CreateOrUpdate(ctx, appLog, r.Client, applicationSet.Spec.IgnoreApplicationDifferences, normalizers.IgnoreNormalizerOpts{}, found, func() error {
// Copy only the Application/ObjectMeta fields that are significant, from the generatedApp
found.Spec = generatedApp.Spec

View File

@@ -20,6 +20,7 @@ import (
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"
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
)
// CreateOrUpdate overrides "sigs.k8s.io/controller-runtime" function
@@ -35,7 +36,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, logCtx *log.Entry, c client.Client, ignoreAppDifferences argov1alpha1.ApplicationSetIgnoreDifferences, obj *argov1alpha1.Application, f controllerutil.MutateFn) (controllerutil.OperationResult, error) {
func CreateOrUpdate(ctx context.Context, logCtx *log.Entry, c client.Client, ignoreAppDifferences argov1alpha1.ApplicationSetIgnoreDifferences, ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts, obj *argov1alpha1.Application, f controllerutil.MutateFn) (controllerutil.OperationResult, error) {
key := client.ObjectKeyFromObject(obj)
if err := c.Get(ctx, key, obj); err != nil {
@@ -60,7 +61,7 @@ func CreateOrUpdate(ctx context.Context, logCtx *log.Entry, c client.Client, ign
// 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)
err := applyIgnoreDifferences(ignoreAppDifferences, normalizedLive, obj, ignoreNormalizerOpts)
if err != nil {
return controllerutil.OperationResultNone, fmt.Errorf("failed to apply ignore differences: %w", err)
}
@@ -134,14 +135,14 @@ func mutate(f controllerutil.MutateFn, key client.ObjectKey, obj client.Object)
}
// 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 {
func applyIgnoreDifferences(applicationSetIgnoreDifferences argov1alpha1.ApplicationSetIgnoreDifferences, found *argov1alpha1.Application, generatedApp *argov1alpha1.Application, ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts) error {
if len(applicationSetIgnoreDifferences) == 0 {
return nil
}
generatedAppCopy := generatedApp.DeepCopy()
diffConfig, err := argodiff.NewDiffConfigBuilder().
WithDiffSettings(applicationSetIgnoreDifferences.ToApplicationIgnoreDifferences(), nil, false).
WithDiffSettings(applicationSetIgnoreDifferences.ToApplicationIgnoreDifferences(), nil, false, ignoreNormalizerOpts).
WithNoCache().
Build()
if err != nil {

View File

@@ -9,6 +9,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
)
func Test_applyIgnoreDifferences(t *testing.T) {
@@ -222,7 +223,7 @@ spec:
generatedApp := v1alpha1.Application{TypeMeta: appMeta}
err = yaml.Unmarshal([]byte(tc.generatedApp), &generatedApp)
require.NoError(t, err, tc.generatedApp)
err = applyIgnoreDifferences(tc.ignoreDifferences, &foundApp, &generatedApp)
err = applyIgnoreDifferences(tc.ignoreDifferences, &foundApp, &generatedApp, normalizers.IgnoreNormalizerOpts{})
require.NoError(t, err)
yamlFound, err := yaml.Marshal(tc.foundApp)
require.NoError(t, err)

View File

@@ -7405,6 +7405,7 @@
"properties": {
"elements": {
"type": "array",
"title": "+kubebuilder:validation:Optional",
"items": {
"$ref": "#/definitions/v1JSON"
}

View File

@@ -6,7 +6,6 @@ import (
"math"
"time"
"github.com/argoproj/argo-cd/v2/pkg/ratelimiter"
"github.com/argoproj/pkg/stats"
"github.com/redis/go-redis/v9"
log "github.com/sirupsen/logrus"
@@ -20,7 +19,9 @@ import (
"github.com/argoproj/argo-cd/v2/controller/sharding"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
"github.com/argoproj/argo-cd/v2/pkg/ratelimiter"
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
"github.com/argoproj/argo-cd/v2/util/cli"
@@ -72,6 +73,7 @@ func NewCommand() *cobra.Command {
shardingAlgorithm string
enableDynamicClusterDistribution bool
serverSideDiff bool
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
)
var command = cobra.Command{
Use: cliName,
@@ -169,6 +171,7 @@ func NewCommand() *cobra.Command {
&workqueueRateLimit,
serverSideDiff,
enableDynamicClusterDistribution,
ignoreNormalizerOpts,
)
errors.CheckError(err)
cacheutil.CollectMetrics(redisClient, appController.GetMetricsServer())
@@ -220,7 +223,7 @@ func NewCommand() *cobra.Command {
command.Flags().StringVar(&shardingAlgorithm, "sharding-method", env.StringFromEnv(common.EnvControllerShardingAlgorithm, common.DefaultShardingAlgorithm), "Enables choice of sharding method. Supported sharding methods are : [legacy, round-robin] ")
// global queue rate limit config
command.Flags().Int64Var(&workqueueRateLimit.BucketSize, "wq-bucket-size", env.ParseInt64FromEnv("WORKQUEUE_BUCKET_SIZE", 500, 1, math.MaxInt64), "Set Workqueue Rate Limiter Bucket Size, default 500")
command.Flags().Int64Var(&workqueueRateLimit.BucketQPS, "wq-bucket-qps", env.ParseInt64FromEnv("WORKQUEUE_BUCKET_QPS", 50, 1, math.MaxInt64), "Set Workqueue Rate Limiter Bucket QPS, default 50")
command.Flags().Float64Var(&workqueueRateLimit.BucketQPS, "wq-bucket-qps", env.ParseFloat64FromEnv("WORKQUEUE_BUCKET_QPS", math.MaxFloat64, 1, math.MaxFloat64), "Set Workqueue Rate Limiter Bucket QPS, default set to MaxFloat64 which disables the bucket limiter")
// individual item rate limit config
// when WORKQUEUE_FAILURE_COOLDOWN is 0 per item rate limiting is disabled(default)
command.Flags().DurationVar(&workqueueRateLimit.FailureCoolDown, "wq-cooldown-ns", time.Duration(env.ParseInt64FromEnv("WORKQUEUE_FAILURE_COOLDOWN_NS", 0, 0, (24*time.Hour).Nanoseconds())), "Set Workqueue Per Item Rate Limiter Cooldown duration in ns, default 0(per item rate limiter disabled)")
@@ -229,6 +232,7 @@ func NewCommand() *cobra.Command {
command.Flags().Float64Var(&workqueueRateLimit.BackoffFactor, "wq-backoff-factor", env.ParseFloat64FromEnv("WORKQUEUE_BACKOFF_FACTOR", 1.5, 0, math.MaxFloat64), "Set Workqueue Per Item Rate Limiter Backoff Factor, default is 1.5")
command.Flags().BoolVar(&enableDynamicClusterDistribution, "dynamic-cluster-distribution-enabled", env.ParseBoolFromEnv(common.EnvEnableDynamicClusterDistribution, false), "Enables dynamic cluster distribution.")
command.Flags().BoolVar(&serverSideDiff, "server-side-diff-enabled", env.ParseBoolFromEnv(common.EnvServerSideDiff, false), "Feature flag to enable ServerSide diff. Default (\"false\")")
command.Flags().DurationVar(&ignoreNormalizerOpts.JQExecutionTimeout, "ignore-normalizer-jq-execution-timeout-seconds", env.ParseDurationFromEnv("ARGOCD_IGNORE_NORMALIZER_JQ_TIMEOUT", 0*time.Second, 0, math.MaxInt64), "Set ignore normalizer JQ execution timeout")
cacheSource = appstatecache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {
redisClient = client
})

View File

@@ -68,6 +68,7 @@ func NewCommand() *cobra.Command {
streamedManifestMaxTarSize string
streamedManifestMaxExtractedSize string
helmManifestMaxExtractedSize string
helmRegistryMaxIndexSize string
disableManifestMaxExtractedSize bool
)
var command = cobra.Command{
@@ -110,6 +111,9 @@ func NewCommand() *cobra.Command {
helmManifestMaxExtractedSizeQuantity, err := resource.ParseQuantity(helmManifestMaxExtractedSize)
errors.CheckError(err)
helmRegistryMaxIndexSizeQuantity, err := resource.ParseQuantity(helmRegistryMaxIndexSize)
errors.CheckError(err)
askPassServer := askpass.NewServer()
metricsServer := metrics.NewMetricsServer()
cacheutil.CollectMetrics(redisClient, metricsServer)
@@ -125,6 +129,7 @@ func NewCommand() *cobra.Command {
StreamedManifestMaxExtractedSize: streamedManifestMaxExtractedSizeQuantity.ToDec().Value(),
StreamedManifestMaxTarSize: streamedManifestMaxTarSizeQuantity.ToDec().Value(),
HelmManifestMaxExtractedSize: helmManifestMaxExtractedSizeQuantity.ToDec().Value(),
HelmRegistryMaxIndexSize: helmRegistryMaxIndexSizeQuantity.ToDec().Value(),
}, askPassServer)
errors.CheckError(err)
@@ -208,6 +213,7 @@ func NewCommand() *cobra.Command {
command.Flags().StringVar(&streamedManifestMaxTarSize, "streamed-manifest-max-tar-size", env.StringFromEnv("ARGOCD_REPO_SERVER_STREAMED_MANIFEST_MAX_TAR_SIZE", "100M"), "Maximum size of streamed manifest archives")
command.Flags().StringVar(&streamedManifestMaxExtractedSize, "streamed-manifest-max-extracted-size", env.StringFromEnv("ARGOCD_REPO_SERVER_STREAMED_MANIFEST_MAX_EXTRACTED_SIZE", "1G"), "Maximum size of streamed manifest archives when extracted")
command.Flags().StringVar(&helmManifestMaxExtractedSize, "helm-manifest-max-extracted-size", env.StringFromEnv("ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE", "1G"), "Maximum size of helm manifest archives when extracted")
command.Flags().StringVar(&helmRegistryMaxIndexSize, "helm-registry-max-index-size", env.StringFromEnv("ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_INDEX_SIZE", "1G"), "Maximum size of registry index file")
command.Flags().BoolVar(&disableManifestMaxExtractedSize, "disable-helm-manifest-max-extracted-size", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE", false), "Disable maximum size of helm manifest archives when extracted")
tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(&command)
cacheSrc = reposervercache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {

View File

@@ -30,6 +30,7 @@ import (
appinformers "github.com/argoproj/argo-cd/v2/pkg/client/informers/externalversions"
reposerverclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient"
"github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
"github.com/argoproj/argo-cd/v2/util/cli"
@@ -238,12 +239,13 @@ func diffReconcileResults(res1 reconcileResults, res2 reconcileResults) error {
func NewReconcileCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
selector string
repoServerAddress string
outputFormat string
refresh bool
serverSideDiff bool
clientConfig clientcmd.ClientConfig
selector string
repoServerAddress string
outputFormat string
refresh bool
serverSideDiff bool
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
)
var command = &cobra.Command{
@@ -281,7 +283,7 @@ func NewReconcileCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
appClientset := appclientset.NewForConfigOrDie(cfg)
kubeClientset := kubernetes.NewForConfigOrDie(cfg)
result, err = reconcileApplications(ctx, kubeClientset, appClientset, namespace, repoServerClient, selector, newLiveStateCache, serverSideDiff)
result, err = reconcileApplications(ctx, kubeClientset, appClientset, namespace, repoServerClient, selector, newLiveStateCache, serverSideDiff, ignoreNormalizerOpts)
errors.CheckError(err)
} else {
appClientset := appclientset.NewForConfigOrDie(cfg)
@@ -297,7 +299,7 @@ func NewReconcileCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
command.Flags().StringVar(&outputFormat, "o", "yaml", "Output format (yaml|json)")
command.Flags().BoolVar(&refresh, "refresh", false, "If set to true then recalculates apps reconciliation")
command.Flags().BoolVar(&serverSideDiff, "server-side-diff", false, "If set to \"true\" will use server-side diff while comparing resources. Default (\"false\")")
command.Flags().DurationVar(&ignoreNormalizerOpts.JQExecutionTimeout, "ignore-normalizer-jq-execution-timeout", normalizers.DefaultJQExecutionTimeout, "Set ignore normalizer JQ execution timeout")
return command
}
@@ -347,6 +349,7 @@ func reconcileApplications(
selector string,
createLiveStateCache func(argoDB db.ArgoDB, appInformer kubecache.SharedIndexInformer, settingsMgr *settings.SettingsManager, server *metrics.MetricsServer) cache.LiveStateCache,
serverSideDiff bool,
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts,
) ([]appReconcileResult, error) {
settingsMgr := settings.NewSettingsManager(ctx, kubeClientset, namespace)
argoDB := db.NewDB(namespace, settingsMgr, kubeClientset)
@@ -387,7 +390,7 @@ func reconcileApplications(
)
appStateManager := controller.NewAppStateManager(
argoDB, appClientset, repoServerClient, namespace, kubeutil.NewKubectl(), settingsMgr, stateCache, projInformer, server, cache, time.Second, argo.NewResourceTracking(), false, 0, serverSideDiff)
argoDB, appClientset, repoServerClient, namespace, kubeutil.NewKubectl(), settingsMgr, stateCache, projInformer, server, cache, time.Second, argo.NewResourceTracking(), false, 0, serverSideDiff, ignoreNormalizerOpts)
appsList, err := appClientset.ArgoprojV1alpha1().Applications(namespace).List(ctx, v1.ListOptions{LabelSelector: selector})
if err != nil {

View File

@@ -23,6 +23,7 @@ import (
argocdclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient"
"github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
"github.com/argoproj/argo-cd/v2/test"
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
"github.com/argoproj/argo-cd/v2/util/db"
"github.com/argoproj/argo-cd/v2/util/settings"
)
@@ -114,6 +115,7 @@ func TestGetReconcileResults_Refresh(t *testing.T) {
return &liveStateCache
},
false,
normalizers.IgnoreNormalizerOpts{},
)
if !assert.NoError(t, err) {

View File

@@ -428,7 +428,7 @@ argocd admin settings resource-overrides ignore-differences ./deploy.yaml --argo
// configurations. This requires access to live resources which is not the
// purpose of this command. This will just apply jsonPointers and
// jqPathExpressions configurations.
normalizer, err := normalizers.NewIgnoreNormalizer(nil, overrides)
normalizer, err := normalizers.NewIgnoreNormalizer(nil, overrides, normalizers.IgnoreNormalizerOpts{})
errors.CheckError(err)
normalizedRes := res.DeepCopy()
@@ -453,6 +453,9 @@ argocd admin settings resource-overrides ignore-differences ./deploy.yaml --argo
}
func NewResourceIgnoreResourceUpdatesCommand(cmdCtx commandContext) *cobra.Command {
var (
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
)
var command = &cobra.Command{
Use: "ignore-resource-updates RESOURCE_YAML_PATH",
Short: "Renders fields excluded from resource updates",
@@ -474,7 +477,7 @@ argocd admin settings resource-overrides ignore-resource-updates ./deploy.yaml -
return
}
normalizer, err := normalizers.NewIgnoreNormalizer(nil, overrides)
normalizer, err := normalizers.NewIgnoreNormalizer(nil, overrides, ignoreNormalizerOpts)
errors.CheckError(err)
normalizedRes := res.DeepCopy()
@@ -495,6 +498,7 @@ argocd admin settings resource-overrides ignore-resource-updates ./deploy.yaml -
})
},
}
command.Flags().DurationVar(&ignoreNormalizerOpts.JQExecutionTimeout, "ignore-normalizer-jq-execution-timeout", normalizers.DefaultJQExecutionTimeout, "Set ignore normalizer JQ execution timeout")
return command
}

View File

@@ -44,6 +44,7 @@ import (
"github.com/argoproj/argo-cd/v2/reposerver/repository"
"github.com/argoproj/argo-cd/v2/util/argo"
argodiff "github.com/argoproj/argo-cd/v2/util/argo/diff"
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
"github.com/argoproj/argo-cd/v2/util/cli"
"github.com/argoproj/argo-cd/v2/util/errors"
"github.com/argoproj/argo-cd/v2/util/git"
@@ -1049,14 +1050,15 @@ type objKeyLiveTarget struct {
// NewApplicationDiffCommand returns a new instance of an `argocd app diff` command
func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
refresh bool
hardRefresh bool
exitCode bool
local string
revision string
localRepoRoot string
serverSideGenerate bool
localIncludes []string
refresh bool
hardRefresh bool
exitCode bool
local string
revision string
localRepoRoot string
serverSideGenerate bool
localIncludes []string
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
)
shortDesc := "Perform a diff against the target and live state."
var command = &cobra.Command{
@@ -1123,7 +1125,7 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
}
}
proj := getProject(c, clientOpts, ctx, app.Spec.Project)
foundDiffs := findandPrintDiff(ctx, app, proj.Project, resources, argoSettings, diffOption)
foundDiffs := findandPrintDiff(ctx, app, proj.Project, resources, argoSettings, diffOption, ignoreNormalizerOpts)
if foundDiffs && exitCode {
os.Exit(1)
}
@@ -1137,6 +1139,7 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
command.Flags().StringVar(&localRepoRoot, "local-repo-root", "/", "Path to the repository root. Used together with --local allows setting the repository root")
command.Flags().BoolVar(&serverSideGenerate, "server-side-generate", false, "Used with --local, this will send your manifests to the server for diffing")
command.Flags().StringArrayVar(&localIncludes, "local-include", []string{"*.yaml", "*.yml", "*.json"}, "Used with --server-side-generate, specify patterns of filenames to send. Matching is based on filename and not path.")
command.Flags().DurationVar(&ignoreNormalizerOpts.JQExecutionTimeout, "ignore-normalizer-jq-execution-timeout", normalizers.DefaultJQExecutionTimeout, "Set ignore normalizer JQ execution timeout")
return command
}
@@ -1151,7 +1154,7 @@ type DifferenceOption struct {
}
// findandPrintDiff ... Prints difference between application current state and state stored in git or locally, returns boolean as true if difference is found else returns false
func findandPrintDiff(ctx context.Context, app *argoappv1.Application, proj *argoappv1.AppProject, resources *application.ManagedResourcesResponse, argoSettings *settings.Settings, diffOptions *DifferenceOption) bool {
func findandPrintDiff(ctx context.Context, app *argoappv1.Application, proj *argoappv1.AppProject, resources *application.ManagedResourcesResponse, argoSettings *settings.Settings, diffOptions *DifferenceOption, ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts) bool {
var foundDiffs bool
liveObjs, err := cmdutil.LiveObjects(resources.Items)
errors.CheckError(err)
@@ -1206,7 +1209,7 @@ func findandPrintDiff(ctx context.Context, app *argoappv1.Application, proj *arg
// compareOptions in the protobuf
ignoreAggregatedRoles := false
diffConfig, err := argodiff.NewDiffConfigBuilder().
WithDiffSettings(app.Spec.IgnoreDifferences, overrides, ignoreAggregatedRoles).
WithDiffSettings(app.Spec.IgnoreDifferences, overrides, ignoreAggregatedRoles, ignoreNormalizerOpts).
WithTracking(argoSettings.AppLabelKey, argoSettings.TrackingMethod).
WithNoCache().
Build()
@@ -1699,6 +1702,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
diffChangesConfirm bool
projects []string
output string
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
)
var command = &cobra.Command{
Use: "sync [APPNAME... | -l selector | --project project-name]",
@@ -1923,7 +1927,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
fmt.Printf("====== Previewing differences between live and desired state of application %s ======\n", appQualifiedName)
proj := getProject(c, clientOpts, ctx, app.Spec.Project)
foundDiffs = findandPrintDiff(ctx, app, proj.Project, resources, argoSettings, diffOption)
foundDiffs = findandPrintDiff(ctx, app, proj.Project, resources, argoSettings, diffOption, ignoreNormalizerOpts)
if foundDiffs {
if !diffChangesConfirm {
yesno := cli.AskToProceed(fmt.Sprintf("Please review changes to application %s shown above. Do you want to continue the sync process? (y/n): ", appQualifiedName))
@@ -1981,6 +1985,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
command.Flags().BoolVar(&diffChanges, "preview-changes", false, "Preview difference against the target and live state before syncing app and wait for user confirmation")
command.Flags().StringArrayVar(&projects, "project", []string{}, "Sync apps that belong to the specified projects. This option may be specified repeatedly.")
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|tree|tree=detailed")
command.Flags().DurationVar(&ignoreNormalizerOpts.JQExecutionTimeout, "ignore-normalizer-jq-execution-timeout", normalizers.DefaultJQExecutionTimeout, "Set ignore normalizer JQ execution timeout")
return command
}

View File

@@ -55,6 +55,7 @@ import (
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
"github.com/argoproj/argo-cd/v2/util/argo"
argodiff "github.com/argoproj/argo-cd/v2/util/argo/diff"
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
"github.com/argoproj/argo-cd/v2/util/env"
kubeerrors "k8s.io/apimachinery/pkg/api/errors"
@@ -130,6 +131,7 @@ type ApplicationController struct {
clusterSharding sharding.ClusterShardingCache
projByNameCache sync.Map
applicationNamespaces []string
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
// dynamicClusterDistributionEnabled if disabled deploymentInformer is never initialized
dynamicClusterDistributionEnabled bool
@@ -160,6 +162,7 @@ func NewApplicationController(
rateLimiterConfig *ratelimiter.AppControllerRateLimiterConfig,
serverSideDiff bool,
dynamicClusterDistributionEnabled bool,
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts,
) (*ApplicationController, error) {
log.Infof("appResyncPeriod=%v, appHardResyncPeriod=%v, appResyncJitter=%v", appResyncPeriod, appHardResyncPeriod, appResyncJitter)
db := db.NewDB(namespace, settingsMgr, kubeClientset)
@@ -191,6 +194,7 @@ func NewApplicationController(
projByNameCache: sync.Map{},
applicationNamespaces: applicationNamespaces,
dynamicClusterDistributionEnabled: dynamicClusterDistributionEnabled,
ignoreNormalizerOpts: ignoreNormalizerOpts,
}
if kubectlParallelismLimit > 0 {
ctrl.kubectlSemaphore = semaphore.NewWeighted(kubectlParallelismLimit)
@@ -278,7 +282,7 @@ func NewApplicationController(
}
}
stateCache := statecache.NewLiveStateCache(db, appInformer, ctrl.settingsMgr, kubectl, ctrl.metricsServer, ctrl.handleObjectUpdated, clusterSharding, argo.NewResourceTracking())
appStateManager := NewAppStateManager(db, applicationClientset, repoClientset, namespace, kubectl, ctrl.settingsMgr, stateCache, projInformer, ctrl.metricsServer, argoCache, ctrl.statusRefreshTimeout, argo.NewResourceTracking(), persistResourceHealth, repoErrorGracePeriod, serverSideDiff)
appStateManager := NewAppStateManager(db, applicationClientset, repoClientset, namespace, kubectl, ctrl.settingsMgr, stateCache, projInformer, ctrl.metricsServer, argoCache, ctrl.statusRefreshTimeout, argo.NewResourceTracking(), persistResourceHealth, repoErrorGracePeriod, serverSideDiff, ignoreNormalizerOpts)
ctrl.appInformer = appInformer
ctrl.appLister = appLister
ctrl.projInformer = projInformer
@@ -729,7 +733,7 @@ func (ctrl *ApplicationController) hideSecretData(app *appv1.Application, compar
return nil, fmt.Errorf("error getting cluster cache: %s", err)
}
diffConfig, err := argodiff.NewDiffConfigBuilder().
WithDiffSettings(app.Spec.IgnoreDifferences, resourceOverrides, compareOptions.IgnoreAggregatedRoles).
WithDiffSettings(app.Spec.IgnoreDifferences, resourceOverrides, compareOptions.IgnoreAggregatedRoles, ctrl.ignoreNormalizerOpts).
WithTracking(appLabelKey, trackingMethod).
WithNoCache().
WithLogger(logutils.NewLogrusLogger(logutils.NewWithCurrentConfig())).

View File

@@ -42,6 +42,7 @@ import (
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
mockrepoclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
"github.com/argoproj/argo-cd/v2/test"
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
"github.com/argoproj/argo-cd/v2/util/settings"
@@ -155,9 +156,9 @@ func newFakeController(data *fakeData, repoErr error) *ApplicationController {
nil,
data.applicationNamespaces,
nil,
false,
false,
normalizers.IgnoreNormalizerOpts{},
)
db := &dbmocks.ArgoDB{}
db.On("GetApplicationControllerReplicas").Return(1)

View File

@@ -33,6 +33,7 @@ import (
"github.com/argoproj/argo-cd/v2/pkg/apis/application"
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
"github.com/argoproj/argo-cd/v2/util/db"
"github.com/argoproj/argo-cd/v2/util/env"
logutils "github.com/argoproj/argo-cd/v2/util/log"
@@ -197,14 +198,15 @@ type cacheSettings struct {
}
type liveStateCache struct {
db db.ArgoDB
appInformer cache.SharedIndexInformer
onObjectUpdated ObjectUpdatedHandler
kubectl kube.Kubectl
settingsMgr *settings.SettingsManager
metricsServer *metrics.MetricsServer
clusterSharding sharding.ClusterShardingCache
resourceTracking argo.ResourceTracking
db db.ArgoDB
appInformer cache.SharedIndexInformer
onObjectUpdated ObjectUpdatedHandler
kubectl kube.Kubectl
settingsMgr *settings.SettingsManager
metricsServer *metrics.MetricsServer
clusterSharding sharding.ClusterShardingCache
resourceTracking argo.ResourceTracking
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
clusters map[string]clustercache.ClusterCache
cacheSettings cacheSettings
@@ -487,7 +489,7 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
gvk := un.GroupVersionKind()
if cacheSettings.ignoreResourceUpdatesEnabled && shouldHashManifest(appName, gvk) {
hash, err := generateManifestHash(un, nil, cacheSettings.resourceOverrides)
hash, err := generateManifestHash(un, nil, cacheSettings.resourceOverrides, c.ignoreNormalizerOpts)
if err != nil {
log.Errorf("Failed to generate manifest hash: %v", err)
} else {

View File

@@ -408,8 +408,8 @@ func populateHostNodeInfo(un *unstructured.Unstructured, res *ResourceInfo) {
}
}
func generateManifestHash(un *unstructured.Unstructured, ignores []v1alpha1.ResourceIgnoreDifferences, overrides map[string]v1alpha1.ResourceOverride) (string, error) {
normalizer, err := normalizers.NewIgnoreNormalizer(ignores, overrides)
func generateManifestHash(un *unstructured.Unstructured, ignores []v1alpha1.ResourceIgnoreDifferences, overrides map[string]v1alpha1.ResourceOverride, opts normalizers.IgnoreNormalizerOpts) (string, error) {
normalizer, err := normalizers.NewIgnoreNormalizer(ignores, overrides, opts)
if err != nil {
return "", fmt.Errorf("error creating normalizer: %w", err)
}

View File

@@ -16,6 +16,7 @@ import (
"sigs.k8s.io/yaml"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
)
func strToUnstructured(jsonStr string) *unstructured.Unstructured {
@@ -749,7 +750,7 @@ func TestManifestHash(t *testing.T) {
expected := hash(data)
hash, err := generateManifestHash(manifest, ignores, nil)
hash, err := generateManifestHash(manifest, ignores, nil, normalizers.IgnoreNormalizerOpts{})
assert.Equal(t, expected, hash)
assert.Nil(t, err)
}

View File

@@ -35,6 +35,7 @@ import (
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
"github.com/argoproj/argo-cd/v2/util/argo"
argodiff "github.com/argoproj/argo-cd/v2/util/argo/diff"
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
"github.com/argoproj/argo-cd/v2/util/db"
"github.com/argoproj/argo-cd/v2/util/gpg"
@@ -117,6 +118,7 @@ type appStateManager struct {
repoErrorCache goSync.Map
repoErrorGracePeriod time.Duration
serverSideDiff bool
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
}
// GetRepoObjs will generate the manifests for the given application delegating the
@@ -605,7 +607,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1
useDiffCache := useDiffCache(noCache, manifestInfos, sources, app, manifestRevisions, m.statusRefreshTimeout, serverSideDiff, logCtx)
diffConfigBuilder := argodiff.NewDiffConfigBuilder().
WithDiffSettings(app.Spec.IgnoreDifferences, resourceOverrides, compareOptions.IgnoreAggregatedRoles).
WithDiffSettings(app.Spec.IgnoreDifferences, resourceOverrides, compareOptions.IgnoreAggregatedRoles, m.ignoreNormalizerOpts).
WithTracking(appLabelKey, string(trackingMethod))
if useDiffCache {
@@ -935,6 +937,7 @@ func NewAppStateManager(
persistResourceHealth bool,
repoErrorGracePeriod time.Duration,
serverSideDiff bool,
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts,
) AppStateManager {
return &appStateManager{
liveStateCache: liveStateCache,
@@ -952,6 +955,7 @@ func NewAppStateManager(
persistResourceHealth: persistResourceHealth,
repoErrorGracePeriod: repoErrorGracePeriod,
serverSideDiff: serverSideDiff,
ignoreNormalizerOpts: ignoreNormalizerOpts,
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

@@ -267,13 +267,13 @@ The final rate limiter uses a combination of both and calculates the final backo
### Global rate limits
This is enabled by default, it is a simple bucket based rate limiter that limits the number of items that can be queued per second.
This is disabled by default, it is a simple bucket based rate limiter that limits the number of items that can be queued per second.
This is useful to prevent a large number of apps from being queued at the same time.
To configure the bucket limiter you can set the following environment variables:
* `WORKQUEUE_BUCKET_SIZE` - The number of items that can be queued in a single burst. Defaults to 500.
* `WORKQUEUE_BUCKET_QPS` - The number of items that can be queued per second. Defaults to 50.
* `WORKQUEUE_BUCKET_QPS` - The number of items that can be queued per second. Defaults to MaxFloat64, which disables the limiter.
### Per item rate limits

View File

@@ -15,71 +15,72 @@ 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-resync-jitter int Maximum time period in seconds to add as a delay jitter for application resync.
--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)
--disable-compression If true, opt-out of response compression for all requests to the server
--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)
--otlp-headers stringToString List of OpenTelemetry collector extra headers sent with traces, headers are comma-separated key-value pairs(e.g. key1=value1,key2=value2) (default [])
--otlp-insecure OpenTelemetry collector insecure mode (default true)
--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-error-grace-period-seconds int Grace period in seconds for ignoring consecutive errors while communicating with repo server. (default 180)
--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
--server-side-diff-enabled Feature flag to enable ServerSide diff. Default ("false")
--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
--wq-backoff-factor float Set Workqueue Per Item Rate Limiter Backoff Factor, default is 1.5 (default 1.5)
--wq-basedelay-ns duration Set Workqueue Per Item Rate Limiter Base Delay duration in nanoseconds, default 1000000 (1ms) (default 1ms)
--wq-bucket-qps int Set Workqueue Rate Limiter Bucket QPS, default 50 (default 50)
--wq-bucket-size int Set Workqueue Rate Limiter Bucket Size, default 500 (default 500)
--wq-cooldown-ns duration Set Workqueue Per Item Rate Limiter Cooldown duration in ns, default 0(per item rate limiter disabled)
--wq-maxdelay-ns duration Set Workqueue Per Item Rate Limiter Max Delay duration in nanoseconds, default 1000000000 (1s) (default 1s)
--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-resync-jitter int Maximum time period in seconds to add as a delay jitter for application resync.
--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)
--disable-compression If true, opt-out of response compression for all requests to the server
--dynamic-cluster-distribution-enabled Enables dynamic cluster distribution.
--gloglevel int Set the glog logging level
-h, --help help for argocd-application-controller
--ignore-normalizer-jq-execution-timeout-seconds duration Set ignore normalizer JQ execution timeout
--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)
--otlp-headers stringToString List of OpenTelemetry collector extra headers sent with traces, headers are comma-separated key-value pairs(e.g. key1=value1,key2=value2) (default [])
--otlp-insecure OpenTelemetry collector insecure mode (default true)
--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-error-grace-period-seconds int Grace period in seconds for ignoring consecutive errors while communicating with repo server. (default 180)
--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
--server-side-diff-enabled Feature flag to enable ServerSide diff. Default ("false")
--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
--wq-backoff-factor float Set Workqueue Per Item Rate Limiter Backoff Factor, default is 1.5 (default 1.5)
--wq-basedelay-ns duration Set Workqueue Per Item Rate Limiter Base Delay duration in nanoseconds, default 1000000 (1ms) (default 1ms)
--wq-bucket-qps float Set Workqueue Rate Limiter Bucket QPS, default set to MaxFloat64 which disables the bucket limiter (default 1.7976931348623157e+308)
--wq-bucket-size int Set Workqueue Rate Limiter Bucket Size, default 500 (default 500)
--wq-cooldown-ns duration Set Workqueue Per Item Rate Limiter Cooldown duration in ns, default 0(per item rate limiter disabled)
--wq-maxdelay-ns duration Set Workqueue Per Item Rate Limiter Max Delay duration in nanoseconds, default 1000000000 (1s) (default 1s)
```

View File

@@ -21,6 +21,7 @@ argocd-repo-server [flags]
--disable-helm-manifest-max-extracted-size Disable maximum size of helm manifest archives when extracted
--disable-tls Disable TLS on the gRPC endpoint
--helm-manifest-max-extracted-size string Maximum size of helm manifest archives when extracted (default "1G")
--helm-registry-max-index-size string Maximum size of registry index file (default "1G")
-h, --help help for argocd-repo-server
--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")

View File

@@ -13,4 +13,4 @@ before enabling `managedNamespaceMetadata` on an existing namespace.
## Upgraded Helm Version
Note that bundled Helm version has been upgraded from 3.13.2 to 3.14.2.
Note that bundled Helm version has been upgraded from 3.13.2 to 3.14.3.

View File

@@ -11,32 +11,33 @@ argocd admin app get-reconcile-results PATH [flags]
### Options
```
--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
--disable-compression If true, opt-out of response compression for all requests to the server
-h, --help help for get-reconcile-results
--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
--l string Label selector
-n, --namespace string If present, the namespace scope for this CLI request
--o string Output format (yaml|json) (default "yaml")
--password string Password for basic authentication to the API server
--proxy-url string If provided, this URL will be used to connect via proxy
--refresh If set to true then recalculates apps reconciliation
--repo-server string Repo server address.
--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")
--server string The address and port of the Kubernetes API server
--server-side-diff If set to "true" will use server-side diff while comparing resources. Default ("false")
--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
--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
--disable-compression If true, opt-out of response compression for all requests to the server
-h, --help help for get-reconcile-results
--ignore-normalizer-jq-execution-timeout duration Set ignore normalizer JQ execution timeout (default 1s)
--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
--l string Label selector
-n, --namespace string If present, the namespace scope for this CLI request
--o string Output format (yaml|json) (default "yaml")
--password string Password for basic authentication to the API server
--proxy-url string If provided, this URL will be used to connect via proxy
--refresh If set to true then recalculates apps reconciliation
--repo-server string Repo server address.
--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")
--server string The address and port of the Kubernetes API server
--server-side-diff If set to "true" will use server-side diff while comparing resources. Default ("false")
--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
```
### Options inherited from parent commands

View File

@@ -22,7 +22,8 @@ argocd admin settings resource-overrides ignore-resource-updates ./deploy.yaml -
### Options
```
-h, --help help for ignore-resource-updates
-h, --help help for ignore-resource-updates
--ignore-normalizer-jq-execution-timeout duration Set ignore normalizer JQ execution timeout (default 1s)
```
### Options inherited from parent commands

View File

@@ -17,15 +17,16 @@ argocd app diff APPNAME [flags]
### Options
```
--exit-code Return non-zero exit code when there is a diff (default true)
--hard-refresh Refresh application data as well as target manifests cache
-h, --help help for diff
--local string Compare live app to a local manifests
--local-include stringArray Used with --server-side-generate, specify patterns of filenames to send. Matching is based on filename and not path. (default [*.yaml,*.yml,*.json])
--local-repo-root string Path to the repository root. Used together with --local allows setting the repository root (default "/")
--refresh Refresh application data when retrieving
--revision string Compare live app to a particular revision
--server-side-generate Used with --local, this will send your manifests to the server for diffing
--exit-code Return non-zero exit code when there is a diff (default true)
--hard-refresh Refresh application data as well as target manifests cache
-h, --help help for diff
--ignore-normalizer-jq-execution-timeout duration Set ignore normalizer JQ execution timeout (default 1s)
--local string Compare live app to a local manifests
--local-include stringArray Used with --server-side-generate, specify patterns of filenames to send. Matching is based on filename and not path. (default [*.yaml,*.yml,*.json])
--local-repo-root string Path to the repository root. Used together with --local allows setting the repository root (default "/")
--refresh Refresh application data when retrieving
--revision string Compare live app to a particular revision
--server-side-generate Used with --local, this will send your manifests to the server for diffing
```
### Options inherited from parent commands

View File

@@ -38,31 +38,32 @@ argocd app sync [APPNAME... | -l selector | --project project-name] [flags]
### Options
```
--apply-out-of-sync-only Sync only out-of-sync resources
--assumeYes Assume yes as answer for all user queries or prompts
--async Do not wait for application to sync before continuing
--dry-run Preview apply without affecting cluster
--force Use a force apply
-h, --help help for sync
--info stringArray A list of key-value pairs during sync process. These infos will be persisted in app.
--label stringArray Sync only specific resources with a label. This option may be specified repeatedly.
--local string Path to a local directory. When this flag is present no git queries will be made
--local-repo-root string Path to the repository root. Used together with --local allows setting the repository root (default "/")
-o, --output string Output format. One of: json|yaml|wide|tree|tree=detailed (default "wide")
--preview-changes Preview difference against the target and live state before syncing app and wait for user confirmation
--project stringArray Sync apps that belong to the specified projects. This option may be specified repeatedly.
--prune Allow deleting unexpected resources
--replace Use a kubectl create/replace instead apply
--resource stringArray Sync only specific resources as GROUP:KIND:NAME or !GROUP:KIND:NAME. Fields may be blank and '*' can be used. This option may be specified repeatedly
--retry-backoff-duration duration Retry backoff base duration. Input needs to be a duration (e.g. 2m, 1h) (default 5s)
--retry-backoff-factor int Factor multiplies the base duration after each failed retry (default 2)
--retry-backoff-max-duration duration Max retry backoff duration. Input needs to be a duration (e.g. 2m, 1h) (default 3m0s)
--retry-limit int Max number of allowed sync retries
--revision string Sync to a specific revision. Preserves parameter overrides
-l, --selector string Sync apps that match this label. Supports '=', '==', '!=', in, notin, exists & not exists. Matching apps must satisfy all of the specified label constraints.
--server-side Use server-side apply while syncing the application
--strategy string Sync strategy (one of: apply|hook)
--timeout uint Time out after this many seconds
--apply-out-of-sync-only Sync only out-of-sync resources
--assumeYes Assume yes as answer for all user queries or prompts
--async Do not wait for application to sync before continuing
--dry-run Preview apply without affecting cluster
--force Use a force apply
-h, --help help for sync
--ignore-normalizer-jq-execution-timeout duration Set ignore normalizer JQ execution timeout (default 1s)
--info stringArray A list of key-value pairs during sync process. These infos will be persisted in app.
--label stringArray Sync only specific resources with a label. This option may be specified repeatedly.
--local string Path to a local directory. When this flag is present no git queries will be made
--local-repo-root string Path to the repository root. Used together with --local allows setting the repository root (default "/")
-o, --output string Output format. One of: json|yaml|wide|tree|tree=detailed (default "wide")
--preview-changes Preview difference against the target and live state before syncing app and wait for user confirmation
--project stringArray Sync apps that belong to the specified projects. This option may be specified repeatedly.
--prune Allow deleting unexpected resources
--replace Use a kubectl create/replace instead apply
--resource stringArray Sync only specific resources as GROUP:KIND:NAME or !GROUP:KIND:NAME. Fields may be blank and '*' can be used. This option may be specified repeatedly
--retry-backoff-duration duration Retry backoff base duration. Input needs to be a duration (e.g. 2m, 1h) (default 5s)
--retry-backoff-factor int Factor multiplies the base duration after each failed retry (default 2)
--retry-backoff-max-duration duration Max retry backoff duration. Input needs to be a duration (e.g. 2m, 1h) (default 3m0s)
--retry-limit int Max number of allowed sync retries
--revision string Sync to a specific revision. Preserves parameter overrides
-l, --selector string Sync apps that match this label. Supports '=', '==', '!=', in, notin, exists & not exists. Matching apps must satisfy all of the specified label constraints.
--server-side Use server-side apply while syncing the application
--strategy string Sync strategy (one of: apply|hook)
--timeout uint Time out after this many seconds
```
### Options inherited from parent commands

View File

@@ -185,3 +185,16 @@ The list of supported Kubernetes types is available in [diffing_known_types.txt]
* `core/Quantity`
* `meta/v1/duration`
### JQ Path expression timeout
By default, the evaluation of a JQPathExpression is limited to one second. If you encounter a "JQ patch execution timed out" error message due to a complex JQPathExpression that requires more time to evaluate, you can extend the timeout period by configuring the `ignore.normalizer.jq.timeout` setting within the `argocd-cmd-params-cm` ConfigMap.
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cmd-params-cm
data:
ignore.normalizer.jq.timeout: "5s"

3
go.mod
View File

@@ -298,6 +298,9 @@ replace (
github.com/golang/protobuf => github.com/golang/protobuf v1.4.2
github.com/grpc-ecosystem/grpc-gateway => github.com/grpc-ecosystem/grpc-gateway v1.16.0
// Avoid CVE-2023-46402
github.com/whilp/git-urls => github.com/chainguard-dev/git-urls v1.0.2
// Avoid CVE-2022-3064
gopkg.in/yaml.v2 => gopkg.in/yaml.v2 v2.4.0

4
go.sum
View File

@@ -786,6 +786,8 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk=
github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=
github.com/chainguard-dev/git-urls v1.0.2 h1:pSpT7ifrpc5X55n4aTTm7FFUE+ZQHKiqpiwNkJrVcKQ=
github.com/chainguard-dev/git-urls v1.0.2/go.mod h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -1699,8 +1701,6 @@ github.com/vmihailenco/msgpack/v5 v5.3.4 h1:qMKAwOV+meBw2Y8k9cVwAy7qErtYCwBzZ2el
github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU=
github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE=
github.com/xanzy/go-gitlab v0.91.1 h1:gnV57IPGYywWer32oXKBcdmc8dVxeKl3AauV8Bu17rw=
github.com/xanzy/go-gitlab v0.91.1/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=

View File

@@ -0,0 +1 @@
4d5d01a94c7d6b07e71690dc1988bf3229680284c87f4242d28c6f1cc99653be helm-v3.14.3-darwin-amd64.tar.gz

View File

@@ -0,0 +1 @@
dff794152b62b7c1a9ff615d510f8657bcd7a3727c668e0d9d4955f70d5f7573 helm-v3.14.3-darwin-arm64.tar.gz

View File

@@ -0,0 +1 @@
3c90f24e180f8c207b8a18e5ec82cb0fa49858a7a0a86e4ed52a98398681e00b helm-v3.14.3-linux-amd64.tar.gz

View File

@@ -0,0 +1 @@
85e1573e76fa60af14ba7e9ec75db2129b6884203be866893fa0b3f7e41ccd5e helm-v3.14.3-linux-arm64.tar.gz

View File

@@ -0,0 +1 @@
aab121ca470e2a502cda849a9b3e92eeb9a32e213b0f0a79a95a04e375d26ce7 helm-v3.14.3-linux-ppc64le.tar.gz

View File

@@ -0,0 +1 @@
d64fa8aced3244b549377741dc4e2db8109e5270c0723c11b547a9da5f99ad43 helm-v3.14.3-linux-s390x.tar.gz

View File

@@ -11,7 +11,7 @@
# Use ./hack/installers/checksums/add-helm-checksums.sh and
# add-kustomize-checksums.sh to help download checksums.
###############################################################################
helm3_version=3.14.2
helm3_version=3.14.3
kubectl_version=1.17.8
kubectx_version=0.6.3
kustomize5_version=5.2.1

View File

@@ -197,6 +197,12 @@ spec:
name: argocd-cmd-params-cm
key: controller.diff.server.side
optional: true
- name: ARGOCD_IGNORE_NORMALIZER_JQ_TIMEOUT
valueFrom:
configMapKeyRef:
name: argocd-cmd-params-cm
key: controller.ignore.normalizer.jq.timeout
optional: true
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-application-controller

View File

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

View File

@@ -23,7 +23,7 @@ spec:
serviceAccountName: argocd-redis
containers:
- name: redis
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: Always
args:
- "--save"

View File

@@ -7310,8 +7310,6 @@ spec:
- metadata
- spec
type: object
required:
- elements
type: object
matrix:
properties:
@@ -9654,8 +9652,6 @@ spec:
- metadata
- spec
type: object
required:
- elements
type: object
matrix:
x-kubernetes-preserve-unknown-fields: true
@@ -14681,8 +14677,6 @@ spec:
- metadata
- spec
type: object
required:
- elements
type: object
matrix:
x-kubernetes-preserve-unknown-fields: true
@@ -21026,7 +21020,7 @@ spec:
key: applicationsetcontroller.enable.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -21121,7 +21115,7 @@ spec:
- ""
- --appendonly
- "no"
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: Always
name: redis
ports:
@@ -21350,7 +21344,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -21402,7 +21396,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -21663,7 +21657,13 @@ spec:
key: controller.diff.server.side
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.2
- name: ARGOCD_IGNORE_NORMALIZER_JQ_TIMEOUT
valueFrom:
configMapKeyRef:
key: controller.ignore.normalizer.jq.timeout
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.8
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: v2.10.2
newTag: v2.10.8

View File

@@ -2370,8 +2370,6 @@ spec:
- metadata
- spec
type: object
required:
- elements
type: object
matrix:
properties:
@@ -4714,8 +4712,6 @@ spec:
- metadata
- spec
type: object
required:
- elements
type: object
matrix:
x-kubernetes-preserve-unknown-fields: true
@@ -9741,8 +9737,6 @@ spec:
- metadata
- spec
type: object
required:
- elements
type: object
matrix:
x-kubernetes-preserve-unknown-fields: true

View File

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

View File

@@ -1207,7 +1207,7 @@ spec:
automountServiceAccountToken: false
initContainers:
- name: config-init
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
resources:
{}
@@ -1241,7 +1241,7 @@ spec:
containers:
- name: redis
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
command:
- redis-server
@@ -1298,7 +1298,7 @@ spec:
- /bin/sh
- /readonly-config/trigger-failover-if-master.sh
- name: sentinel
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
command:
- redis-sentinel
@@ -1349,7 +1349,7 @@ spec:
{}
- name: split-brain-fix
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
command:
- sh

View File

@@ -20,7 +20,7 @@ redis-ha:
metrics:
enabled: true
image:
tag: 7.0.14-alpine
tag: 7.0.15-alpine
containerSecurityContext: null
sentinel:
bind: "0.0.0.0"

View File

@@ -7310,8 +7310,6 @@ spec:
- metadata
- spec
type: object
required:
- elements
type: object
matrix:
properties:
@@ -9654,8 +9652,6 @@ spec:
- metadata
- spec
type: object
required:
- elements
type: object
matrix:
x-kubernetes-preserve-unknown-fields: true
@@ -14681,8 +14677,6 @@ spec:
- metadata
- spec
type: object
required:
- elements
type: object
matrix:
x-kubernetes-preserve-unknown-fields: true
@@ -22389,7 +22383,7 @@ spec:
key: applicationsetcontroller.enable.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -22512,7 +22506,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -22594,7 +22588,7 @@ spec:
key: notificationscontroller.selfservice.enabled
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -22949,7 +22943,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -23001,7 +22995,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -23320,7 +23314,7 @@ spec:
key: server.api.content.types
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -23608,7 +23602,13 @@ spec:
key: controller.diff.server.side
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.2
- name: ARGOCD_IGNORE_NORMALIZER_JQ_TIMEOUT
valueFrom:
configMapKeyRef:
key: controller.ignore.normalizer.jq.timeout
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
name: argocd-application-controller
ports:
@@ -23685,7 +23685,7 @@ spec:
- /data/conf/redis.conf
command:
- redis-server
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
@@ -23739,7 +23739,7 @@ spec:
- /data/conf/sentinel.conf
command:
- redis-sentinel
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
lifecycle: {}
livenessProbe:
@@ -23792,7 +23792,7 @@ spec:
value: 40000915ab58c3fa8fd888fb8b24711944e6cbb4
- name: SENTINEL_ID_2
value: 2bbec7894d954a8af3bb54d13eaec53cb024e2ca
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
name: split-brain-fix
resources: {}
@@ -23822,7 +23822,7 @@ spec:
value: 40000915ab58c3fa8fd888fb8b24711944e6cbb4
- name: SENTINEL_ID_2
value: 2bbec7894d954a8af3bb54d13eaec53cb024e2ca
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
name: config-init
securityContext:

View File

@@ -1668,7 +1668,7 @@ spec:
key: applicationsetcontroller.enable.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -1791,7 +1791,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -1873,7 +1873,7 @@ spec:
key: notificationscontroller.selfservice.enabled
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -2228,7 +2228,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -2280,7 +2280,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -2599,7 +2599,7 @@ spec:
key: server.api.content.types
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2887,7 +2887,13 @@ spec:
key: controller.diff.server.side
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.2
- name: ARGOCD_IGNORE_NORMALIZER_JQ_TIMEOUT
valueFrom:
configMapKeyRef:
key: controller.ignore.normalizer.jq.timeout
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
name: argocd-application-controller
ports:
@@ -2964,7 +2970,7 @@ spec:
- /data/conf/redis.conf
command:
- redis-server
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
@@ -3018,7 +3024,7 @@ spec:
- /data/conf/sentinel.conf
command:
- redis-sentinel
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
lifecycle: {}
livenessProbe:
@@ -3071,7 +3077,7 @@ spec:
value: 40000915ab58c3fa8fd888fb8b24711944e6cbb4
- name: SENTINEL_ID_2
value: 2bbec7894d954a8af3bb54d13eaec53cb024e2ca
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
name: split-brain-fix
resources: {}
@@ -3101,7 +3107,7 @@ spec:
value: 40000915ab58c3fa8fd888fb8b24711944e6cbb4
- name: SENTINEL_ID_2
value: 2bbec7894d954a8af3bb54d13eaec53cb024e2ca
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: IfNotPresent
name: config-init
securityContext:

View File

@@ -7310,8 +7310,6 @@ spec:
- metadata
- spec
type: object
required:
- elements
type: object
matrix:
properties:
@@ -9654,8 +9652,6 @@ spec:
- metadata
- spec
type: object
required:
- elements
type: object
matrix:
x-kubernetes-preserve-unknown-fields: true
@@ -14681,8 +14677,6 @@ spec:
- metadata
- spec
type: object
required:
- elements
type: object
matrix:
x-kubernetes-preserve-unknown-fields: true
@@ -21484,7 +21478,7 @@ spec:
key: applicationsetcontroller.enable.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -21607,7 +21601,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -21689,7 +21683,7 @@ spec:
key: notificationscontroller.selfservice.enabled
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -21766,7 +21760,7 @@ spec:
- ""
- --appendonly
- "no"
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: Always
name: redis
ports:
@@ -21995,7 +21989,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -22047,7 +22041,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -22364,7 +22358,7 @@ spec:
key: server.api.content.types
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -22652,7 +22646,13 @@ spec:
key: controller.diff.server.side
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.2
- name: ARGOCD_IGNORE_NORMALIZER_JQ_TIMEOUT
valueFrom:
configMapKeyRef:
key: controller.ignore.normalizer.jq.timeout
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -763,7 +763,7 @@ spec:
key: applicationsetcontroller.enable.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -886,7 +886,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -968,7 +968,7 @@ spec:
key: notificationscontroller.selfservice.enabled
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -1045,7 +1045,7 @@ spec:
- ""
- --appendonly
- "no"
image: redis:7.0.14-alpine
image: redis:7.0.15-alpine
imagePullPolicy: Always
name: redis
ports:
@@ -1274,7 +1274,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -1326,7 +1326,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -1643,7 +1643,7 @@ spec:
key: server.api.content.types
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.2
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -1931,7 +1931,13 @@ spec:
key: controller.diff.server.side
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.2
- name: ARGOCD_IGNORE_NORMALIZER_JQ_TIMEOUT
valueFrom:
configMapKeyRef:
key: controller.ignore.normalizer.jq.timeout
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.10.8
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -99,6 +99,9 @@ API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/applicat
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ApplicationSourcePluginParameter,String_
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ClusterCacheInfo,APIsCount
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ConnectionState,ModifiedAt
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ErrApplicationNotAllowedToUseProject,application
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ErrApplicationNotAllowedToUseProject,namespace
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ErrApplicationNotAllowedToUseProject,project
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,HelmOptions,ValuesFileSchemes
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,JWTToken,ExpiresAt
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,JWTToken,IssuedAt

View File

@@ -17,6 +17,24 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
)
type ErrApplicationNotAllowedToUseProject struct {
application string
namespace string
project string
}
func NewErrApplicationNotAllowedToUseProject(application, namespace, project string) error {
return &ErrApplicationNotAllowedToUseProject{
application: application,
namespace: namespace,
project: project,
}
}
func (err *ErrApplicationNotAllowedToUseProject) Error() string {
return fmt.Sprintf("application '%s' in namespace '%s' is not allowed to use project %s", err.application, err.namespace, err.project)
}
// AppProjectList is list of AppProject resources
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type AppProjectList struct {

View File

@@ -260,6 +260,7 @@ func (g ApplicationSetTerminalGenerators) toApplicationSetNestedGenerators() []A
// ListGenerator include items info
type ListGenerator struct {
// +kubebuilder:validation:Optional
Elements []apiextensionsv1.JSON `json:"elements" protobuf:"bytes,1,name=elements"`
Template ApplicationSetTemplate `json:"template,omitempty" protobuf:"bytes,2,name=template"`
ElementsYaml string `json:"elementsYaml,omitempty" protobuf:"bytes,3,opt,name=elementsYaml"`

File diff suppressed because it is too large Load Diff

View File

@@ -898,6 +898,9 @@ message EnvEntry {
optional string value = 2;
}
message ErrApplicationNotAllowedToUseProject {
}
// ExecProviderConfig is config used to call an external command to perform cluster authentication
// See: https://godoc.org/k8s.io/client-go/tools/clientcmd/api#ExecConfig
message ExecProviderConfig {
@@ -1131,6 +1134,7 @@ message KustomizeSelector {
// ListGenerator include items info
message ListGenerator {
// +kubebuilder:validation:Optional
repeated k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1.JSON elements = 1;
optional ApplicationSetTemplate template = 2;

View File

@@ -70,6 +70,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ConnectionState": schema_pkg_apis_application_v1alpha1_ConnectionState(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.DuckTypeGenerator": schema_pkg_apis_application_v1alpha1_DuckTypeGenerator(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.EnvEntry": schema_pkg_apis_application_v1alpha1_EnvEntry(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ErrApplicationNotAllowedToUseProject": schema_pkg_apis_application_v1alpha1_ErrApplicationNotAllowedToUseProject(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ExecProviderConfig": schema_pkg_apis_application_v1alpha1_ExecProviderConfig(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.GitDirectoryGeneratorItem": schema_pkg_apis_application_v1alpha1_GitDirectoryGeneratorItem(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.GitFileGeneratorItem": schema_pkg_apis_application_v1alpha1_GitFileGeneratorItem(ref),
@@ -3207,6 +3208,40 @@ func schema_pkg_apis_application_v1alpha1_EnvEntry(ref common.ReferenceCallback)
}
}
func schema_pkg_apis_application_v1alpha1_ErrApplicationNotAllowedToUseProject(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"application": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"namespace": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"project": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"application", "namespace", "project"},
},
},
}
}
func schema_pkg_apis_application_v1alpha1_ExecProviderConfig(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{

View File

@@ -1862,6 +1862,22 @@ func (in *EnvEntry) DeepCopy() *EnvEntry {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ErrApplicationNotAllowedToUseProject) DeepCopyInto(out *ErrApplicationNotAllowedToUseProject) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ErrApplicationNotAllowedToUseProject.
func (in *ErrApplicationNotAllowedToUseProject) DeepCopy() *ErrApplicationNotAllowedToUseProject {
if in == nil {
return nil
}
out := new(ErrApplicationNotAllowedToUseProject)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExecProviderConfig) DeepCopyInto(out *ExecProviderConfig) {
*out = *in

View File

@@ -11,7 +11,7 @@ import (
type AppControllerRateLimiterConfig struct {
BucketSize int64
BucketQPS int64
BucketQPS float64
FailureCoolDown time.Duration
BaseDelay time.Duration
MaxDelay time.Duration
@@ -22,7 +22,8 @@ func GetDefaultAppRateLimiterConfig() *AppControllerRateLimiterConfig {
return &AppControllerRateLimiterConfig{
// global queue rate limit config
500,
50,
// when WORKQUEUE_BUCKET_QPS is MaxFloat64 global bucket limiting is disabled(default)
math.MaxFloat64,
// individual item rate limit config
// when WORKQUEUE_FAILURE_COOLDOWN is 0 per item rate limiting is disabled(default)
0,

View File

@@ -107,6 +107,7 @@ type RepoServerInitConstants struct {
StreamedManifestMaxExtractedSize int64
StreamedManifestMaxTarSize int64
HelmManifestMaxExtractedSize int64
HelmRegistryMaxIndexSize int64
DisableHelmManifestMaxExtractedSize bool
}
@@ -2356,7 +2357,7 @@ func (s *Service) newHelmClientResolveRevision(repo *v1alpha1.Repository, revisi
return helmClient, version.String(), nil
}
index, err := helmClient.GetIndex(noRevisionCache)
index, err := helmClient.GetIndex(noRevisionCache, s.initConstants.HelmRegistryMaxIndexSize)
if err != nil {
return nil, "", err
}
@@ -2434,7 +2435,7 @@ func checkoutRevision(gitClient git.Client, revision string, submoduleEnabled bo
}
func (s *Service) GetHelmCharts(ctx context.Context, q *apiclient.HelmChartsRequest) (*apiclient.HelmChartsResponse, error) {
index, err := s.newHelmClient(q.Repo.Repo, q.Repo.GetHelmCreds(), q.Repo.EnableOCI, q.Repo.Proxy, helm.WithChartPaths(s.chartPaths)).GetIndex(true)
index, err := s.newHelmClient(q.Repo.Repo, q.Repo.GetHelmCreds(), q.Repo.EnableOCI, q.Repo.Proxy, helm.WithChartPaths(s.chartPaths)).GetIndex(true, s.initConstants.HelmRegistryMaxIndexSize)
if err != nil {
return nil, err
}
@@ -2469,7 +2470,7 @@ func (s *Service) TestRepository(ctx context.Context, q *apiclient.TestRepositor
_, err := helm.NewClient(repo.Repo, repo.GetHelmCreds(), repo.EnableOCI, repo.Proxy).TestHelmOCI()
return err
} else {
_, err := helm.NewClient(repo.Repo, repo.GetHelmCreds(), repo.EnableOCI, repo.Proxy).GetIndex(false)
_, err := helm.NewClient(repo.Repo, repo.GetHelmCreds(), repo.EnableOCI, repo.Proxy).GetIndex(false, s.initConstants.HelmRegistryMaxIndexSize)
return err
}
},

View File

@@ -113,7 +113,7 @@ func newServiceWithMocks(t *testing.T, root string, signed bool) (*Service, *git
chart := "my-chart"
oobChart := "out-of-bounds-chart"
version := "1.1.0"
helmClient.On("GetIndex", mock.AnythingOfType("bool")).Return(&helm.Index{Entries: map[string]helm.Entries{
helmClient.On("GetIndex", mock.AnythingOfType("bool"), mock.Anything).Return(&helm.Index{Entries: map[string]helm.Entries{
chart: {{Version: "1.0.0"}, {Version: version}},
oobChart: {{Version: "1.0.0"}, {Version: version}},
}}, nil)

View File

@@ -151,7 +151,7 @@ func NewServer(
//
// If the user does provide a "project," we can respond more specifically. If the user does not have access to the given
// app name in the given project, we return "permission denied." If the app exists, but the project is different from
func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespace, name string, getApp func() (*appv1.Application, error)) (*appv1.Application, error) {
func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespace, name string, getApp func() (*appv1.Application, error)) (*appv1.Application, *appv1.AppProject, error) {
user := session.Username(ctx)
if user == "" {
user = "Unknown user"
@@ -173,7 +173,7 @@ func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespa
// but the app is in a different project" response. We don't want the user inferring the existence of the
// app from response time.
_, _ = getApp()
return nil, permissionDeniedErr
return nil, nil, permissionDeniedErr
}
}
a, err := getApp()
@@ -181,15 +181,15 @@ func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespa
if apierr.IsNotFound(err) {
if project != "" {
// We know that the user was allowed to get the Application, but the Application does not exist. Return 404.
return nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
return nil, nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
}
// We don't know if the user was allowed to get the Application, and we don't want to leak information about
// the Application's existence. Return 403.
logCtx.Warn("application does not exist")
return nil, permissionDeniedErr
return nil, nil, permissionDeniedErr
}
logCtx.Errorf("failed to get application: %s", err)
return nil, permissionDeniedErr
return nil, nil, permissionDeniedErr
}
// Even if we performed an initial RBAC check (because the request was fully parameterized), we still need to
// perform a second RBAC check to ensure that the user has access to the actual Application's project (not just the
@@ -203,11 +203,11 @@ func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespa
// The user specified a project. We would have returned a 404 if the user had access to the app, but the app
// did not exist. So we have to return a 404 when the app does exist, but the user does not have access.
// Otherwise, they could infer that the app exists based on the error code.
return nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
return nil, nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
}
// The user didn't specify a project. We always return permission denied for both lack of access and lack of
// existence.
return nil, permissionDeniedErr
return nil, nil, permissionDeniedErr
}
effectiveProject := "default"
if a.Spec.Project != "" {
@@ -220,15 +220,20 @@ func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespa
}).Warnf("user tried to %s application in project %s, but the application is in project %s", action, project, effectiveProject)
// The user has access to the app, but the app is in a different project. Return 404, meaning "app doesn't
// exist in that project".
return nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
return nil, nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
}
return a, nil
// Get the app's associated project, and make sure all project restrictions are enforced.
proj, err := s.getAppProject(ctx, a, logCtx)
if err != nil {
return a, nil, err
}
return a, proj, nil
}
// getApplicationEnforceRBACInformer uses an informer to get an Application. If the app does not exist, permission is
// denied, or any other error occurs when getting the app, we return a permission denied error to obscure any sensitive
// information.
func (s *Server) getApplicationEnforceRBACInformer(ctx context.Context, action, project, namespace, name string) (*appv1.Application, error) {
func (s *Server) getApplicationEnforceRBACInformer(ctx context.Context, action, project, namespace, name string) (*appv1.Application, *appv1.AppProject, error) {
namespaceOrDefault := s.appNamespaceOrDefault(namespace)
return s.getAppEnforceRBAC(ctx, action, project, namespaceOrDefault, name, func() (*appv1.Application, error) {
return s.appLister.Applications(namespaceOrDefault).Get(name)
@@ -238,7 +243,7 @@ func (s *Server) getApplicationEnforceRBACInformer(ctx context.Context, action,
// getApplicationEnforceRBACClient uses a client to get an Application. If the app does not exist, permission is denied,
// or any other error occurs when getting the app, we return a permission denied error to obscure any sensitive
// information.
func (s *Server) getApplicationEnforceRBACClient(ctx context.Context, action, project, namespace, name, resourceVersion string) (*appv1.Application, error) {
func (s *Server) getApplicationEnforceRBACClient(ctx context.Context, action, project, namespace, name, resourceVersion string) (*appv1.Application, *appv1.AppProject, error) {
namespaceOrDefault := s.appNamespaceOrDefault(namespace)
return s.getAppEnforceRBAC(ctx, action, project, namespaceOrDefault, name, func() (*appv1.Application, error) {
if !s.isNamespaceEnabled(namespaceOrDefault) {
@@ -322,7 +327,13 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq
if q.Validate != nil {
validate = *q.Validate
}
err := s.validateAndNormalizeApp(ctx, a, validate)
proj, err := s.getAppProject(ctx, a, log.WithField("application", a.Name))
if err != nil {
return nil, err
}
err = s.validateAndNormalizeApp(ctx, a, proj, validate)
if err != nil {
return nil, fmt.Errorf("error while validating and normalizing app: %w", err)
}
@@ -333,6 +344,15 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq
return nil, security.NamespaceNotPermittedError(appNs)
}
// Don't let the app creator set the operation explicitly. Those requests should always go through the Sync API.
if a.Operation != nil {
log.WithFields(log.Fields{
"application": a.Name,
argocommon.SecurityField: argocommon.SecurityLow,
}).Warn("User attempted to set operation on application creation. This could have allowed them to bypass branch protection rules by setting manifests directly. Ignoring the set operation.")
a.Operation = nil
}
created, err := s.appclientset.ArgoprojV1alpha1().Applications(appNs).Create(ctx, a, metav1.CreateOptions{})
if err == nil {
s.logAppEvent(created, ctx, argo.EventReasonResourceCreated, "created application")
@@ -369,7 +389,7 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq
return updated, nil
}
func (s *Server) queryRepoServer(ctx context.Context, a *appv1.Application, action func(
func (s *Server) queryRepoServer(ctx context.Context, a *appv1.Application, proj *appv1.AppProject, action func(
client apiclient.RepoServerServiceClient,
repo *appv1.Repository,
helmRepos []*appv1.Repository,
@@ -396,13 +416,6 @@ func (s *Server) queryRepoServer(ctx context.Context, a *appv1.Application, acti
if err != nil {
return fmt.Errorf("error getting kustomize settings options: %w", err)
}
proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
if err != nil {
if apierr.IsNotFound(err) {
return status.Errorf(codes.InvalidArgument, "application references project %s which does not exist", a.Spec.Project)
}
return fmt.Errorf("error getting application's project: %w", err)
}
helmRepos, err := s.db.ListHelmRepositories(ctx)
if err != nil {
@@ -437,7 +450,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
if q.Name == nil || *q.Name == "" {
return nil, fmt.Errorf("invalid request: application name is missing")
}
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
a, proj, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
if err != nil {
return nil, err
}
@@ -449,7 +462,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
}
var manifestInfo *apiclient.ManifestResponse
err = s.queryRepoServer(ctx, a, func(
err = s.queryRepoServer(ctx, a, proj, func(
client apiclient.RepoServerServiceClient, repo *appv1.Repository, helmRepos []*appv1.Repository, helmCreds []*appv1.RepoCreds, helmOptions *appv1.HelmOptions, kustomizeOptions *appv1.KustomizeOptions, enableGenerateManifests map[string]bool) error {
revision := source.TargetRevision
if q.GetRevision() != "" {
@@ -475,11 +488,6 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
return fmt.Errorf("error getting API resources: %w", err)
}
proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
if err != nil {
return fmt.Errorf("error getting app project: %w", err)
}
manifestInfo, err = client.GenerateManifest(ctx, &apiclient.ManifestRequest{
Repo: repo,
Revision: revision,
@@ -542,13 +550,13 @@ func (s *Server) GetManifestsWithFiles(stream application.ApplicationService_Get
return fmt.Errorf("invalid request: application name is missing")
}
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, query.GetProject(), query.GetAppNamespace(), query.GetName())
a, proj, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, query.GetProject(), query.GetAppNamespace(), query.GetName())
if err != nil {
return err
}
var manifestInfo *apiclient.ManifestResponse
err = s.queryRepoServer(ctx, a, func(
err = s.queryRepoServer(ctx, a, proj, func(
client apiclient.RepoServerServiceClient, repo *appv1.Repository, helmRepos []*appv1.Repository, helmCreds []*appv1.RepoCreds, helmOptions *appv1.HelmOptions, kustomizeOptions *appv1.KustomizeOptions, enableGenerateManifests map[string]bool) error {
appInstanceLabelKey, err := s.settingsMgr.GetAppInstanceLabelKey()
@@ -659,7 +667,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
// We must use a client Get instead of an informer Get, because it's common to call Get immediately
// following a Watch (which is not yet powered by an informer), and the Get must reflect what was
// previously seen by the client.
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, project, appNs, appName, q.GetResourceVersion())
a, proj, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, project, appNs, appName, q.GetResourceVersion())
if err != nil {
return nil, err
}
@@ -690,7 +698,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
if refreshType == appv1.RefreshTypeHard {
// force refresh cached application details
if err := s.queryRepoServer(ctx, a, func(
if err := s.queryRepoServer(ctx, a, proj, func(
client apiclient.RepoServerServiceClient,
repo *appv1.Repository,
helmRepos []*appv1.Repository,
@@ -742,7 +750,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
// ListResourceEvents returns a list of event resources
func (s *Server) ListResourceEvents(ctx context.Context, q *application.ApplicationResourceEventsQuery) (*v1.EventList, error) {
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
a, _, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
if err != nil {
return nil, err
}
@@ -810,12 +818,12 @@ func (s *Server) validateAndUpdateApp(ctx context.Context, newApp *appv1.Applica
s.projectLock.RLock(newApp.Spec.GetProject())
defer s.projectLock.RUnlock(newApp.Spec.GetProject())
app, err := s.getApplicationEnforceRBACClient(ctx, action, currentProject, newApp.Namespace, newApp.Name, "")
app, proj, err := s.getApplicationEnforceRBACClient(ctx, action, currentProject, newApp.Namespace, newApp.Name, "")
if err != nil {
return nil, err
}
err = s.validateAndNormalizeApp(ctx, newApp, validate)
err = s.validateAndNormalizeApp(ctx, newApp, proj, validate)
if err != nil {
return nil, fmt.Errorf("error validating and normalizing app: %w", err)
}
@@ -914,7 +922,7 @@ func (s *Server) UpdateSpec(ctx context.Context, q *application.ApplicationUpdat
if q.GetSpec() == nil {
return nil, fmt.Errorf("error updating application spec: spec is nil in request")
}
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionUpdate, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
a, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionUpdate, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
if err != nil {
return nil, err
}
@@ -933,7 +941,7 @@ func (s *Server) UpdateSpec(ctx context.Context, q *application.ApplicationUpdat
// Patch patches an application
func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchRequest) (*appv1.Application, error) {
app, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
app, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
if err != nil {
return nil, err
}
@@ -976,11 +984,35 @@ func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchReque
return s.validateAndUpdateApp(ctx, newApp, false, true, rbacpolicy.ActionUpdate, q.GetProject())
}
func (s *Server) getAppProject(ctx context.Context, a *appv1.Application, logCtx *log.Entry) (*appv1.AppProject, error) {
proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
if err == nil {
return proj, nil
}
// If there's a permission issue or the app doesn't exist, return a vague error to avoid letting the user enumerate project names.
vagueError := status.Errorf(codes.InvalidArgument, "app is not allowed in project %q, or the project does not exist", a.Spec.Project)
if apierr.IsNotFound(err) {
return nil, vagueError
}
if _, ok := err.(*appv1.ErrApplicationNotAllowedToUseProject); ok {
logCtx.WithFields(map[string]interface{}{
"project": a.Spec.Project,
argocommon.SecurityField: argocommon.SecurityMedium,
}).Warnf("error getting app project: %s", err)
return nil, vagueError
}
return nil, vagueError
}
// Delete removes an application and all associated resources
func (s *Server) Delete(ctx context.Context, q *application.ApplicationDeleteRequest) (*application.ApplicationResponse, error) {
appName := q.GetName()
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), appNs, appName, "")
a, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), appNs, appName, "")
if err != nil {
return nil, err
}
@@ -1135,16 +1167,7 @@ func (s *Server) Watch(q *application.ApplicationQuery, ws application.Applicati
}
}
func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Application, validate bool) error {
proj, err := argo.GetAppProject(app, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
if err != nil {
if apierr.IsNotFound(err) {
// Offer no hint that the project does not exist.
log.Warnf("User attempted to create/update application in non-existent project %q", app.Spec.Project)
return permissionDeniedErr
}
return fmt.Errorf("error getting application's project: %w", err)
}
func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Application, proj *appv1.AppProject, validate bool) error {
if app.GetName() == "" {
return fmt.Errorf("resource name may not be empty")
}
@@ -1248,7 +1271,7 @@ func (s *Server) getAppResources(ctx context.Context, a *appv1.Application) (*ap
}
func (s *Server) getAppLiveResource(ctx context.Context, action string, q *application.ApplicationResourceRequest) (*appv1.ResourceNode, *rest.Config, *appv1.Application, error) {
a, err := s.getApplicationEnforceRBACInformer(ctx, action, q.GetProject(), q.GetAppNamespace(), q.GetName())
a, _, err := s.getApplicationEnforceRBACInformer(ctx, action, q.GetProject(), q.GetAppNamespace(), q.GetName())
if err != nil {
return nil, nil, nil, err
}
@@ -1385,7 +1408,7 @@ func (s *Server) DeleteResource(ctx context.Context, q *application.ApplicationR
}
func (s *Server) ResourceTree(ctx context.Context, q *application.ResourcesQuery) (*appv1.ApplicationTree, error) {
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
a, _, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
if err != nil {
return nil, err
}
@@ -1394,7 +1417,7 @@ func (s *Server) ResourceTree(ctx context.Context, q *application.ResourcesQuery
}
func (s *Server) WatchResourceTree(q *application.ResourcesQuery, ws application.ApplicationService_WatchResourceTreeServer) error {
_, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
_, _, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
if err != nil {
return err
}
@@ -1411,7 +1434,7 @@ func (s *Server) WatchResourceTree(q *application.ResourcesQuery, ws application
}
func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMetadataQuery) (*appv1.RevisionMetadata, error) {
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
a, proj, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
if err != nil {
return nil, err
}
@@ -1421,12 +1444,6 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe
if err != nil {
return nil, fmt.Errorf("error getting repository by URL: %w", err)
}
// We need to get some information with the project associated to the app,
// so we'll know whether GPG signatures are enforced.
proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
if err != nil {
return nil, fmt.Errorf("error getting app project: %w", err)
}
conn, repoClient, err := s.repoClientset.NewRepoServerClient()
if err != nil {
return nil, fmt.Errorf("error creating repo server client: %w", err)
@@ -1441,7 +1458,7 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe
// RevisionChartDetails returns the helm chart metadata, as fetched from the reposerver
func (s *Server) RevisionChartDetails(ctx context.Context, q *application.RevisionMetadataQuery) (*appv1.ChartDetails, error) {
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
a, _, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
if err != nil {
return nil, err
}
@@ -1472,7 +1489,7 @@ func isMatchingResource(q *application.ResourcesQuery, key kube.ResourceKey) boo
}
func (s *Server) ManagedResources(ctx context.Context, q *application.ResourcesQuery) (*application.ManagedResourcesResponse, error) {
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
a, _, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
if err != nil {
return nil, err
}
@@ -1529,7 +1546,7 @@ func (s *Server) PodLogs(q *application.ApplicationPodLogsQuery, ws application.
}
}
a, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
a, _, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
if err != nil {
return err
}
@@ -1721,19 +1738,11 @@ func isTheSelectedOne(currentNode *appv1.ResourceNode, q *application.Applicatio
// Sync syncs an application to its target state
func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncRequest) (*appv1.Application, error) {
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, syncReq.GetProject(), syncReq.GetAppNamespace(), syncReq.GetName(), "")
a, proj, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, syncReq.GetProject(), syncReq.GetAppNamespace(), syncReq.GetName(), "")
if err != nil {
return nil, err
}
proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
if err != nil {
if apierr.IsNotFound(err) {
return a, status.Errorf(codes.InvalidArgument, "application references project %s which does not exist", a.Spec.Project)
}
return a, fmt.Errorf("error getting app project: %w", err)
}
s.inferResourcesStatusHealth(a)
if !proj.Spec.SyncWindows.Matches(a).CanSync(true) {
@@ -1830,7 +1839,7 @@ func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncR
}
func (s *Server) Rollback(ctx context.Context, rollbackReq *application.ApplicationRollbackRequest) (*appv1.Application, error) {
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, rollbackReq.GetProject(), rollbackReq.GetAppNamespace(), rollbackReq.GetName(), "")
a, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, rollbackReq.GetProject(), rollbackReq.GetAppNamespace(), rollbackReq.GetName(), "")
if err != nil {
return nil, err
}
@@ -1889,7 +1898,7 @@ func (s *Server) Rollback(ctx context.Context, rollbackReq *application.Applicat
}
func (s *Server) ListLinks(ctx context.Context, req *application.ListAppLinksRequest) (*application.LinksResponse, error) {
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, req.GetProject(), req.GetNamespace(), req.GetName(), "")
a, proj, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, req.GetProject(), req.GetNamespace(), req.GetName(), "")
if err != nil {
return nil, err
}
@@ -1904,7 +1913,7 @@ func (s *Server) ListLinks(ctx context.Context, req *application.ListAppLinksReq
return nil, fmt.Errorf("failed to read application deep links from configmap: %w", err)
}
clstObj, _, err := s.getObjectsForDeepLinks(ctx, a)
clstObj, _, err := s.getObjectsForDeepLinks(ctx, a, proj)
if err != nil {
return nil, err
}
@@ -1919,12 +1928,7 @@ func (s *Server) ListLinks(ctx context.Context, req *application.ListAppLinksReq
return finalList, nil
}
func (s *Server) getObjectsForDeepLinks(ctx context.Context, app *appv1.Application) (cluster *unstructured.Unstructured, project *unstructured.Unstructured, err error) {
proj, err := argo.GetAppProject(app, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
if err != nil {
return nil, nil, fmt.Errorf("error getting app project: %w", err)
}
func (s *Server) getObjectsForDeepLinks(ctx context.Context, app *appv1.Application, proj *appv1.AppProject) (cluster *unstructured.Unstructured, project *unstructured.Unstructured, err error) {
// sanitize project jwt tokens
proj.Status = appv1.AppProjectStatus{}
@@ -1987,7 +1991,12 @@ func (s *Server) ListResourceLinks(ctx context.Context, req *application.Applica
return nil, err
}
clstObj, projObj, err := s.getObjectsForDeepLinks(ctx, app)
proj, err := s.getAppProject(ctx, app, log.WithField("application", app.GetName()))
if err != nil {
return nil, err
}
clstObj, projObj, err := s.getObjectsForDeepLinks(ctx, app, proj)
if err != nil {
return nil, err
}
@@ -2043,7 +2052,7 @@ func (s *Server) resolveRevision(ctx context.Context, app *appv1.Application, sy
func (s *Server) TerminateOperation(ctx context.Context, termOpReq *application.OperationTerminateRequest) (*application.OperationTerminateResponse, error) {
appName := termOpReq.GetName()
appNs := s.appNamespaceOrDefault(termOpReq.GetAppNamespace())
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, termOpReq.GetProject(), appNs, appName, "")
a, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, termOpReq.GetProject(), appNs, appName, "")
if err != nil {
return nil, err
}
@@ -2116,7 +2125,7 @@ func (s *Server) ListResourceActions(ctx context.Context, q *application.Applica
func (s *Server) getUnstructuredLiveResourceOrApp(ctx context.Context, rbacRequest string, q *application.ApplicationResourceRequest) (obj *unstructured.Unstructured, res *appv1.ResourceNode, app *appv1.Application, config *rest.Config, err error) {
if q.GetKind() == applicationType.ApplicationKind && q.GetGroup() == applicationType.Group && q.GetName() == q.GetResourceName() {
app, err = s.getApplicationEnforceRBACInformer(ctx, rbacRequest, q.GetProject(), q.GetAppNamespace(), q.GetName())
app, _, err = s.getApplicationEnforceRBACInformer(ctx, rbacRequest, q.GetProject(), q.GetAppNamespace(), q.GetName())
if err != nil {
return nil, nil, nil, nil, err
}
@@ -2212,6 +2221,11 @@ func (s *Server) RunResourceAction(ctx context.Context, q *application.ResourceA
}
}
proj, err := s.getAppProject(ctx, a, log.WithField("application", a.Name))
if err != nil {
return nil, err
}
// First, make sure all the returned resources are permitted, for each operation.
// Also perform create with dry-runs for all create-operation resources.
// This is performed separately to reduce the risk of only some of the resources being successfully created later.
@@ -2219,7 +2233,7 @@ func (s *Server) RunResourceAction(ctx context.Context, q *application.ResourceA
// the dry-run for relevant apply/delete operation would have to be invoked as well.
for _, impactedResource := range newObjects {
newObj := impactedResource.UnstructuredObj
err := s.verifyResourcePermitted(ctx, app, newObj)
err := s.verifyResourcePermitted(ctx, app, proj, newObj)
if err != nil {
return nil, err
}
@@ -2313,14 +2327,7 @@ func (s *Server) patchResource(ctx context.Context, config *rest.Config, liveObj
return &application.ApplicationResponse{}, nil
}
func (s *Server) verifyResourcePermitted(ctx context.Context, app *appv1.Application, obj *unstructured.Unstructured) error {
proj, err := argo.GetAppProject(app, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
if err != nil {
if apierr.IsNotFound(err) {
return fmt.Errorf("application references project %s which does not exist", app.Spec.Project)
}
return fmt.Errorf("failed to get project %s: %w", app.Spec.Project, err)
}
func (s *Server) verifyResourcePermitted(ctx context.Context, app *appv1.Application, proj *appv1.AppProject, obj *unstructured.Unstructured) error {
permitted, err := proj.IsResourcePermitted(schema.GroupKind{Group: obj.GroupVersionKind().Group, Kind: obj.GroupVersionKind().Kind}, obj.GetNamespace(), app.Spec.Destination, func(project string) ([]*appv1.Cluster, error) {
clusters, err := s.db.GetProjectClusters(context.TODO(), project)
if err != nil {
@@ -2380,16 +2387,11 @@ func splitStatusPatch(patch []byte) ([]byte, []byte, error) {
}
func (s *Server) GetApplicationSyncWindows(ctx context.Context, q *application.ApplicationSyncWindowsQuery) (*application.ApplicationSyncWindowsResponse, error) {
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
a, proj, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
if err != nil {
return nil, err
}
proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
if err != nil {
return nil, fmt.Errorf("error getting app project: %w", err)
}
windows := proj.Spec.SyncWindows.Matches(a)
sync := windows.CanSync(true)

View File

@@ -1439,6 +1439,27 @@ func TestCreateAppWithDestName(t *testing.T) {
assert.Equal(t, app.Spec.Destination.Server, "https://cluster-api.example.com")
}
// TestCreateAppWithOperation tests that an application created with an operation is created with the operation removed.
// Avoids regressions of https://github.com/argoproj/argo-cd/security/advisories/GHSA-g623-jcgg-mhmm
func TestCreateAppWithOperation(t *testing.T) {
appServer := newTestAppServer(t)
testApp := newTestAppWithDestName()
testApp.Operation = &appsv1.Operation{
Sync: &appsv1.SyncOperation{
Manifests: []string{
"test",
},
},
}
createReq := application.ApplicationCreateRequest{
Application: testApp,
}
app, err := appServer.Create(context.Background(), &createReq)
require.NoError(t, err)
require.NotNil(t, app)
assert.Nil(t, app.Operation)
}
func TestUpdateApp(t *testing.T) {
testApp := newTestApp()
appServer := newTestAppServer(t, testApp)
@@ -1797,7 +1818,7 @@ func TestServer_GetApplicationSyncWindowsState(t *testing.T) {
appServer := newTestAppServer(t, testApp)
active, err := appServer.GetApplicationSyncWindows(context.Background(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name})
assert.Contains(t, err.Error(), "not found")
assert.Contains(t, err.Error(), "not exist")
assert.Nil(t, active)
})
}
@@ -2407,7 +2428,16 @@ func TestAppNamespaceRestrictions(t *testing.T) {
t.Run("Get application in other namespace when allowed", func(t *testing.T) {
testApp := newTestApp()
testApp.Namespace = "argocd-1"
appServer := newTestAppServer(t, testApp)
testApp.Spec.Project = "other-ns"
otherNsProj := &appsv1.AppProject{
ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
Spec: appsv1.AppProjectSpec{
SourceRepos: []string{"*"},
Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
SourceNamespaces: []string{"argocd-1"},
},
}
appServer := newTestAppServer(t, testApp, otherNsProj)
appServer.enabledNamespaces = []string{"argocd-1"}
app, err := appServer.Get(context.TODO(), &application.ApplicationQuery{
Name: pointer.String("test-app"),
@@ -2418,6 +2448,28 @@ func TestAppNamespaceRestrictions(t *testing.T) {
require.Equal(t, "argocd-1", app.Namespace)
require.Equal(t, "test-app", app.Name)
})
t.Run("Get application in other namespace when project is not allowed", func(t *testing.T) {
testApp := newTestApp()
testApp.Namespace = "argocd-1"
testApp.Spec.Project = "other-ns"
otherNsProj := &appsv1.AppProject{
ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
Spec: appsv1.AppProjectSpec{
SourceRepos: []string{"*"},
Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
SourceNamespaces: []string{"argocd-2"},
},
}
appServer := newTestAppServer(t, testApp, otherNsProj)
appServer.enabledNamespaces = []string{"argocd-1"}
app, err := appServer.Get(context.TODO(), &application.ApplicationQuery{
Name: pointer.String("test-app"),
AppNamespace: pointer.String("argocd-1"),
})
require.Error(t, err)
require.Nil(t, app)
require.ErrorContains(t, err, "app is not allowed in project")
})
t.Run("Create application in other namespace when allowed", func(t *testing.T) {
testApp := newTestApp()
testApp.Namespace = "argocd-1"
@@ -2460,7 +2512,7 @@ func TestAppNamespaceRestrictions(t *testing.T) {
})
require.Error(t, err)
require.Nil(t, app)
require.ErrorContains(t, err, "not allowed to use project")
require.ErrorContains(t, err, "app is not allowed in project")
})
t.Run("Create application in other namespace when not allowed by configuration", func(t *testing.T) {
@@ -2484,5 +2536,84 @@ func TestAppNamespaceRestrictions(t *testing.T) {
require.Nil(t, app)
require.ErrorContains(t, err, "namespace 'argocd-1' is not permitted")
})
t.Run("Get application sync window in other namespace when project is allowed", func(t *testing.T) {
testApp := newTestApp()
testApp.Namespace = "argocd-1"
testApp.Spec.Project = "other-ns"
otherNsProj := &appsv1.AppProject{
ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
Spec: appsv1.AppProjectSpec{
SourceRepos: []string{"*"},
Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
SourceNamespaces: []string{"argocd-1"},
},
}
appServer := newTestAppServer(t, testApp, otherNsProj)
appServer.enabledNamespaces = []string{"argocd-1"}
active, err := appServer.GetApplicationSyncWindows(context.TODO(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name, AppNamespace: &testApp.Namespace})
assert.NoError(t, err)
assert.Equal(t, 0, len(active.ActiveWindows))
})
t.Run("Get application sync window in other namespace when project is not allowed", func(t *testing.T) {
testApp := newTestApp()
testApp.Namespace = "argocd-1"
testApp.Spec.Project = "other-ns"
otherNsProj := &appsv1.AppProject{
ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
Spec: appsv1.AppProjectSpec{
SourceRepos: []string{"*"},
Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
SourceNamespaces: []string{"argocd-2"},
},
}
appServer := newTestAppServer(t, testApp, otherNsProj)
appServer.enabledNamespaces = []string{"argocd-1"}
active, err := appServer.GetApplicationSyncWindows(context.TODO(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name, AppNamespace: &testApp.Namespace})
require.Error(t, err)
require.Nil(t, active)
require.ErrorContains(t, err, "app is not allowed in project")
})
t.Run("Get list of links in other namespace when project is not allowed", func(t *testing.T) {
testApp := newTestApp()
testApp.Namespace = "argocd-1"
testApp.Spec.Project = "other-ns"
otherNsProj := &appsv1.AppProject{
ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
Spec: appsv1.AppProjectSpec{
SourceRepos: []string{"*"},
Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
SourceNamespaces: []string{"argocd-2"},
},
}
appServer := newTestAppServer(t, testApp, otherNsProj)
appServer.enabledNamespaces = []string{"argocd-1"}
links, err := appServer.ListLinks(context.TODO(), &application.ListAppLinksRequest{
Name: pointer.String("test-app"),
Namespace: pointer.String("argocd-1"),
})
require.Error(t, err)
require.Nil(t, links)
require.ErrorContains(t, err, "app is not allowed in project")
})
t.Run("Get list of links in other namespace when project is allowed", func(t *testing.T) {
testApp := newTestApp()
testApp.Namespace = "argocd-1"
testApp.Spec.Project = "other-ns"
otherNsProj := &appsv1.AppProject{
ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
Spec: appsv1.AppProjectSpec{
SourceRepos: []string{"*"},
Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
SourceNamespaces: []string{"argocd-1"},
},
}
appServer := newTestAppServer(t, testApp, otherNsProj)
appServer.enabledNamespaces = []string{"argocd-1"}
links, err := appServer.ListLinks(context.TODO(), &application.ListAppLinksRequest{
Name: pointer.String("test-app"),
Namespace: pointer.String("argocd-1"),
})
require.NoError(t, err)
assert.Equal(t, 0, len(links.Items))
})
}

View File

@@ -38,12 +38,12 @@ type terminalHandler struct {
allowedShells []string
namespace string
enabledNamespaces []string
sessionManager util_session.SessionManager
sessionManager *util_session.SessionManager
}
// NewHandler returns a new terminal handler.
func NewHandler(appLister applisters.ApplicationLister, namespace string, enabledNamespaces []string, db db.ArgoDB, enf *rbac.Enforcer, cache *servercache.Cache,
appResourceTree AppResourceTreeFn, allowedShells []string, sessionManager util_session.SessionManager) *terminalHandler {
appResourceTree AppResourceTreeFn, allowedShells []string, sessionManager *util_session.SessionManager) *terminalHandler {
return &terminalHandler{
appLister: appLister,
db: db,

View File

@@ -37,7 +37,7 @@ type terminalSession struct {
tty bool
readLock sync.Mutex
writeLock sync.Mutex
sessionManager util_session.SessionManager
sessionManager *util_session.SessionManager
token *string
}
@@ -48,7 +48,7 @@ func getToken(r *http.Request) (string, error) {
}
// newTerminalSession create terminalSession
func newTerminalSession(w http.ResponseWriter, r *http.Request, responseHeader http.Header, sessionManager util_session.SessionManager) (*terminalSession, error) {
func newTerminalSession(w http.ResponseWriter, r *http.Request, responseHeader http.Header, sessionManager *util_session.SessionManager) (*terminalSession, error) {
token, err := getToken(r)
if err != nil {
return nil, err

View File

@@ -997,7 +997,7 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
}
mux.Handle("/api/", handler)
terminal := application.NewHandler(a.appLister, a.Namespace, a.ApplicationNamespaces, a.db, a.enf, a.Cache, appResourceTreeFn, a.settings.ExecShells, *a.sessionMgr).
terminal := application.NewHandler(a.appLister, a.Namespace, a.ApplicationNamespaces, a.db, a.enf, a.Cache, appResourceTreeFn, a.settings.ExecShells, a.sessionMgr).
WithFeatureFlagMiddleware(a.settingsMgr.GetSettings)
th := util_session.WithAuthMiddleware(a.DisableAuth, a.sessionMgr, terminal)
mux.Handle("/terminal", th)

View File

@@ -432,7 +432,7 @@ func TestNamespacedInvalidAppProject(t *testing.T) {
Then().
// We're not allowed to infer whether the project exists based on this error message. Instead, we get a generic
// permission denied error.
Expect(Error("", "permission denied"))
Expect(Error("", "is not allowed"))
}
func TestNamespacedAppDeletion(t *testing.T) {

View File

@@ -547,7 +547,7 @@ func TestInvalidAppProject(t *testing.T) {
Then().
// We're not allowed to infer whether the project exists based on this error message. Instead, we get a generic
// permission denied error.
Expect(Error("", "permission denied"))
Expect(Error("", "is not allowed"))
}
func TestAppDeletion(t *testing.T) {

View File

@@ -54,12 +54,14 @@ func TestDeclarativeInvalidProject(t *testing.T) {
Expect(Success("")).
Expect(HealthIs(health.HealthStatusUnknown)).
Expect(SyncStatusIs(SyncStatusCodeUnknown)).
Expect(Condition(ApplicationConditionInvalidSpecError, "Application referencing project garbage which does not exist")).
When().
Delete(false).
Then().
Expect(Success("")).
Expect(DoesNotExist())
Expect(Condition(ApplicationConditionInvalidSpecError, "Application referencing project garbage which does not exist"))
// TODO: you can`t delete application with invalid project due to enforcment that was recently added,
// in https://github.com/argoproj/argo-cd/security/advisories/GHSA-2gvw-w6fj-7m3c
//When().
//Delete(false).
//Then().
//Expect(Success("")).
//Expect(DoesNotExist())
}
func TestDeclarativeInvalidRepoURL(t *testing.T) {

View File

@@ -286,7 +286,7 @@ export const ApplicationParameters = (props: {
} else if (props.details.type === 'Plugin') {
attributes.push({
title: 'NAME',
view: <div style={{marginTop: 15, marginBottom: 5}}>{ValueEditor(app.spec.source.plugin && app.spec.source.plugin.name, null)}</div>,
view: <div style={{marginTop: 15, marginBottom: 5}}>{ValueEditor(app.spec.source?.plugin?.name, null)}</div>,
edit: (formApi: FormApi) => (
<DataLoader load={() => services.authService.plugins()}>
{(plugins: Plugin[]) => (
@@ -299,12 +299,11 @@ export const ApplicationParameters = (props: {
title: 'ENV',
view: (
<div style={{marginTop: 15}}>
{app.spec.source.plugin &&
(app.spec.source.plugin.env || []).map(val => (
<span key={val.name} style={{display: 'block', marginBottom: 5}}>
{NameValueEditor(val, null)}
</span>
))}
{(app.spec.source?.plugin?.env || []).map(val => (
<span key={val.name} style={{display: 'block', marginBottom: 5}}>
{NameValueEditor(val, null)}
</span>
))}
</div>
),
edit: (formApi: FormApi) => <FormField field='spec.source.plugin.env' formApi={formApi} component={ArrayInputField} />
@@ -315,7 +314,7 @@ export const ApplicationParameters = (props: {
parametersSet.add(announcement.name);
}
}
if (app.spec.source.plugin?.parameters) {
if (app.spec.source?.plugin?.parameters) {
for (const appParameter of app.spec.source.plugin.parameters) {
parametersSet.add(appParameter.name);
}
@@ -326,7 +325,7 @@ export const ApplicationParameters = (props: {
}
parametersSet.forEach(name => {
const announcement = props.details.plugin.parametersAnnouncement?.find(param => param.name === name);
const liveParam = app.spec.source.plugin?.parameters?.find(param => param.name === name);
const liveParam = app.spec.source?.plugin?.parameters?.find(param => param.name === name);
const pluginIcon =
announcement && liveParam ? 'This parameter has been provided by plugin, but is overridden in application manifest.' : 'This parameter is provided by the plugin.';
const isPluginPar = !!announcement;

View File

@@ -30,6 +30,7 @@ import {EditAnnotations} from './edit-annotations';
import './application-summary.scss';
import {DeepLinks} from '../../../shared/components/deep-links';
import {ExternalLinks} from '../application-urls';
function swap(array: any[], a: number, b: number) {
array = array.slice();
@@ -326,20 +327,19 @@ export const ApplicationSummary = (props: ApplicationSummaryProps) => {
)
}
];
const urls = app.status.summary.externalURLs || [];
const urls = ExternalLinks(app.status.summary.externalURLs);
if (urls.length > 0) {
attributes.push({
title: 'URLs',
view: (
<React.Fragment>
{urls
.map(item => item.split('|'))
.map((parts, i) => (
<a key={i} href={parts.length > 1 ? parts[1] : parts[0]} target='__blank'>
{parts[0]} &nbsp;
{urls.map((url, i) => {
return (
<a key={i} href={url.ref} target='__blank'>
{url.title} &nbsp;
</a>
))}
);
})}
</React.Fragment>
)
});

View File

@@ -1,4 +1,4 @@
import {ExternalLink, InvalidExternalLinkError} from './application-urls';
import { ExternalLink, ExternalLinks, InvalidExternalLinkError } from './application-urls';
test('rejects malicious URLs', () => {
expect(() => {
@@ -7,6 +7,16 @@ test('rejects malicious URLs', () => {
expect(() => {
const _ = new ExternalLink('data:text/html;<h1>hi</h1>');
}).toThrowError(InvalidExternalLinkError);
expect(() => {
const _ = new ExternalLink('title|data:text/html;<h1>hi</h1>');
}).toThrowError(InvalidExternalLinkError);
expect(() => {
const _ = new ExternalLink('data:title|data:text/html;<h1>hi</h1>');
}).toThrowError(InvalidExternalLinkError);
expect(() => {
const _ = new ExternalLink('data:title|https://localhost:8080/applications');
}).not.toThrowError(InvalidExternalLinkError);
});
test('allows absolute URLs', () => {
@@ -18,3 +28,59 @@ test('allows relative URLs', () => {
window.location = new URL('https://localhost:8080/applications');
expect(new ExternalLink('/applications').ref).toEqual('/applications');
});
test('URLs format', () => {
expect(new ExternalLink('https://localhost:8080/applications')).toEqual({
ref: 'https://localhost:8080/applications',
title: 'https://localhost:8080/applications',
})
expect(new ExternalLink('title|https://localhost:8080/applications')).toEqual({
ref: 'https://localhost:8080/applications',
title: 'title',
})
});
test('malicious URLs from list to be removed', () => {
const urls: string[] = [
'javascript:alert("hi")',
'https://localhost:8080/applications',
]
const links = ExternalLinks(urls);
expect(links).toHaveLength(1);
expect(links).toContainEqual({
ref: 'https://localhost:8080/applications',
title: 'https://localhost:8080/applications',
});
});
test('list to be sorted', () => {
const urls: string[] = [
'https://a',
'https://b',
'a|https://c',
'z|https://c',
'x|https://d',
'x|https://c',
]
const links = ExternalLinks(urls);
// 'a|https://c',
// 'x|https://c',
// 'x|https://d',
// 'z|https://c',
// 'https://a',
// 'https://b',
expect(links).toHaveLength(6);
expect(links[0].title).toEqual('a')
expect(links[1].title).toEqual('x')
expect(links[1].ref).toEqual('https://c')
expect(links[2].title).toEqual('x')
expect(links[2].ref).toEqual('https://d')
expect(links[3].title).toEqual('z')
expect(links[4].title).toEqual('https://a')
expect(links[5].title).toEqual('https://b')
});

View File

@@ -29,7 +29,7 @@ export class ExternalLink {
}
}
export const ApplicationURLs = ({urls}: {urls: string[]}) => {
export const ExternalLinks = (urls?: string[]) => {
const externalLinks: ExternalLink[] = [];
for (const url of urls || []) {
try {
@@ -42,16 +42,26 @@ export const ApplicationURLs = ({urls}: {urls: string[]}) => {
// sorted alphabetically & links with titles first
externalLinks.sort((a, b) => {
if (a.title !== '' && b.title !== '') {
const hasTitle = (x: ExternalLink): boolean => {
return x.title !== x.ref && x.title !== '';
};
if (hasTitle(a) && hasTitle(b) && a.title !== b.title) {
return a.title > b.title ? 1 : -1;
} else if (a.title === '') {
} else if (hasTitle(b) && !hasTitle(a)) {
return 1;
} else if (b.title === '') {
} else if (hasTitle(a) && !hasTitle(b)) {
return -1;
}
return a.ref > b.ref ? 1 : -1;
});
return externalLinks;
};
export const ApplicationURLs = ({urls}: {urls: string[]}) => {
const externalLinks: ExternalLink[] = ExternalLinks(urls);
return (
((externalLinks || []).length > 0 && (
<div className='applications-list__external-links-icon-container'>

View File

@@ -64,7 +64,7 @@ function stringHashCode(str: string) {
// ansi color for pod name
function podColor(podName: string) {
return colors[stringHashCode(podName) % colors.length];
return colors[Math.abs(stringHashCode(podName) % colors.length)];
}
// https://2ality.com/2012/09/empty-regexp.html

View File

@@ -325,7 +325,12 @@ export const deletePodAction = async (pod: appModels.Pod, appContext: AppContext
};
export const deletePopup = async (ctx: ContextApis, resource: ResourceTreeNode, application: appModels.Application, appChanged?: BehaviorSubject<appModels.Application>) => {
const isManaged = !!resource.status;
function isTopLevelResource(res: ResourceTreeNode, app: appModels.Application): boolean {
const uniqRes = `/${res.namespace}/${res.group}/${res.kind}/${res.name}`;
return app.status.resources.some(resStatus => `/${resStatus.namespace}/${resStatus.group}/${resStatus.kind}/${resStatus.name}` === uniqRes);
}
const isManaged = isTopLevelResource(resource, application);
const deleteOptions = {
option: 'foreground'
};

View File

@@ -4591,10 +4591,10 @@ fs-minipass@^2.0.0:
dependencies:
minipass "^3.0.0"
fs-monkey@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3"
integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==
fs-monkey@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.5.tgz#fe450175f0db0d7ea758102e1d84096acb925788"
integrity sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==
fs.realpath@^1.0.0:
version "1.0.0"
@@ -6259,12 +6259,12 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
memfs@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.1.tgz#b78092f466a0dce054d63d39275b24c71d3f1305"
integrity sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw==
memfs@^3.4.3:
version "3.6.0"
resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6"
integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ==
dependencies:
fs-monkey "1.0.3"
fs-monkey "^1.0.4"
merge-descriptors@1.0.1:
version "1.0.1"
@@ -9652,12 +9652,12 @@ webpack-cli@^4.9.2:
webpack-merge "^5.7.3"
webpack-dev-middleware@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.1.tgz#aa079a8dedd7e58bfeab358a9af7dab304cee57f"
integrity sha512-81EujCKkyles2wphtdrnPg/QqegC/AtqNH//mQkBYSMqwFVCQrxM6ktB2O/SPlZy7LqeEfTbV3cZARGQz6umhg==
version "5.3.4"
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz#eb7b39281cbce10e104eb2b8bf2b63fce49a3517"
integrity sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==
dependencies:
colorette "^2.0.10"
memfs "^3.4.1"
memfs "^3.4.3"
mime-types "^2.1.31"
range-parser "^1.2.1"
schema-utils "^4.0.0"

View File

@@ -694,8 +694,7 @@ func GetAppProject(app *argoappv1.Application, projLister applicationsv1.AppProj
return nil, err
}
if !proj.IsAppNamespacePermitted(app, ns) {
return nil, fmt.Errorf("application '%s' in namespace '%s' is not allowed to use project '%s'",
app.Name, app.Namespace, proj.Name)
return nil, argoappv1.NewErrApplicationNotAllowedToUseProject(app.Name, app.Namespace, proj.Name)
}
return proj, nil
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/argo-cd/v2/util/argo/managedfields"
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
"github.com/argoproj/gitops-engine/pkg/diff"
@@ -34,7 +35,7 @@ func NewDiffConfigBuilder() *DiffConfigBuilder {
}
// WithDiffSettings will set the diff settings in the builder.
func (b *DiffConfigBuilder) WithDiffSettings(id []v1alpha1.ResourceIgnoreDifferences, o map[string]v1alpha1.ResourceOverride, ignoreAggregatedRoles bool) *DiffConfigBuilder {
func (b *DiffConfigBuilder) WithDiffSettings(id []v1alpha1.ResourceIgnoreDifferences, o map[string]v1alpha1.ResourceOverride, ignoreAggregatedRoles bool, ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts) *DiffConfigBuilder {
ignores := id
if ignores == nil {
ignores = []v1alpha1.ResourceIgnoreDifferences{}
@@ -47,6 +48,7 @@ func (b *DiffConfigBuilder) WithDiffSettings(id []v1alpha1.ResourceIgnoreDiffere
}
b.diffConfig.overrides = overrides
b.diffConfig.ignoreAggregatedRoles = ignoreAggregatedRoles
b.diffConfig.ignoreNormalizerOpts = ignoreNormalizerOpts
return b
}
@@ -161,6 +163,8 @@ type DiffConfig interface {
ServerSideDiff() bool
ServerSideDryRunner() diff.ServerSideDryRunner
IgnoreMutationWebhook() bool
IgnoreNormalizerOpts() normalizers.IgnoreNormalizerOpts
}
// diffConfig defines the configurations used while applying diffs.
@@ -180,6 +184,7 @@ type diffConfig struct {
serverSideDiff bool
serverSideDryRunner diff.ServerSideDryRunner
ignoreMutationWebhook bool
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
}
func (c *diffConfig) Ignores() []v1alpha1.ResourceIgnoreDifferences {
@@ -227,6 +232,9 @@ func (c *diffConfig) ServerSideDiff() bool {
func (c *diffConfig) IgnoreMutationWebhook() bool {
return c.ignoreMutationWebhook
}
func (c *diffConfig) IgnoreNormalizerOpts() normalizers.IgnoreNormalizerOpts {
return c.ignoreNormalizerOpts
}
// Validate will check the current state of this diffConfig and return
// error if it finds any required configuration missing.
@@ -279,7 +287,7 @@ func StateDiffs(lives, configs []*unstructured.Unstructured, diffConfig DiffConf
return nil, fmt.Errorf("failed to perform pre-diff normalization: %w", err)
}
diffNormalizer, err := newDiffNormalizer(diffConfig.Ignores(), diffConfig.Overrides())
diffNormalizer, err := newDiffNormalizer(diffConfig.Ignores(), diffConfig.Overrides(), diffConfig.IgnoreNormalizerOpts())
if err != nil {
return nil, fmt.Errorf("failed to create diff normalizer: %w", err)
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
testutil "github.com/argoproj/argo-cd/v2/test"
argo "github.com/argoproj/argo-cd/v2/util/argo/diff"
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
"github.com/argoproj/argo-cd/v2/util/argo/testdata"
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
)
@@ -40,7 +41,7 @@ func TestStateDiff(t *testing.T) {
diffConfig := func(t *testing.T, params *diffConfigParams) argo.DiffConfig {
t.Helper()
diffConfig, err := argo.NewDiffConfigBuilder().
WithDiffSettings(params.ignores, params.overrides, params.ignoreRoles).
WithDiffSettings(params.ignores, params.overrides, params.ignoreRoles, normalizers.IgnoreNormalizerOpts{}).
WithTracking(params.label, params.trackingMethod).
WithNoCache().
Build()
@@ -185,7 +186,7 @@ func TestDiffConfigBuilder(t *testing.T) {
// when
diffConfig, err := argo.NewDiffConfigBuilder().
WithDiffSettings(f.ignores, f.overrides, f.ignoreRoles).
WithDiffSettings(f.ignores, f.overrides, f.ignoreRoles, normalizers.IgnoreNormalizerOpts{}).
WithTracking(f.label, f.trackingMethod).
WithNoCache().
Build()
@@ -209,7 +210,7 @@ func TestDiffConfigBuilder(t *testing.T) {
// when
diffConfig, err := argo.NewDiffConfigBuilder().
WithDiffSettings(nil, nil, f.ignoreRoles).
WithDiffSettings(nil, nil, f.ignoreRoles, normalizers.IgnoreNormalizerOpts{}).
WithTracking(f.label, f.trackingMethod).
WithNoCache().
Build()
@@ -231,7 +232,7 @@ func TestDiffConfigBuilder(t *testing.T) {
// when
diffConfig, err := argo.NewDiffConfigBuilder().
WithDiffSettings(f.ignores, f.overrides, f.ignoreRoles).
WithDiffSettings(f.ignores, f.overrides, f.ignoreRoles, normalizers.IgnoreNormalizerOpts{}).
WithTracking(f.label, f.trackingMethod).
WithCache(&appstatecache.Cache{}, "").
Build()
@@ -246,7 +247,7 @@ func TestDiffConfigBuilder(t *testing.T) {
// when
diffConfig, err := argo.NewDiffConfigBuilder().
WithDiffSettings(f.ignores, f.overrides, f.ignoreRoles).
WithDiffSettings(f.ignores, f.overrides, f.ignoreRoles, normalizers.IgnoreNormalizerOpts{}).
WithTracking(f.label, f.trackingMethod).
WithCache(nil, f.appName).
Build()

View File

@@ -15,7 +15,7 @@ func Normalize(lives, configs []*unstructured.Unstructured, diffConfig DiffConfi
if err != nil {
return nil, err
}
diffNormalizer, err := newDiffNormalizer(diffConfig.Ignores(), diffConfig.Overrides())
diffNormalizer, err := newDiffNormalizer(diffConfig.Ignores(), diffConfig.Overrides(), diffConfig.IgnoreNormalizerOpts())
if err != nil {
return nil, err
}
@@ -40,8 +40,8 @@ func Normalize(lives, configs []*unstructured.Unstructured, diffConfig DiffConfi
}
// newDiffNormalizer creates normalizer that uses Argo CD and application settings to normalize the resource prior to diffing.
func newDiffNormalizer(ignore []v1alpha1.ResourceIgnoreDifferences, overrides map[string]v1alpha1.ResourceOverride) (diff.Normalizer, error) {
ignoreNormalizer, err := normalizers.NewIgnoreNormalizer(ignore, overrides)
func newDiffNormalizer(ignore []v1alpha1.ResourceIgnoreDifferences, overrides map[string]v1alpha1.ResourceOverride, opts normalizers.IgnoreNormalizerOpts) (diff.Normalizer, error) {
ignoreNormalizer, err := normalizers.NewIgnoreNormalizer(ignore, overrides, opts)
if err != nil {
return nil, err
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/test"
"github.com/argoproj/argo-cd/v2/util/argo/diff"
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
"github.com/argoproj/argo-cd/v2/util/argo/testdata"
)
@@ -22,7 +23,7 @@ func TestNormalize(t *testing.T) {
setup := func(t *testing.T, ignores []v1alpha1.ResourceIgnoreDifferences) *fixture {
t.Helper()
dc, err := diff.NewDiffConfigBuilder().
WithDiffSettings(ignores, nil, true).
WithDiffSettings(ignores, nil, true, normalizers.IgnoreNormalizerOpts{}).
WithNoCache().
Build()
require.NoError(t, err)

View File

@@ -1,9 +1,11 @@
package normalizers
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/argoproj/gitops-engine/pkg/diff"
jsonpatch "github.com/evanphx/json-patch"
@@ -16,6 +18,11 @@ import (
"github.com/argoproj/argo-cd/v2/util/glob"
)
const (
// DefaultJQExecutionTimeout is the maximum time allowed for a JQ patch to execute
DefaultJQExecutionTimeout = 1 * time.Second
)
type normalizerPatch interface {
GetGroupKind() schema.GroupKind
GetNamespace() string
@@ -57,7 +64,8 @@ func (np *jsonPatchNormalizerPatch) Apply(data []byte) ([]byte, error) {
type jqNormalizerPatch struct {
baseNormalizerPatch
code *gojq.Code
code *gojq.Code
jqExecutionTimeout time.Duration
}
func (np *jqNormalizerPatch) Apply(data []byte) ([]byte, error) {
@@ -67,12 +75,18 @@ func (np *jqNormalizerPatch) Apply(data []byte) ([]byte, error) {
return nil, err
}
iter := np.code.Run(dataJson)
ctx, cancel := context.WithTimeout(context.Background(), np.jqExecutionTimeout)
defer cancel()
iter := np.code.RunWithContext(ctx, dataJson)
first, ok := iter.Next()
if !ok {
return nil, fmt.Errorf("JQ patch did not return any data")
}
if err, ok = first.(error); ok {
if err == context.DeadlineExceeded {
return nil, fmt.Errorf("JQ patch execution timed out (%v)", np.jqExecutionTimeout.String())
}
return nil, fmt.Errorf("JQ patch returned error: %w", err)
}
_, ok = iter.Next()
@@ -91,8 +105,19 @@ type ignoreNormalizer struct {
patches []normalizerPatch
}
type IgnoreNormalizerOpts struct {
JQExecutionTimeout time.Duration
}
func (opts *IgnoreNormalizerOpts) getJQExecutionTimeout() time.Duration {
if opts == nil || opts.JQExecutionTimeout == 0 {
return DefaultJQExecutionTimeout
}
return opts.JQExecutionTimeout
}
// NewIgnoreNormalizer creates diff normalizer which removes ignored fields according to given application spec and resource overrides
func NewIgnoreNormalizer(ignore []v1alpha1.ResourceIgnoreDifferences, overrides map[string]v1alpha1.ResourceOverride) (diff.Normalizer, error) {
func NewIgnoreNormalizer(ignore []v1alpha1.ResourceIgnoreDifferences, overrides map[string]v1alpha1.ResourceOverride, opts IgnoreNormalizerOpts) (diff.Normalizer, error) {
for key, override := range overrides {
group, kind, err := getGroupKindForOverrideKey(key)
if err != nil {
@@ -147,7 +172,8 @@ func NewIgnoreNormalizer(ignore []v1alpha1.ResourceIgnoreDifferences, overrides
name: ignore[i].Name,
namespace: ignore[i].Namespace,
},
code: jqDeletionCode,
code: jqDeletionCode,
jqExecutionTimeout: opts.getJQExecutionTimeout(),
})
}
}

View File

@@ -19,7 +19,7 @@ func TestNormalizeObjectWithMatchedGroupKind(t *testing.T) {
Group: "apps",
Kind: "Deployment",
JSONPointers: []string{"/not-matching-path", "/spec/template/spec/containers"},
}}, make(map[string]v1alpha1.ResourceOverride))
}}, make(map[string]v1alpha1.ResourceOverride), IgnoreNormalizerOpts{})
assert.Nil(t, err)
@@ -44,7 +44,7 @@ func TestNormalizeNoMatchedGroupKinds(t *testing.T) {
Group: "",
Kind: "Service",
JSONPointers: []string{"/spec"},
}}, make(map[string]v1alpha1.ResourceOverride))
}}, make(map[string]v1alpha1.ResourceOverride), IgnoreNormalizerOpts{})
assert.Nil(t, err)
@@ -63,7 +63,7 @@ func TestNormalizeMatchedResourceOverrides(t *testing.T) {
"apps/Deployment": {
IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{JSONPointers: []string{"/spec/template/spec/containers"}},
},
})
}, IgnoreNormalizerOpts{})
assert.Nil(t, err)
@@ -118,7 +118,7 @@ func TestNormalizeMissingJsonPointer(t *testing.T) {
"apiextensions.k8s.io/CustomResourceDefinition": {
IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{JSONPointers: []string{"/spec/additionalPrinterColumns/0/priority"}},
},
})
}, IgnoreNormalizerOpts{})
assert.NoError(t, err)
deployment := test.NewDeployment()
@@ -139,7 +139,7 @@ func TestNormalizeGlobMatch(t *testing.T) {
"*/*": {
IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{JSONPointers: []string{"/spec/template/spec/containers"}},
},
})
}, IgnoreNormalizerOpts{})
assert.Nil(t, err)
@@ -161,7 +161,7 @@ func TestNormalizeJQPathExpression(t *testing.T) {
Group: "apps",
Kind: "Deployment",
JQPathExpressions: []string{".spec.template.spec.initContainers[] | select(.name == \"init-container-0\")"},
}}, make(map[string]v1alpha1.ResourceOverride))
}}, make(map[string]v1alpha1.ResourceOverride), IgnoreNormalizerOpts{})
assert.Nil(t, err)
@@ -197,7 +197,7 @@ func TestNormalizeIllegalJQPathExpression(t *testing.T) {
Kind: "Deployment",
JQPathExpressions: []string{".spec.template.spec.containers[] | select(.name == \"missing-quote)"},
// JSONPointers: []string{"no-starting-slash"},
}}, make(map[string]v1alpha1.ResourceOverride))
}}, make(map[string]v1alpha1.ResourceOverride), IgnoreNormalizerOpts{})
assert.Error(t, err)
}
@@ -207,7 +207,7 @@ func TestNormalizeJQPathExpressionWithError(t *testing.T) {
Group: "apps",
Kind: "Deployment",
JQPathExpressions: []string{".spec.fakeField.foo[]"},
}}, make(map[string]v1alpha1.ResourceOverride))
}}, make(map[string]v1alpha1.ResourceOverride), IgnoreNormalizerOpts{})
assert.Nil(t, err)
@@ -230,7 +230,7 @@ func TestNormalizeExpectedErrorAreSilenced(t *testing.T) {
JSONPointers: []string{"/invalid", "/invalid/json/path"},
},
},
})
}, IgnoreNormalizerOpts{})
assert.Nil(t, err)
ignoreNormalizer := normalizer.(*ignoreNormalizer)
@@ -254,12 +254,34 @@ func TestNormalizeExpectedErrorAreSilenced(t *testing.T) {
}
func TestJqPathExpressionFailWithTimeout(t *testing.T) {
normalizer, err := NewIgnoreNormalizer([]v1alpha1.ResourceIgnoreDifferences{}, map[string]v1alpha1.ResourceOverride{
"*/*": {
IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{
JQPathExpressions: []string{"until(true==false; [.] + [1])"},
},
},
}, IgnoreNormalizerOpts{})
assert.Nil(t, err)
ignoreNormalizer := normalizer.(*ignoreNormalizer)
assert.Len(t, ignoreNormalizer.patches, 1)
jqPatch := ignoreNormalizer.patches[0]
deployment := test.NewDeployment()
deploymentData, err := json.Marshal(deployment)
assert.Nil(t, err)
_, err = jqPatch.Apply(deploymentData)
assert.ErrorContains(t, err, "JQ patch execution timed out")
}
func TestJQPathExpressionReturnsHelpfulError(t *testing.T) {
normalizer, err := NewIgnoreNormalizer([]v1alpha1.ResourceIgnoreDifferences{{
Kind: "ConfigMap",
// This is a really wild expression, but it does trigger the desired error.
JQPathExpressions: []string{`.nothing) | .data["config.yaml"] |= (fromjson | del(.auth) | tojson`},
}}, nil)
}}, nil, IgnoreNormalizerOpts{})
assert.NoError(t, err)

View File

@@ -8,7 +8,6 @@ import (
"encoding/json"
"errors"
"fmt"
executil "github.com/argoproj/argo-cd/v2/util/exec"
"io"
"net/http"
"net/url"
@@ -19,6 +18,8 @@ import (
"strings"
"time"
executil "github.com/argoproj/argo-cd/v2/util/exec"
"github.com/argoproj/pkg/sync"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
@@ -34,6 +35,8 @@ import (
var (
globalLock = sync.NewKeyLock()
indexLock = sync.NewKeyLock()
OCINotEnabledErr = errors.New("could not perform the action when oci is not enabled")
)
type Creds struct {
@@ -53,7 +56,7 @@ type indexCache interface {
type Client interface {
CleanChartCache(chart string, version string) error
ExtractChart(chart string, version string, passCredentials bool, manifestMaxExtractedSize int64, disableManifestMaxExtractedSize bool) (string, argoio.Closer, error)
GetIndex(noCache bool) (*Index, error)
GetIndex(noCache bool, maxIndexSize int64) (*Index, error)
GetTags(chart string, noCache bool) (*TagsList, error)
TestHelmOCI() (bool, error)
}
@@ -227,7 +230,7 @@ func (c *nativeHelmChart) ExtractChart(chart string, version string, passCredent
}), nil
}
func (c *nativeHelmChart) GetIndex(noCache bool) (*Index, error) {
func (c *nativeHelmChart) GetIndex(noCache bool, maxIndexSize int64) (*Index, error) {
indexLock.Lock(c.repoURL)
defer indexLock.Unlock(c.repoURL)
@@ -241,7 +244,7 @@ func (c *nativeHelmChart) GetIndex(noCache bool) (*Index, error) {
if len(data) == 0 {
start := time.Now()
var err error
data, err = c.loadRepoIndex()
data, err = c.loadRepoIndex(maxIndexSize)
if err != nil {
return nil, err
}
@@ -294,7 +297,7 @@ func (c *nativeHelmChart) TestHelmOCI() (bool, error) {
return true, nil
}
func (c *nativeHelmChart) loadRepoIndex() ([]byte, error) {
func (c *nativeHelmChart) loadRepoIndex(maxIndexSize int64) ([]byte, error) {
indexURL, err := getIndexURL(c.repoURL)
if err != nil {
return nil, err
@@ -329,7 +332,7 @@ func (c *nativeHelmChart) loadRepoIndex() ([]byte, error) {
if resp.StatusCode != http.StatusOK {
return nil, errors.New("failed to get index: " + resp.Status)
}
return io.ReadAll(resp.Body)
return io.ReadAll(io.LimitReader(resp.Body, maxIndexSize))
}
func newTLSConfig(creds Creds) (*tls.Config, error) {
@@ -401,6 +404,10 @@ func getIndexURL(rawURL string) (string, error) {
}
func (c *nativeHelmChart) GetTags(chart string, noCache bool) (*TagsList, error) {
if !c.enableOci {
return nil, OCINotEnabledErr
}
tagsURL := strings.Replace(fmt.Sprintf("%s/%s", c.repoURL, chart), "https://", "", 1)
indexLock.Lock(tagsURL)
defer indexLock.Unlock(tagsURL)
@@ -428,10 +435,12 @@ func (c *nativeHelmChart) GetTags(chart string, noCache bool) (*TagsList, error)
TLSClientConfig: tlsConf,
DisableKeepAlives: true,
}}
repoHost, _, _ := strings.Cut(tagsURL, "/")
repo.Client = &auth.Client{
Client: client,
Cache: nil,
Credential: auth.StaticCredential(c.repoURL, auth.Credential{
Credential: auth.StaticCredential(repoHost, auth.Credential{
Username: c.creds.Username,
Password: c.creds.Password,
}),

View File

@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"math"
"net/url"
"os"
"strings"
"testing"
@@ -36,12 +37,12 @@ func (f *fakeIndexCache) GetHelmIndex(_ string, indexData *[]byte) error {
func TestIndex(t *testing.T) {
t.Run("Invalid", func(t *testing.T) {
client := NewClient("", Creds{}, false, "")
_, err := client.GetIndex(false)
_, err := client.GetIndex(false, 10000)
assert.Error(t, err)
})
t.Run("Stable", func(t *testing.T) {
client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false, "")
index, err := client.GetIndex(false)
index, err := client.GetIndex(false, 10000)
assert.NoError(t, err)
assert.NotNil(t, index)
})
@@ -50,7 +51,7 @@ func TestIndex(t *testing.T) {
Username: "my-password",
Password: "my-username",
}, false, "")
index, err := client.GetIndex(false)
index, err := client.GetIndex(false, 10000)
assert.NoError(t, err)
assert.NotNil(t, index)
})
@@ -62,12 +63,18 @@ func TestIndex(t *testing.T) {
require.NoError(t, err)
client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false, "", WithIndexCache(&fakeIndexCache{data: data.Bytes()}))
index, err := client.GetIndex(false)
index, err := client.GetIndex(false, 10000)
assert.NoError(t, err)
assert.Equal(t, fakeIndex, *index)
})
t.Run("Limited", func(t *testing.T) {
client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false, "")
_, err := client.GetIndex(false, 100)
assert.ErrorContains(t, err, "unexpected end of stream")
})
}
func Test_nativeHelmChart_ExtractChart(t *testing.T) {
@@ -159,41 +166,129 @@ func TestGetIndexURL(t *testing.T) {
}
func TestGetTagsFromUrl(t *testing.T) {
t.Run("should return tags correctly while following the link header", func(t *testing.T) {
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Logf("called %s", r.URL.Path)
responseTags := TagsList{}
w.Header().Set("Content-Type", "application/json")
if !strings.Contains(r.URL.String(), "token") {
w.Header().Set("Link", fmt.Sprintf("<https://%s%s?token=next-token>; rel=next", r.Host, r.URL.Path))
responseTags.Tags = []string{"first"}
} else {
responseTags.Tags = []string{
"second",
"2.8.0",
"2.8.0-prerelease",
"2.8.0_build",
"2.8.0-prerelease_build",
"2.8.0-prerelease.1_build.1234",
}
}
w.WriteHeader(http.StatusOK)
err := json.NewEncoder(w).Encode(responseTags)
if err != nil {
t.Fatal(err)
}
}))
client := NewClient(server.URL, Creds{InsecureSkipVerify: true}, true, "")
tags, err := client.GetTags("mychart", true)
assert.NoError(t, err)
assert.ElementsMatch(t, tags.Tags, []string{
"first",
"second",
"2.8.0",
"2.8.0-prerelease",
"2.8.0+build",
"2.8.0-prerelease+build",
"2.8.0-prerelease.1+build.1234",
})
})
t.Run("should return an error not when oci is not enabled", func(t *testing.T) {
client := NewClient("example.com", Creds{}, false, "")
_, err := client.GetTags("my-chart", true)
assert.ErrorIs(t, OCINotEnabledErr, err)
})
}
func TestGetTagsFromURLPrivateRepoAuthentication(t *testing.T) {
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Logf("called %s", r.URL.Path)
responseTags := TagsList{}
w.Header().Set("Content-Type", "application/json")
if !strings.Contains(r.URL.String(), "token") {
w.Header().Set("Link", fmt.Sprintf("<https://%s%s?token=next-token>; rel=next", r.Host, r.URL.Path))
responseTags.Tags = []string{"first"}
} else {
responseTags.Tags = []string{
"second",
authorization := r.Header.Get("Authorization")
if authorization == "" {
w.Header().Set("WWW-Authenticate", `Basic realm="helm repo to get tags"`)
w.WriteHeader(http.StatusUnauthorized)
return
}
t.Logf("authorization received %s", authorization)
responseTags := TagsList{
Tags: []string{
"2.8.0",
"2.8.0-prerelease",
"2.8.0_build",
"2.8.0-prerelease_build",
"2.8.0-prerelease.1_build.1234",
}
},
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
err := json.NewEncoder(w).Encode(responseTags)
if err != nil {
t.Fatal(err)
}
}))
t.Cleanup(server.Close)
client := NewClient(server.URL, Creds{InsecureSkipVerify: true}, true, "")
tags, err := client.GetTags("mychart", true)
serverURL, err := url.Parse(server.URL)
assert.NoError(t, err)
assert.ElementsMatch(t, tags.Tags, []string{
"first",
"second",
"2.8.0",
"2.8.0-prerelease",
"2.8.0+build",
"2.8.0-prerelease+build",
"2.8.0-prerelease.1+build.1234",
})
testCases := []struct {
name string
repoURL string
}{
{
name: "should login correctly when the repo path is in the server root with http scheme",
repoURL: server.URL,
},
{
name: "should login correctly when the repo path is not in the server root with http scheme",
repoURL: fmt.Sprintf("%s/my-repo", server.URL),
},
{
name: "should login correctly when the repo path is in the server root without http scheme",
repoURL: serverURL.Host,
},
{
name: "should login correctly when the repo path is not in the server root without http scheme",
repoURL: fmt.Sprintf("%s/my-repo", serverURL.Host),
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
client := NewClient(testCase.repoURL, Creds{
InsecureSkipVerify: true,
Username: "my-username",
Password: "my-password",
}, true, "")
tags, err := client.GetTags("mychart", true)
assert.NoError(t, err)
assert.ElementsMatch(t, tags.Tags, []string{
"2.8.0",
"2.8.0-prerelease",
"2.8.0+build",
"2.8.0-prerelease+build",
"2.8.0-prerelease.1+build.1234",
})
})
}
}

View File

@@ -59,7 +59,7 @@ func (_m *Client) ExtractChart(chart string, version string, passCredentials boo
}
// GetIndex provides a mock function with given fields: noCache
func (_m *Client) GetIndex(noCache bool) (*helm.Index, error) {
func (_m *Client) GetIndex(noCache bool, maxIndexSize int64) (*helm.Index, error) {
ret := _m.Called(noCache)
var r0 *helm.Index

View File

@@ -107,11 +107,14 @@ func (svc *argoCDService) GetAppDetails(ctx context.Context, appSource *v1alpha1
var has *shared.CustomHelmAppSpec
if appDetail.Helm != nil {
has = &shared.CustomHelmAppSpec{
Name: appDetail.Helm.Name,
ValueFiles: appDetail.Helm.ValueFiles,
Parameters: appDetail.Helm.Parameters,
Values: appDetail.Helm.Values,
FileParameters: appDetail.Helm.FileParameters,
HelmAppSpec: apiclient.HelmAppSpec{
Name: appDetail.Helm.Name,
ValueFiles: appDetail.Helm.ValueFiles,
Parameters: appDetail.Helm.Parameters,
Values: appDetail.Helm.Values,
FileParameters: appDetail.Helm.FileParameters,
},
HelmParameterOverrides: appSource.Helm.Parameters,
}
}
return &shared.AppDetail{

View File

@@ -1,6 +1,7 @@
package shared
import (
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"time"
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
@@ -28,24 +29,32 @@ type AppDetail struct {
Directory *apiclient.DirectoryAppSpec
}
type CustomHelmAppSpec apiclient.HelmAppSpec
type CustomHelmAppSpec struct {
HelmAppSpec apiclient.HelmAppSpec
HelmParameterOverrides []v1alpha1.HelmParameter
}
func (has CustomHelmAppSpec) GetParameterValueByName(Name string) string {
var value string
for i := range has.Parameters {
if has.Parameters[i].Name == Name {
value = has.Parameters[i].Value
break
// Check in overrides first
for i := range has.HelmParameterOverrides {
if has.HelmParameterOverrides[i].Name == Name {
return has.HelmParameterOverrides[i].Value
}
}
return value
for i := range has.HelmAppSpec.Parameters {
if has.HelmAppSpec.Parameters[i].Name == Name {
return has.HelmAppSpec.Parameters[i].Value
}
}
return ""
}
func (has CustomHelmAppSpec) GetFileParameterPathByName(Name string) string {
var path string
for i := range has.FileParameters {
if has.FileParameters[i].Name == Name {
path = has.FileParameters[i].Path
for i := range has.HelmAppSpec.FileParameters {
if has.HelmAppSpec.FileParameters[i].Name == Name {
path = has.HelmAppSpec.FileParameters[i].Path
break
}
}

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