mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-03-29 12:58:48 +02:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed35bc3204 | ||
|
|
beb9860db5 | ||
|
|
7a7dd4e5a5 | ||
|
|
12c8d42f4f | ||
|
|
c19d63446d | ||
|
|
a46152e850 | ||
|
|
28b618775d | ||
|
|
1b7cfc0c06 | ||
|
|
0262c8ff97 |
@@ -267,7 +267,7 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp
|
||||
Revision: revision,
|
||||
SyncedRevision: syncedRevision,
|
||||
NoRevisionCache: noRevisionCache,
|
||||
Paths: path.GetAppRefreshPaths(app),
|
||||
Paths: path.GetSourceRefreshPaths(app, source),
|
||||
AppLabelKey: appLabelKey,
|
||||
AppName: app.InstanceName(m.namespace),
|
||||
Namespace: appNamespace,
|
||||
|
||||
@@ -18,6 +18,10 @@ IMAGE_TAG="${IMAGE_TAG:-}"
|
||||
# if the tag has not been declared, and we are on a release branch, use the VERSION file.
|
||||
if [ "$IMAGE_TAG" = "" ]; then
|
||||
branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
# In GitHub Actions PRs, HEAD is detached; use GITHUB_BASE_REF (the target branch) instead
|
||||
if [ "$branch" = "HEAD" ] && [ -n "${GITHUB_BASE_REF:-}" ]; then
|
||||
branch="$GITHUB_BASE_REF"
|
||||
fi
|
||||
if [[ $branch = release-* ]]; then
|
||||
pwd
|
||||
IMAGE_TAG=v$(cat $SRCROOT/VERSION)
|
||||
|
||||
@@ -12,4 +12,4 @@ resources:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v3.1.11
|
||||
newTag: v3.1.13
|
||||
|
||||
@@ -5,7 +5,7 @@ kind: Kustomization
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v3.1.11
|
||||
newTag: v3.1.13
|
||||
resources:
|
||||
- ./application-controller
|
||||
- ./dex
|
||||
|
||||
12
manifests/core-install-with-hydrator.yaml
generated
12
manifests/core-install-with-hydrator.yaml
generated
@@ -24705,7 +24705,7 @@ spec:
|
||||
key: applicationsetcontroller.status.max.resources.count
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -24831,7 +24831,7 @@ spec:
|
||||
key: log.format.timestamp
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -24959,7 +24959,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -25250,7 +25250,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -25302,7 +25302,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -25644,7 +25644,7 @@ spec:
|
||||
optional: true
|
||||
- name: KUBECACHEDIR
|
||||
value: /tmp/kubecache
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
10
manifests/core-install.yaml
generated
10
manifests/core-install.yaml
generated
@@ -24673,7 +24673,7 @@ spec:
|
||||
key: applicationsetcontroller.status.max.resources.count
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -24793,7 +24793,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -25084,7 +25084,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -25136,7 +25136,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -25478,7 +25478,7 @@ spec:
|
||||
optional: true
|
||||
- name: KUBECACHEDIR
|
||||
value: /tmp/kubecache
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -12,4 +12,4 @@ resources:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v3.1.11
|
||||
newTag: v3.1.13
|
||||
|
||||
@@ -12,7 +12,7 @@ patches:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v3.1.11
|
||||
newTag: v3.1.13
|
||||
resources:
|
||||
- ../../base/application-controller
|
||||
- ../../base/applicationset-controller
|
||||
|
||||
18
manifests/ha/install-with-hydrator.yaml
generated
18
manifests/ha/install-with-hydrator.yaml
generated
@@ -26071,7 +26071,7 @@ spec:
|
||||
key: applicationsetcontroller.status.max.resources.count
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -26197,7 +26197,7 @@ spec:
|
||||
key: log.format.timestamp
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -26348,7 +26348,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -26444,7 +26444,7 @@ spec:
|
||||
key: notificationscontroller.repo.server.plaintext
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -26568,7 +26568,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -26885,7 +26885,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -26937,7 +26937,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -27311,7 +27311,7 @@ spec:
|
||||
key: server.sync.replace.allowed
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -27689,7 +27689,7 @@ spec:
|
||||
optional: true
|
||||
- name: KUBECACHEDIR
|
||||
value: /tmp/kubecache
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
16
manifests/ha/install.yaml
generated
16
manifests/ha/install.yaml
generated
@@ -26041,7 +26041,7 @@ spec:
|
||||
key: applicationsetcontroller.status.max.resources.count
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -26184,7 +26184,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -26280,7 +26280,7 @@ spec:
|
||||
key: notificationscontroller.repo.server.plaintext
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -26404,7 +26404,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -26721,7 +26721,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -26773,7 +26773,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -27147,7 +27147,7 @@ spec:
|
||||
key: server.sync.replace.allowed
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -27525,7 +27525,7 @@ spec:
|
||||
optional: true
|
||||
- name: KUBECACHEDIR
|
||||
value: /tmp/kubecache
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
18
manifests/ha/namespace-install-with-hydrator.yaml
generated
18
manifests/ha/namespace-install-with-hydrator.yaml
generated
@@ -1874,7 +1874,7 @@ spec:
|
||||
key: applicationsetcontroller.status.max.resources.count
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -2000,7 +2000,7 @@ spec:
|
||||
key: log.format.timestamp
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -2151,7 +2151,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -2247,7 +2247,7 @@ spec:
|
||||
key: notificationscontroller.repo.server.plaintext
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -2371,7 +2371,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -2688,7 +2688,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -2740,7 +2740,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -3114,7 +3114,7 @@ spec:
|
||||
key: server.sync.replace.allowed
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -3492,7 +3492,7 @@ spec:
|
||||
optional: true
|
||||
- name: KUBECACHEDIR
|
||||
value: /tmp/kubecache
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
16
manifests/ha/namespace-install.yaml
generated
16
manifests/ha/namespace-install.yaml
generated
@@ -1844,7 +1844,7 @@ spec:
|
||||
key: applicationsetcontroller.status.max.resources.count
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -1987,7 +1987,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -2083,7 +2083,7 @@ spec:
|
||||
key: notificationscontroller.repo.server.plaintext
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -2207,7 +2207,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -2524,7 +2524,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -2576,7 +2576,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -2950,7 +2950,7 @@ spec:
|
||||
key: server.sync.replace.allowed
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -3328,7 +3328,7 @@ spec:
|
||||
optional: true
|
||||
- name: KUBECACHEDIR
|
||||
value: /tmp/kubecache
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
18
manifests/install-with-hydrator.yaml
generated
18
manifests/install-with-hydrator.yaml
generated
@@ -25165,7 +25165,7 @@ spec:
|
||||
key: applicationsetcontroller.status.max.resources.count
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -25291,7 +25291,7 @@ spec:
|
||||
key: log.format.timestamp
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -25442,7 +25442,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -25538,7 +25538,7 @@ spec:
|
||||
key: notificationscontroller.repo.server.plaintext
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -25640,7 +25640,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -25931,7 +25931,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -25983,7 +25983,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -26355,7 +26355,7 @@ spec:
|
||||
key: server.sync.replace.allowed
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -26733,7 +26733,7 @@ spec:
|
||||
optional: true
|
||||
- name: KUBECACHEDIR
|
||||
value: /tmp/kubecache
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
16
manifests/install.yaml
generated
16
manifests/install.yaml
generated
@@ -25133,7 +25133,7 @@ spec:
|
||||
key: applicationsetcontroller.status.max.resources.count
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -25276,7 +25276,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -25372,7 +25372,7 @@ spec:
|
||||
key: notificationscontroller.repo.server.plaintext
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -25474,7 +25474,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -25765,7 +25765,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -25817,7 +25817,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -26189,7 +26189,7 @@ spec:
|
||||
key: server.sync.replace.allowed
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -26567,7 +26567,7 @@ spec:
|
||||
optional: true
|
||||
- name: KUBECACHEDIR
|
||||
value: /tmp/kubecache
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
18
manifests/namespace-install-with-hydrator.yaml
generated
18
manifests/namespace-install-with-hydrator.yaml
generated
@@ -968,7 +968,7 @@ spec:
|
||||
key: applicationsetcontroller.status.max.resources.count
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -1094,7 +1094,7 @@ spec:
|
||||
key: log.format.timestamp
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -1245,7 +1245,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -1341,7 +1341,7 @@ spec:
|
||||
key: notificationscontroller.repo.server.plaintext
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -1443,7 +1443,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -1734,7 +1734,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -1786,7 +1786,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -2158,7 +2158,7 @@ spec:
|
||||
key: server.sync.replace.allowed
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -2536,7 +2536,7 @@ spec:
|
||||
optional: true
|
||||
- name: KUBECACHEDIR
|
||||
value: /tmp/kubecache
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
16
manifests/namespace-install.yaml
generated
16
manifests/namespace-install.yaml
generated
@@ -936,7 +936,7 @@ spec:
|
||||
key: applicationsetcontroller.status.max.resources.count
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -1079,7 +1079,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -1175,7 +1175,7 @@ spec:
|
||||
key: notificationscontroller.repo.server.plaintext
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -1277,7 +1277,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -1568,7 +1568,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -1620,7 +1620,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -1992,7 +1992,7 @@ spec:
|
||||
key: server.sync.replace.allowed
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -2370,7 +2370,7 @@ spec:
|
||||
optional: true
|
||||
- name: KUBECACHEDIR
|
||||
value: /tmp/kubecache
|
||||
image: quay.io/argoproj/argocd:v3.1.11
|
||||
image: quay.io/argoproj/argocd:v3.1.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -942,6 +942,8 @@ func (server *ArgoCDServer) newGRPCServer(prometheusRegistry *prometheus.Registr
|
||||
// NOTE: notice we do not configure the gRPC server here with TLS (e.g. grpc.Creds(creds))
|
||||
// This is because TLS handshaking occurs in cmux handling
|
||||
sOpts = append(sOpts, grpc.ChainStreamInterceptor(
|
||||
// for mitigation of grpc-go CVE-2026-33186, see https://github.com/argoproj/argo-cd/issues/26932
|
||||
grpc_util.InvalidMethodNameErrorStreamServerInterceptor(),
|
||||
otelgrpc.StreamServerInterceptor(), //nolint:staticcheck // TODO: ignore SA1019 for depreciation: see https://github.com/argoproj/argo-cd/issues/18258
|
||||
logging.StreamServerInterceptor(grpc_util.InterceptorLogger(server.log)),
|
||||
serverMetrics.StreamServerInterceptor(),
|
||||
@@ -955,6 +957,8 @@ func (server *ArgoCDServer) newGRPCServer(prometheusRegistry *prometheus.Registr
|
||||
recovery.StreamServerInterceptor(recovery.WithRecoveryHandler(grpc_util.LoggerRecoveryHandler(server.log))),
|
||||
))
|
||||
sOpts = append(sOpts, grpc.ChainUnaryInterceptor(
|
||||
// for mitigation of grpc-go CVE-2026-33186, see https://github.com/argoproj/argo-cd/issues/26932
|
||||
grpc_util.InvalidMethodNameErrorUnaryServerInterceptor(),
|
||||
bug21955WorkaroundInterceptor,
|
||||
otelgrpc.UnaryServerInterceptor(), //nolint:staticcheck // TODO: ignore SA1019 for depreciation: see https://github.com/argoproj/argo-cd/issues/18258
|
||||
logging.UnaryServerInterceptor(grpc_util.InterceptorLogger(server.log)),
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -29,6 +30,7 @@ import (
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/common"
|
||||
"github.com/argoproj/argo-cd/v3/pkg/apiclient"
|
||||
"github.com/argoproj/argo-cd/v3/pkg/apiclient/project"
|
||||
"github.com/argoproj/argo-cd/v3/pkg/apiclient/session"
|
||||
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
apps "github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned/fake"
|
||||
@@ -40,6 +42,10 @@ import (
|
||||
"github.com/argoproj/argo-cd/v3/util/cache"
|
||||
appstatecache "github.com/argoproj/argo-cd/v3/util/cache/appstate"
|
||||
"github.com/argoproj/argo-cd/v3/util/oidc"
|
||||
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
grpc_util "github.com/argoproj/argo-cd/v3/util/grpc"
|
||||
"github.com/argoproj/argo-cd/v3/util/rbac"
|
||||
settings_util "github.com/argoproj/argo-cd/v3/util/settings"
|
||||
testutil "github.com/argoproj/argo-cd/v3/util/test"
|
||||
@@ -1710,3 +1716,101 @@ func Test_StaticAssetsDir_no_symlink_traversal(t *testing.T) {
|
||||
resp = w.Result()
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode, "should have been able to access the normal file")
|
||||
}
|
||||
|
||||
// test mitigation for grpc-go CVE-2026-33186, see https://github.com/argoproj/argo-cd/issues/26932
|
||||
func TestGrpcInvalidMethodNameCVEFix(t *testing.T) {
|
||||
timeout := 10 * time.Second
|
||||
listenHost := "localhost"
|
||||
listenPort, err := test.GetFreePort()
|
||||
require.NoError(t, err)
|
||||
serverAddr := fmt.Sprintf("%s:%d", listenHost, listenPort)
|
||||
redis, redisCloser := test.NewInMemoryRedis()
|
||||
defer redisCloser()
|
||||
argoCDOpts := ArgoCDServerOpts{
|
||||
DisableAuth: true,
|
||||
Insecure: true,
|
||||
ListenPort: listenPort,
|
||||
ListenHost: listenHost,
|
||||
Namespace: test.FakeArgoCDNamespace,
|
||||
KubeClientset: fake.NewSimpleClientset(test.NewFakeConfigMap(), test.NewFakeSecret()),
|
||||
AppClientset: apps.NewSimpleClientset(),
|
||||
RepoClientset: &mocks.Clientset{RepoServerServiceClient: &mocks.RepoServerServiceClient{}},
|
||||
RedisClient: redis,
|
||||
}
|
||||
runCtx, runCancel := context.WithTimeout(t.Context(), timeout)
|
||||
defer runCancel()
|
||||
argocd := NewServer(runCtx, argoCDOpts, ApplicationSetOpts{})
|
||||
assert.NotNil(t, argocd)
|
||||
listeners, err := argocd.Listen()
|
||||
require.NoError(t, err)
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
argocd.Init(ctx)
|
||||
|
||||
wg := gosync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
argocd.Run(ctx, listeners)
|
||||
}()
|
||||
|
||||
err = test.WaitForPortListen(serverAddr, timeout)
|
||||
require.NoError(t, err)
|
||||
|
||||
var dialOpts []grpc.DialOption
|
||||
creds := insecure.NewCredentials()
|
||||
conn, err := grpc_util.BlockingDial(ctx, "tcp", serverAddr, creds, dialOpts...)
|
||||
require.NoError(t, err)
|
||||
defer conn.Close()
|
||||
|
||||
projectGetOut := new(v1alpha1.AppProject)
|
||||
projectGetIn := &project.ProjectQuery{Name: "default"}
|
||||
invalidunaryServiceName := "project.ProjectService/Get"
|
||||
invalidStreamingMethodName := "application.ApplicationService/GetManifestsWithFiles"
|
||||
|
||||
streamDesc := &grpc.StreamDesc{
|
||||
StreamName: "dummy_stream",
|
||||
ClientStreams: true,
|
||||
ServerStreams: false,
|
||||
}
|
||||
|
||||
t.Run("unary method with invalid name", func(t *testing.T) {
|
||||
err = conn.Invoke(ctx, invalidunaryServiceName, projectGetIn, projectGetOut)
|
||||
// it should fail with the "malformed method name" error message from interceptor,
|
||||
// but it does not, because unary methods do not seem to be vulnerable because of
|
||||
// the way their handler code is autogenerated: if there are interceptors
|
||||
// it implicitly sanitizes the service name before calling the actual handler,
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("unary method with valid name", func(t *testing.T) {
|
||||
err = conn.Invoke(ctx, "/"+invalidunaryServiceName, projectGetIn, projectGetOut)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("streaming method with invalid name", func(t *testing.T) {
|
||||
stream, err := conn.NewStream(ctx, streamDesc, invalidStreamingMethodName)
|
||||
require.NoError(t, err)
|
||||
err = stream.CloseSend()
|
||||
require.NoError(t, err)
|
||||
var resp any
|
||||
err = stream.RecvMsg(&resp)
|
||||
// ensure we get error method from interceptor
|
||||
require.ErrorContains(t, err, "code = InvalidArgument desc = malformed method name: \""+invalidStreamingMethodName+"\"")
|
||||
})
|
||||
t.Run("streaming method with valid name", func(t *testing.T) {
|
||||
stream, err := conn.NewStream(ctx, streamDesc, "/"+invalidStreamingMethodName)
|
||||
require.NoError(t, err)
|
||||
err = stream.CloseSend()
|
||||
require.NoError(t, err)
|
||||
var resp any
|
||||
err = stream.RecvMsg(&resp)
|
||||
// ensure we get the expected error from the actual logic of the method
|
||||
require.ErrorContains(t, err, "code = Unknown desc = error getting query: failed to receive header: EOF")
|
||||
})
|
||||
argocd.stopCh <- syscall.SIGINT
|
||||
wg.Wait()
|
||||
|
||||
err = argocd.healthCheck(&http.Request{URL: &url.URL{Path: "/healthz", RawQuery: "full=true"}})
|
||||
require.Error(t, err, "API Server is terminating and unable to serve requests.")
|
||||
assert.True(t, argocd.terminateRequested.Load())
|
||||
assert.False(t, argocd.available.Load())
|
||||
}
|
||||
|
||||
@@ -37,15 +37,17 @@ export const ApplicationHydrateOperationState: React.FunctionComponent<Props> =
|
||||
if (hydrateOperationState.finishedAt && hydrateOperationState.phase !== 'Hydrating') {
|
||||
operationAttributes.push({title: 'FINISHED AT', value: <Timestamp date={hydrateOperationState.finishedAt} />});
|
||||
}
|
||||
operationAttributes.push({
|
||||
title: 'DRY REVISION',
|
||||
value: (
|
||||
<div>
|
||||
<Revision repoUrl={hydrateOperationState.sourceHydrator.drySource.repoURL} revision={hydrateOperationState.drySHA} />
|
||||
</div>
|
||||
)
|
||||
});
|
||||
if (hydrateOperationState.finishedAt) {
|
||||
if (hydrateOperationState.drySHA) {
|
||||
operationAttributes.push({
|
||||
title: 'DRY REVISION',
|
||||
value: (
|
||||
<div>
|
||||
<Revision repoUrl={hydrateOperationState.sourceHydrator.drySource.repoURL} revision={hydrateOperationState.drySHA} />
|
||||
</div>
|
||||
)
|
||||
});
|
||||
}
|
||||
if (hydrateOperationState.finishedAt && hydrateOperationState.hydratedSHA) {
|
||||
operationAttributes.push({
|
||||
title: 'HYDRATED REVISION',
|
||||
value: (
|
||||
|
||||
@@ -8,6 +8,7 @@ import {services} from '../../../shared/services';
|
||||
import {
|
||||
ApplicationSyncWindowStatusIcon,
|
||||
ComparisonStatusIcon,
|
||||
formatApplicationSetProgressiveSyncStep,
|
||||
getAppDefaultSource,
|
||||
getAppDefaultSyncRevisionExtra,
|
||||
getAppOperationState,
|
||||
@@ -130,7 +131,7 @@ const ProgressiveSyncStatus = ({application}: {application: models.Application})
|
||||
<div className='application-status-panel__item-value' style={{color: getProgressiveSyncStatusColor(appResource.status)}}>
|
||||
{getProgressiveSyncStatusIcon({status: appResource.status})} {appResource.status}
|
||||
</div>
|
||||
{appResource?.step && <div className='application-status-panel__item-value'>Wave: {appResource.step}</div>}
|
||||
{appResource?.step !== undefined && <div className='application-status-panel__item-value'>{formatApplicationSetProgressiveSyncStep(appResource.step)}</div>}
|
||||
{lastTransitionTime && (
|
||||
<div className='application-status-panel__item-name' style={{marginBottom: '0.5em'}}>
|
||||
Last Transition: <br />
|
||||
|
||||
@@ -1705,6 +1705,14 @@ export function getAppUrl(app: appModels.Application): string {
|
||||
return `applications/${app.metadata.namespace}/${app.metadata.name}`;
|
||||
}
|
||||
|
||||
/** RollingSync step for display; backend uses -1 when no step matches the app's labels. */
|
||||
export function formatApplicationSetProgressiveSyncStep(step: string | undefined): string {
|
||||
if (step === '-1') {
|
||||
return 'Step: unmatched label';
|
||||
}
|
||||
return `Step: ${step ?? ''}`;
|
||||
}
|
||||
|
||||
export const getProgressiveSyncStatusIcon = ({status, isButton}: {status: string; isButton?: boolean}) => {
|
||||
const getIconProps = () => {
|
||||
switch (status) {
|
||||
|
||||
@@ -494,7 +494,9 @@ export interface HydrateOperation {
|
||||
finishedAt?: models.Time;
|
||||
phase: HydrateOperationPhase;
|
||||
message: string;
|
||||
// drySHA is the sha of the DRY commit being hydrated. This will be empty if the operation is not successful.
|
||||
drySHA: string;
|
||||
// hydratedSHA is the sha of the hydrated commit. This will be empty if the operation is not successful.
|
||||
hydratedSHA: string;
|
||||
sourceHydrator: SourceHydrator;
|
||||
}
|
||||
|
||||
12
ui/yarn.lock
12
ui/yarn.lock
@@ -6437,9 +6437,9 @@ locate-path@^6.0.0:
|
||||
p-locate "^5.0.0"
|
||||
|
||||
lodash-es@^4.17.21, lodash-es@^4.2.1:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
|
||||
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
|
||||
version "4.17.23"
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.23.tgz#58c4360fd1b5d33afc6c0bbd3d1149349b1138e0"
|
||||
integrity sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==
|
||||
|
||||
lodash.memoize@4.x:
|
||||
version "4.1.2"
|
||||
@@ -6452,9 +6452,9 @@ lodash.merge@^4.6.2:
|
||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||
|
||||
lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.2.1, lodash@^4.6.1:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
version "4.17.23"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a"
|
||||
integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==
|
||||
|
||||
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
|
||||
@@ -97,23 +97,41 @@ func CheckOutOfBoundsSymlinks(basePath string) error {
|
||||
})
|
||||
}
|
||||
|
||||
// GetAppRefreshPaths returns the list of paths that should trigger a refresh for an application
|
||||
func GetAppRefreshPaths(app *v1alpha1.Application) []string {
|
||||
// GetSourceRefreshPaths returns the list of paths that should trigger a refresh for an application.
|
||||
// The source parameter influences the returned refresh paths:
|
||||
// - if source hydrator configured AND source is syncSource: use sync source path (ignores annotation)
|
||||
// - if source hydrator configured AND source is drySource WITH annotation: use annotation paths with drySource base
|
||||
// - if source hydrator not configured: use annotation paths with source base, or empty if no annotation
|
||||
func GetSourceRefreshPaths(app *v1alpha1.Application, source v1alpha1.ApplicationSource) []string {
|
||||
annotationPaths, hasAnnotation := app.Annotations[v1alpha1.AnnotationKeyManifestGeneratePaths]
|
||||
|
||||
if app.Spec.SourceHydrator != nil {
|
||||
syncSource := app.Spec.SourceHydrator.GetSyncSource()
|
||||
|
||||
// if source is syncSource use the source path
|
||||
if (source).Equals(&syncSource) {
|
||||
return []string{source.Path}
|
||||
}
|
||||
}
|
||||
|
||||
var paths []string
|
||||
if val, ok := app.Annotations[v1alpha1.AnnotationKeyManifestGeneratePaths]; ok && val != "" {
|
||||
for _, item := range strings.Split(val, ";") {
|
||||
if hasAnnotation && annotationPaths != "" {
|
||||
for _, item := range strings.Split(annotationPaths, ";") {
|
||||
// skip empty paths
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
// if absolute path, add as is
|
||||
if filepath.IsAbs(item) {
|
||||
paths = append(paths, item[1:])
|
||||
} else {
|
||||
for _, source := range app.Spec.GetSources() {
|
||||
paths = append(paths, filepath.Clean(filepath.Join(source.Path, item)))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// add the path relative to the source path
|
||||
paths = append(paths, filepath.Clean(filepath.Join(source.Path, item)))
|
||||
}
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
fileutil "github.com/argoproj/argo-cd/v3/test/fixture/path"
|
||||
@@ -100,96 +101,38 @@ func TestAbsSymlink(t *testing.T) {
|
||||
assert.Equal(t, "abslink", oobError.File)
|
||||
}
|
||||
|
||||
func getApp(annotation string, sourcePath string) *v1alpha1.Application {
|
||||
return &v1alpha1.Application{
|
||||
func getApp(annotation *string, sourcePath *string) *v1alpha1.Application {
|
||||
app := &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
v1alpha1.AnnotationKeyManifestGeneratePaths: annotation,
|
||||
},
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Source: &v1alpha1.ApplicationSource{
|
||||
Path: sourcePath,
|
||||
},
|
||||
Name: "test-app",
|
||||
},
|
||||
}
|
||||
if annotation != nil {
|
||||
app.Annotations = make(map[string]string)
|
||||
app.Annotations[v1alpha1.AnnotationKeyManifestGeneratePaths] = *annotation
|
||||
}
|
||||
|
||||
if sourcePath != nil {
|
||||
app.Spec.Source = &v1alpha1.ApplicationSource{
|
||||
Path: *sourcePath,
|
||||
}
|
||||
}
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func getMultiSourceApp(annotation string, paths ...string) *v1alpha1.Application {
|
||||
var sources v1alpha1.ApplicationSources
|
||||
for _, path := range paths {
|
||||
sources = append(sources, v1alpha1.ApplicationSource{Path: path})
|
||||
}
|
||||
return &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
v1alpha1.AnnotationKeyManifestGeneratePaths: annotation,
|
||||
},
|
||||
func getSourceHydratorApp(annotation *string, drySourcePath string, syncSourcePath string) *v1alpha1.Application {
|
||||
app := getApp(annotation, nil)
|
||||
app.Spec.SourceHydrator = &v1alpha1.SourceHydrator{
|
||||
DrySource: v1alpha1.DrySource{
|
||||
Path: drySourcePath,
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Sources: sources,
|
||||
SyncSource: v1alpha1.SyncSource{
|
||||
Path: syncSourcePath,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AppFilesHaveChanged(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
app *v1alpha1.Application
|
||||
files []string
|
||||
changeExpected bool
|
||||
}{
|
||||
{"default no path", &v1alpha1.Application{}, []string{"README.md"}, true},
|
||||
{"no files changed", getApp(".", "source/path"), []string{}, true},
|
||||
{"relative path - matching", getApp(".", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
|
||||
{"relative path, multi source - matching #1", getMultiSourceApp(".", "source/path", "other/path"), []string{"source/path/my-deployment.yaml"}, true},
|
||||
{"relative path, multi source - matching #2", getMultiSourceApp(".", "other/path", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
|
||||
{"relative path - not matching", getApp(".", "source/path"), []string{"README.md"}, false},
|
||||
{"relative path, multi source - not matching", getMultiSourceApp(".", "other/path", "unrelated/path"), []string{"README.md"}, false},
|
||||
{"absolute path - matching", getApp("/source/path", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
|
||||
{"absolute path, multi source - matching #1", getMultiSourceApp("/source/path", "source/path", "other/path"), []string{"source/path/my-deployment.yaml"}, true},
|
||||
{"absolute path, multi source - matching #2", getMultiSourceApp("/source/path", "other/path", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
|
||||
{"absolute path - not matching", getApp("/source/path1", "source/path"), []string{"source/path/my-deployment.yaml"}, false},
|
||||
{"absolute path, multi source - not matching", getMultiSourceApp("/source/path1", "other/path", "source/path"), []string{"source/path/my-deployment.yaml"}, false},
|
||||
{"glob path * - matching", getApp("/source/**/my-deployment.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
|
||||
{"glob path * - not matching", getApp("/source/**/my-service.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}, false},
|
||||
{"glob path ? - matching", getApp("/source/path/my-deployment-?.yaml", "source/path"), []string{"source/path/my-deployment-0.yaml"}, true},
|
||||
{"glob path ? - not matching", getApp("/source/path/my-deployment-?.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}, false},
|
||||
{"glob path char range - matching", getApp("/source/path[0-9]/my-deployment.yaml", "source/path"), []string{"source/path1/my-deployment.yaml"}, true},
|
||||
{"glob path char range - not matching", getApp("/source/path[0-9]/my-deployment.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}, false},
|
||||
{"mixed glob path - matching", getApp("/source/path[0-9]/my-*.yaml", "source/path"), []string{"source/path1/my-deployment.yaml"}, true},
|
||||
{"mixed glob path - not matching", getApp("/source/path[0-9]/my-*.yaml", "source/path"), []string{"README.md"}, false},
|
||||
{"two relative paths - matching", getApp(".;../shared", "my-app"), []string{"shared/my-deployment.yaml"}, true},
|
||||
{"two relative paths, multi source - matching #1", getMultiSourceApp(".;../shared", "my-app", "other/path"), []string{"shared/my-deployment.yaml"}, true},
|
||||
{"two relative paths, multi source - matching #2", getMultiSourceApp(".;../shared", "my-app", "other/path"), []string{"shared/my-deployment.yaml"}, true},
|
||||
{"two relative paths - not matching", getApp(".;../shared", "my-app"), []string{"README.md"}, false},
|
||||
{"two relative paths, multi source - not matching", getMultiSourceApp(".;../shared", "my-app", "other/path"), []string{"README.md"}, false},
|
||||
{"file relative path - matching", getApp("./my-deployment.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
|
||||
{"file relative path, multi source - matching #1", getMultiSourceApp("./my-deployment.yaml", "source/path", "other/path"), []string{"source/path/my-deployment.yaml"}, true},
|
||||
{"file relative path, multi source - matching #2", getMultiSourceApp("./my-deployment.yaml", "other/path", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
|
||||
{"file relative path - not matching", getApp("./my-deployment.yaml", "source/path"), []string{"README.md"}, false},
|
||||
{"file relative path, multi source - not matching", getMultiSourceApp("./my-deployment.yaml", "source/path", "other/path"), []string{"README.md"}, false},
|
||||
{"file absolute path - matching", getApp("/source/path/my-deployment.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
|
||||
{"file absolute path, multi source - matching #1", getMultiSourceApp("/source/path/my-deployment.yaml", "source/path", "other/path"), []string{"source/path/my-deployment.yaml"}, true},
|
||||
{"file absolute path, multi source - matching #2", getMultiSourceApp("/source/path/my-deployment.yaml", "other/path", "source/path"), []string{"source/path/my-deployment.yaml"}, true},
|
||||
{"file absolute path - not matching", getApp("/source/path1/README.md", "source/path"), []string{"source/path/my-deployment.yaml"}, false},
|
||||
{"file absolute path, multi source - not matching", getMultiSourceApp("/source/path1/README.md", "source/path", "other/path"), []string{"source/path/my-deployment.yaml"}, false},
|
||||
{"file two relative paths - matching", getApp("./README.md;../shared/my-deployment.yaml", "my-app"), []string{"shared/my-deployment.yaml"}, true},
|
||||
{"file two relative paths, multi source - matching", getMultiSourceApp("./README.md;../shared/my-deployment.yaml", "my-app", "other-path"), []string{"shared/my-deployment.yaml"}, true},
|
||||
{"file two relative paths - not matching", getApp(".README.md;../shared/my-deployment.yaml", "my-app"), []string{"kustomization.yaml"}, false},
|
||||
{"file two relative paths, multi source - not matching", getMultiSourceApp(".README.md;../shared/my-deployment.yaml", "my-app", "other-path"), []string{"kustomization.yaml"}, false},
|
||||
{"changed file absolute path - matching", getApp(".", "source/path"), []string{"/source/path/my-deployment.yaml"}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
ttc := tt
|
||||
t.Run(ttc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
refreshPaths := GetAppRefreshPaths(ttc.app)
|
||||
assert.Equal(t, ttc.changeExpected, AppFilesHaveChanged(refreshPaths, ttc.files), "AppFilesHaveChanged()")
|
||||
})
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
||||
func Test_GetAppRefreshPaths(t *testing.T) {
|
||||
@@ -198,23 +141,64 @@ func Test_GetAppRefreshPaths(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
app *v1alpha1.Application
|
||||
source v1alpha1.ApplicationSource
|
||||
expectedPaths []string
|
||||
}{
|
||||
{"default no path", &v1alpha1.Application{}, []string{}},
|
||||
{"relative path", getApp(".", "source/path"), []string{"source/path"}},
|
||||
{"absolute path - multi source", getMultiSourceApp("/source/path", "source/path", "other/path"), []string{"source/path"}},
|
||||
{"two relative paths ", getApp(".;../shared", "my-app"), []string{"my-app", "shared"}},
|
||||
{"file relative path", getApp("./my-deployment.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}},
|
||||
{"file absolute path", getApp("/source/path/my-deployment.yaml", "source/path"), []string{"source/path/my-deployment.yaml"}},
|
||||
{"file two relative paths", getApp("./README.md;../shared/my-deployment.yaml", "my-app"), []string{"my-app/README.md", "shared/my-deployment.yaml"}},
|
||||
{"glob path", getApp("/source/*/my-deployment.yaml", "source/path"), []string{"source/*/my-deployment.yaml"}},
|
||||
{"empty path", getApp(".;", "source/path"), []string{"source/path"}},
|
||||
{
|
||||
name: "single source without annotation",
|
||||
app: getApp(nil, ptr.To("source/path")),
|
||||
source: v1alpha1.ApplicationSource{Path: "source/path"},
|
||||
expectedPaths: []string{},
|
||||
},
|
||||
{
|
||||
name: "single source with annotation",
|
||||
app: getApp(ptr.To(".;dev/deploy;other/path"), ptr.To("source/path")),
|
||||
source: v1alpha1.ApplicationSource{Path: "source/path"},
|
||||
expectedPaths: []string{"source/path", "source/path/dev/deploy", "source/path/other/path"},
|
||||
},
|
||||
{
|
||||
name: "single source with empty annotation",
|
||||
app: getApp(ptr.To(".;;"), ptr.To("source/path")),
|
||||
source: v1alpha1.ApplicationSource{Path: "source/path"},
|
||||
expectedPaths: []string{"source/path"},
|
||||
},
|
||||
{
|
||||
name: "single source with absolute path annotation",
|
||||
app: getApp(ptr.To("/fullpath/deploy;other/path"), ptr.To("source/path")),
|
||||
source: v1alpha1.ApplicationSource{Path: "source/path"},
|
||||
expectedPaths: []string{"fullpath/deploy", "source/path/other/path"},
|
||||
},
|
||||
{
|
||||
name: "source hydrator sync source without annotation",
|
||||
app: getSourceHydratorApp(nil, "dry/path", "sync/path"),
|
||||
source: v1alpha1.ApplicationSource{Path: "sync/path"},
|
||||
expectedPaths: []string{"sync/path"},
|
||||
},
|
||||
{
|
||||
name: "source hydrator dry source without annotation",
|
||||
app: getSourceHydratorApp(nil, "dry/path", "sync/path"),
|
||||
source: v1alpha1.ApplicationSource{Path: "dry/path"},
|
||||
expectedPaths: []string{},
|
||||
},
|
||||
{
|
||||
name: "source hydrator sync source with annotation",
|
||||
app: getSourceHydratorApp(ptr.To("deploy"), "dry/path", "sync/path"),
|
||||
source: v1alpha1.ApplicationSource{Path: "sync/path"},
|
||||
expectedPaths: []string{"sync/path"},
|
||||
},
|
||||
{
|
||||
name: "source hydrator dry source with annotation",
|
||||
app: getSourceHydratorApp(ptr.To("deploy"), "dry/path", "sync/path"),
|
||||
source: v1alpha1.ApplicationSource{Path: "dry/path"},
|
||||
expectedPaths: []string{"dry/path/deploy"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
ttc := tt
|
||||
t.Run(ttc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert.ElementsMatch(t, ttc.expectedPaths, GetAppRefreshPaths(ttc.app), "GetAppRefreshPath()")
|
||||
assert.ElementsMatch(t, ttc.expectedPaths, GetSourceRefreshPaths(ttc.app, ttc.source), "GetAppRefreshPath()")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package grpc
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
giterr "github.com/go-git/go-git/v5/plumbing/transport"
|
||||
"google.golang.org/grpc"
|
||||
@@ -132,3 +134,25 @@ func ErrorCodeK8sStreamServerInterceptor() grpc.StreamServerInterceptor {
|
||||
return kubeErrToGRPC(err)
|
||||
}
|
||||
}
|
||||
|
||||
// InvalidMethodNameErrorUnaryServerInterceptor is for mitigation of grpc-go CVE-2026-33186
|
||||
// see discussion in https://github.com/argoproj/argo-cd/issues/26932
|
||||
func InvalidMethodNameErrorUnaryServerInterceptor() grpc.UnaryServerInterceptor {
|
||||
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
|
||||
if !strings.HasPrefix(info.FullMethod, "/") {
|
||||
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("malformed method name: %q", info.FullMethod))
|
||||
}
|
||||
return handler(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// InvalidMethodNameErrorStreamServerInterceptor is for mitigation of grpc-go CVE-2026-33186
|
||||
// see discussion in https://github.com/argoproj/argo-cd/issues/26932
|
||||
func InvalidMethodNameErrorStreamServerInterceptor() grpc.StreamServerInterceptor {
|
||||
return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
if !strings.HasPrefix(info.FullMethod, "/") {
|
||||
return status.Error(codes.InvalidArgument, fmt.Sprintf("malformed method name: %q", info.FullMethod))
|
||||
}
|
||||
return handler(srv, ss)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -153,3 +155,56 @@ func Test_kubeErrToGRPC(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func checkGrpcError(t *testing.T, err error, msg string) {
|
||||
t.Helper()
|
||||
require.Error(t, err)
|
||||
s, ok := status.FromError(err)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, codes.InvalidArgument, s.Code())
|
||||
assert.ErrorContains(t, err, msg)
|
||||
}
|
||||
|
||||
func TestInvalidMethodNameErrorUnaryServerInterceptor(t *testing.T) {
|
||||
interceptor := InvalidMethodNameErrorUnaryServerInterceptor()
|
||||
handler := func(_ context.Context, _ any) (any, error) {
|
||||
return nil, nil
|
||||
}
|
||||
t.Run("Test invalid method name", func(t *testing.T) {
|
||||
info := &grpc.UnaryServerInfo{FullMethod: "foo"}
|
||||
_, err := interceptor(t.Context(), nil, info, handler)
|
||||
checkGrpcError(t, err, "malformed method name: \"foo\"")
|
||||
})
|
||||
t.Run("Test empty method name", func(t *testing.T) {
|
||||
info := &grpc.UnaryServerInfo{FullMethod: ""}
|
||||
_, err := interceptor(t.Context(), nil, info, handler)
|
||||
checkGrpcError(t, err, "malformed method name: \"\"")
|
||||
})
|
||||
t.Run("Test valid method name", func(t *testing.T) {
|
||||
info := &grpc.UnaryServerInfo{FullMethod: "/foo"}
|
||||
_, err := interceptor(t.Context(), nil, info, handler)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestInvalidMethodNameErrorStreamServerInterceptor(t *testing.T) {
|
||||
interceptor := InvalidMethodNameErrorStreamServerInterceptor()
|
||||
handler := func(_ any, _ grpc.ServerStream) error {
|
||||
return nil
|
||||
}
|
||||
t.Run("Test invalid method name", func(t *testing.T) {
|
||||
info := &grpc.StreamServerInfo{FullMethod: "foo"}
|
||||
err := interceptor(t.Context(), nil, info, handler)
|
||||
checkGrpcError(t, err, "malformed method name: \"foo\"")
|
||||
})
|
||||
t.Run("Test empty method name", func(t *testing.T) {
|
||||
info := &grpc.StreamServerInfo{FullMethod: ""}
|
||||
err := interceptor(t.Context(), nil, info, handler)
|
||||
checkGrpcError(t, err, "malformed method name: \"\"")
|
||||
})
|
||||
t.Run("Test valid method name", func(t *testing.T) {
|
||||
info := &grpc.StreamServerInfo{FullMethod: "/foo"}
|
||||
err := interceptor(nil, nil, info, handler)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package healthz
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -11,9 +12,13 @@ import (
|
||||
// ServeHealthCheck relies on the provided function to return an error if unhealthy and nil otherwise.
|
||||
func ServeHealthCheck(mux *http.ServeMux, f func(r *http.Request) error) {
|
||||
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
|
||||
startTs := time.Now()
|
||||
if err := f(r); err != nil {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
log.Errorln(w, err)
|
||||
log.WithFields(log.Fields{
|
||||
"duration": time.Since(startTs),
|
||||
"component": "healthcheck",
|
||||
}).WithError(err).Error("Error serving health check request")
|
||||
} else {
|
||||
fmt.Fprintln(w, "ok")
|
||||
}
|
||||
|
||||
@@ -5,16 +5,22 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus/hooks/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHealthCheck(t *testing.T) {
|
||||
sentinel := false
|
||||
|
||||
lc := &net.ListenConfig{}
|
||||
ctx := t.Context()
|
||||
svcErrMsg := "This is a dummy error"
|
||||
serve := func(c chan<- string) {
|
||||
// listen on first available dynamic (unprivileged) port
|
||||
listener, err := net.Listen("tcp", ":0")
|
||||
listener, err := lc.Listen(ctx, "tcp", ":0")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -25,7 +31,7 @@ func TestHealthCheck(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
ServeHealthCheck(mux, func(_ *http.Request) error {
|
||||
if sentinel {
|
||||
return errors.New("This is a dummy error")
|
||||
return errors.New(svcErrMsg)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -47,7 +53,23 @@ func TestHealthCheck(t *testing.T) {
|
||||
require.Equalf(t, http.StatusOK, resp.StatusCode, "Was expecting status code 200 from health check, but got %d instead", resp.StatusCode)
|
||||
|
||||
sentinel = true
|
||||
hook := test.NewGlobal()
|
||||
|
||||
resp, _ = http.Get(server + "/healthz")
|
||||
require.Equalf(t, http.StatusServiceUnavailable, resp.StatusCode, "Was expecting status code 503 from health check, but got %d instead", resp.StatusCode)
|
||||
assert.NotEmpty(t, hook.Entries, "Was expecting at least one log entry from health check, but got none")
|
||||
expectedMsg := "Error serving health check request"
|
||||
var foundEntry log.Entry
|
||||
for _, entry := range hook.Entries {
|
||||
if entry.Level == log.ErrorLevel &&
|
||||
entry.Message == expectedMsg {
|
||||
foundEntry = entry
|
||||
break
|
||||
}
|
||||
}
|
||||
require.NotEmpty(t, foundEntry, "Expected an error message '%s', but it was't found", expectedMsg)
|
||||
actualErr, ok := foundEntry.Data["error"].(error)
|
||||
require.True(t, ok, "Expected 'error' field to contain an error, but it doesn't")
|
||||
assert.Equal(t, svcErrMsg, actualErr.Error(), "expected original error message '"+svcErrMsg+"', but got '"+actualErr.Error()+"'")
|
||||
assert.Greater(t, foundEntry.Data["duration"].(time.Duration), time.Duration(0))
|
||||
}
|
||||
|
||||
@@ -330,23 +330,23 @@ func (a *ArgoCDWebhookHandler) HandleEvent(payload any) {
|
||||
appIf := a.appsLister.Applications(nsFilter)
|
||||
apps, err := appIf.List(labels.Everything())
|
||||
if err != nil {
|
||||
log.Warnf("Failed to list applications: %v", err)
|
||||
log.Errorf("Failed to list applications: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
installationID, err := a.settingsSrc.GetInstallationID()
|
||||
if err != nil {
|
||||
log.Warnf("Failed to get installation ID: %v", err)
|
||||
log.Errorf("Failed to get installation ID: %v", err)
|
||||
return
|
||||
}
|
||||
trackingMethod, err := a.settingsSrc.GetTrackingMethod()
|
||||
if err != nil {
|
||||
log.Warnf("Failed to get trackingMethod: %v", err)
|
||||
log.Errorf("Failed to get trackingMethod: %v", err)
|
||||
return
|
||||
}
|
||||
appInstanceLabelKey, err := a.settingsSrc.GetAppInstanceLabelKey()
|
||||
if err != nil {
|
||||
log.Warnf("Failed to get appInstanceLabelKey: %v", err)
|
||||
log.Errorf("Failed to get appInstanceLabelKey: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -362,41 +362,47 @@ func (a *ArgoCDWebhookHandler) HandleEvent(payload any) {
|
||||
for _, webURL := range webURLs {
|
||||
repoRegexp, err := GetWebURLRegex(webURL)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to get repoRegexp: %s", err)
|
||||
log.Errorf("Failed to get repoRegexp: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// iterate over apps and check if any files specified in their sources have changed
|
||||
for _, app := range filteredApps {
|
||||
// get all sources, including sync source and dry source if source hydrator is configured
|
||||
sources := app.Spec.GetSources()
|
||||
if app.Spec.SourceHydrator != nil {
|
||||
drySource := app.Spec.SourceHydrator.GetDrySource()
|
||||
if sourceRevisionHasChanged(drySource, revision, touchedHead) && sourceUsesURL(drySource, webURL, repoRegexp) {
|
||||
refreshPaths := path.GetAppRefreshPaths(&app)
|
||||
if path.AppFilesHaveChanged(refreshPaths, changedFiles) {
|
||||
namespacedAppInterface := a.appClientset.ArgoprojV1alpha1().Applications(app.Namespace)
|
||||
log.Infof("webhook trigger refresh app to hydrate '%s'", app.Name)
|
||||
_, err = argo.RefreshApp(namespacedAppInterface, app.Name, v1alpha1.RefreshTypeNormal, true)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to hydrate app '%s' for controller reprocessing: %v", app.Name, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
// we already have sync source, so add dry source if source hydrator is configured
|
||||
sources = append(sources, app.Spec.SourceHydrator.GetDrySource())
|
||||
}
|
||||
|
||||
for _, source := range app.Spec.GetSources() {
|
||||
// iterate over all sources and check if any files specified in refresh paths have changed
|
||||
for _, source := range sources {
|
||||
if sourceRevisionHasChanged(source, revision, touchedHead) && sourceUsesURL(source, webURL, repoRegexp) {
|
||||
refreshPaths := path.GetAppRefreshPaths(&app)
|
||||
refreshPaths := path.GetSourceRefreshPaths(&app, source)
|
||||
if path.AppFilesHaveChanged(refreshPaths, changedFiles) {
|
||||
namespacedAppInterface := a.appClientset.ArgoprojV1alpha1().Applications(app.Namespace)
|
||||
_, err = argo.RefreshApp(namespacedAppInterface, app.Name, v1alpha1.RefreshTypeNormal, true)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to refresh app '%s' for controller reprocessing: %v", app.Name, err)
|
||||
continue
|
||||
hydrate := false
|
||||
if app.Spec.SourceHydrator != nil {
|
||||
drySource := app.Spec.SourceHydrator.GetDrySource()
|
||||
if (&source).Equals(&drySource) {
|
||||
hydrate = true
|
||||
}
|
||||
}
|
||||
// No need to refresh multiple times if multiple sources match.
|
||||
break
|
||||
|
||||
// refresh paths have changed, so we need to refresh the app
|
||||
log.Infof("refreshing app '%s' from webhook", app.Name)
|
||||
if hydrate {
|
||||
// log if we need to hydrate the app
|
||||
log.Infof("webhook trigger refresh app to hydrate '%s'", app.Name)
|
||||
}
|
||||
namespacedAppInterface := a.appClientset.ArgoprojV1alpha1().Applications(app.Namespace)
|
||||
if _, err := argo.RefreshApp(namespacedAppInterface, app.Name, v1alpha1.RefreshTypeNormal, hydrate); err != nil {
|
||||
log.Errorf("Failed to refresh app '%s': %v", app.Name, err)
|
||||
}
|
||||
break // we don't need to check other sources
|
||||
} else if change.shaBefore != "" && change.shaAfter != "" {
|
||||
if err := a.storePreviouslyCachedManifests(&app, change, trackingMethod, appInstanceLabelKey, installationID); err != nil {
|
||||
log.Warnf("Failed to store cached manifests of previous revision for app '%s': %v", app.Name, err)
|
||||
// update the cached manifests with the new revision cache key
|
||||
if err := a.storePreviouslyCachedManifests(&app, change, trackingMethod, appInstanceLabelKey, installationID, source); err != nil {
|
||||
log.Errorf("Failed to store cached manifests of previous revision for app '%s': %v", app.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -449,7 +455,7 @@ func getURLRegex(originalURL string, regexpFormat string) (*regexp.Regexp, error
|
||||
return repoRegexp, nil
|
||||
}
|
||||
|
||||
func (a *ArgoCDWebhookHandler) storePreviouslyCachedManifests(app *v1alpha1.Application, change changeInfo, trackingMethod string, appInstanceLabelKey string, installationID string) error {
|
||||
func (a *ArgoCDWebhookHandler) storePreviouslyCachedManifests(app *v1alpha1.Application, change changeInfo, trackingMethod string, appInstanceLabelKey string, installationID string, source v1alpha1.ApplicationSource) error {
|
||||
destCluster, err := argo.GetDestinationCluster(context.Background(), app.Spec.Destination, a.db)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error validating destination: %w", err)
|
||||
@@ -472,7 +478,7 @@ func (a *ArgoCDWebhookHandler) storePreviouslyCachedManifests(app *v1alpha1.Appl
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting ref sources: %w", err)
|
||||
}
|
||||
source := app.Spec.GetSource()
|
||||
|
||||
cache.LogDebugManifestCacheKeyFields("moving manifests cache", "webhook app revision changed", change.shaBefore, &source, refSources, &clusterInfo, app.Spec.Destination.Namespace, trackingMethod, appInstanceLabelKey, app.Name, nil)
|
||||
|
||||
if err := a.repoCache.SetNewRevisionManifests(change.shaAfter, change.shaBefore, &source, refSources, &clusterInfo, app.Spec.Destination.Namespace, trackingMethod, appInstanceLabelKey, app.Name, nil, installationID); err != nil {
|
||||
|
||||
@@ -44,6 +44,7 @@ import (
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
appclientset "github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned/fake"
|
||||
"github.com/argoproj/argo-cd/v3/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v3/reposerver/cache"
|
||||
cacheutil "github.com/argoproj/argo-cd/v3/util/cache"
|
||||
"github.com/argoproj/argo-cd/v3/util/settings"
|
||||
@@ -182,64 +183,6 @@ func TestAzureDevOpsCommitEvent(t *testing.T) {
|
||||
hook.Reset()
|
||||
}
|
||||
|
||||
// TestGitHubCommitEvent_MultiSource_Refresh makes sure that a webhook will refresh a multi-source app when at least
|
||||
// one source matches.
|
||||
func TestGitHubCommitEvent_MultiSource_Refresh(t *testing.T) {
|
||||
hook := test.NewGlobal()
|
||||
var patched bool
|
||||
reaction := func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
patchAction := action.(kubetesting.PatchAction)
|
||||
assert.Equal(t, "app-to-refresh", patchAction.GetName())
|
||||
patched = true
|
||||
return true, nil, nil
|
||||
}
|
||||
h := NewMockHandler(&reactorDef{"patch", "applications", reaction}, []string{}, &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-to-refresh",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Sources: v1alpha1.ApplicationSources{
|
||||
{
|
||||
RepoURL: "https://github.com/some/unrelated-repo",
|
||||
Path: ".",
|
||||
},
|
||||
{
|
||||
RepoURL: "https://github.com/jessesuen/test-repo",
|
||||
Path: ".",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-to-ignore",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Sources: v1alpha1.ApplicationSources{
|
||||
{
|
||||
RepoURL: "https://github.com/some/unrelated-repo",
|
||||
Path: ".",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/webhook", http.NoBody)
|
||||
req.Header.Set("X-GitHub-Event", "push")
|
||||
eventJSON, err := os.ReadFile("testdata/github-commit-event.json")
|
||||
require.NoError(t, err)
|
||||
req.Body = io.NopCloser(bytes.NewReader(eventJSON))
|
||||
w := httptest.NewRecorder()
|
||||
h.Handler(w, req)
|
||||
close(h.queue)
|
||||
h.Wait()
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
expectedLogResult := "Requested app 'app-to-refresh' refresh"
|
||||
assert.Equal(t, expectedLogResult, hook.LastEntry().Message)
|
||||
assert.True(t, patched)
|
||||
hook.Reset()
|
||||
}
|
||||
|
||||
// TestGitHubCommitEvent_AppsInOtherNamespaces makes sure that webhooks properly find apps in the configured set of
|
||||
// allowed namespaces when Apps are allowed in any namespace
|
||||
func TestGitHubCommitEvent_AppsInOtherNamespaces(t *testing.T) {
|
||||
@@ -338,72 +281,6 @@ func TestGitHubCommitEvent_AppsInOtherNamespaces(t *testing.T) {
|
||||
hook.Reset()
|
||||
}
|
||||
|
||||
// TestGitHubCommitEvent_Hydrate makes sure that a webhook will hydrate an app when dry source changed.
|
||||
func TestGitHubCommitEvent_Hydrate(t *testing.T) {
|
||||
hook := test.NewGlobal()
|
||||
var patched bool
|
||||
reaction := func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
patchAction := action.(kubetesting.PatchAction)
|
||||
assert.Equal(t, "app-to-hydrate", patchAction.GetName())
|
||||
patched = true
|
||||
return true, nil, nil
|
||||
}
|
||||
h := NewMockHandler(&reactorDef{"patch", "applications", reaction}, []string{}, &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-to-hydrate",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
SourceHydrator: &v1alpha1.SourceHydrator{
|
||||
DrySource: v1alpha1.DrySource{
|
||||
RepoURL: "https://github.com/jessesuen/test-repo",
|
||||
TargetRevision: "HEAD",
|
||||
Path: ".",
|
||||
},
|
||||
SyncSource: v1alpha1.SyncSource{
|
||||
TargetBranch: "environments/dev",
|
||||
Path: ".",
|
||||
},
|
||||
HydrateTo: nil,
|
||||
},
|
||||
},
|
||||
}, &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-to-ignore",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Sources: v1alpha1.ApplicationSources{
|
||||
{
|
||||
RepoURL: "https://github.com/some/unrelated-repo",
|
||||
Path: ".",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/webhook", http.NoBody)
|
||||
req.Header.Set("X-GitHub-Event", "push")
|
||||
eventJSON, err := os.ReadFile("testdata/github-commit-event.json")
|
||||
require.NoError(t, err)
|
||||
req.Body = io.NopCloser(bytes.NewReader(eventJSON))
|
||||
w := httptest.NewRecorder()
|
||||
h.Handler(w, req)
|
||||
close(h.queue)
|
||||
h.Wait()
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.True(t, patched)
|
||||
|
||||
logMessages := make([]string, 0, len(hook.Entries))
|
||||
for _, entry := range hook.Entries {
|
||||
logMessages = append(logMessages, entry.Message)
|
||||
}
|
||||
|
||||
assert.Contains(t, logMessages, "webhook trigger refresh app to hydrate 'app-to-hydrate'")
|
||||
assert.NotContains(t, logMessages, "webhook trigger refresh app to hydrate 'app-to-ignore'")
|
||||
|
||||
hook.Reset()
|
||||
}
|
||||
|
||||
func TestGitHubTagEvent(t *testing.T) {
|
||||
hook := test.NewGlobal()
|
||||
h := NewMockHandler(nil, []string{})
|
||||
@@ -646,7 +523,8 @@ func Test_affectedRevisionInfo_appRevisionHasChanged(t *testing.T) {
|
||||
// The payload's "push.changes[0].new.name" member seems to only have the branch name (based on the example payload).
|
||||
// https://support.atlassian.com/bitbucket-cloud/docs/event-payloads/#EventPayloads-Push
|
||||
var pl bitbucket.RepoPushPayload
|
||||
_ = json.Unmarshal([]byte(fmt.Sprintf(`{"push":{"changes":[{"new":{"name":%q}}]}}`, branchName)), &pl)
|
||||
err := json.Unmarshal([]byte(fmt.Sprintf(`{"push":{"changes":[{"new":{"name":%q}}]}}`, branchName)), &pl)
|
||||
require.NoError(t, err)
|
||||
return pl
|
||||
}
|
||||
|
||||
@@ -878,6 +756,463 @@ func TestGitHubCommitEventMaxPayloadSize(t *testing.T) {
|
||||
hook.Reset()
|
||||
}
|
||||
|
||||
func TestHandleEvent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
app *v1alpha1.Application
|
||||
changedFile string // file that was changed in the webhook payload
|
||||
hasRefresh bool // application has refresh annotation applied
|
||||
hasHydrate bool // application has hydrate annotation applied
|
||||
updateCache bool // cache should be updated with the new revision
|
||||
}{
|
||||
{
|
||||
name: "single source without annotation - always refreshes",
|
||||
app: &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-app",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Sources: v1alpha1.ApplicationSources{
|
||||
{
|
||||
RepoURL: "https://github.com/jessesuen/test-repo",
|
||||
Path: "source/path",
|
||||
TargetRevision: "HEAD",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
changedFile: "source/path/app.yaml",
|
||||
hasRefresh: true,
|
||||
hasHydrate: false,
|
||||
updateCache: false,
|
||||
},
|
||||
{
|
||||
name: "single source with annotation - matching file triggers refresh",
|
||||
app: &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-app",
|
||||
Namespace: "argocd",
|
||||
Annotations: map[string]string{
|
||||
"argocd.argoproj.io/manifest-generate-paths": "deploy",
|
||||
},
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Sources: v1alpha1.ApplicationSources{
|
||||
{
|
||||
RepoURL: "https://github.com/jessesuen/test-repo",
|
||||
Path: "source/path",
|
||||
TargetRevision: "HEAD",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
changedFile: "source/path/deploy/app.yaml",
|
||||
hasRefresh: true,
|
||||
hasHydrate: false,
|
||||
updateCache: false,
|
||||
},
|
||||
{
|
||||
name: "single source with annotation - non-matching file updates cache",
|
||||
app: &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-app",
|
||||
Namespace: "argocd",
|
||||
Annotations: map[string]string{
|
||||
"argocd.argoproj.io/manifest-generate-paths": "manifests",
|
||||
},
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Sources: v1alpha1.ApplicationSources{
|
||||
{
|
||||
RepoURL: "https://github.com/jessesuen/test-repo",
|
||||
Path: "source/path",
|
||||
TargetRevision: "HEAD",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
changedFile: "source/path/other/app.yaml",
|
||||
hasRefresh: false,
|
||||
hasHydrate: false,
|
||||
updateCache: true,
|
||||
},
|
||||
{
|
||||
name: "single source with multiple paths annotation - matching subpath triggers refresh",
|
||||
app: &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-app",
|
||||
Namespace: "argocd",
|
||||
Annotations: map[string]string{
|
||||
"argocd.argoproj.io/manifest-generate-paths": "manifests;dev/deploy;other/path",
|
||||
},
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Sources: v1alpha1.ApplicationSources{
|
||||
{
|
||||
RepoURL: "https://github.com/jessesuen/test-repo",
|
||||
Path: "source/path",
|
||||
TargetRevision: "HEAD",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
changedFile: "source/path/dev/deploy/app.yaml",
|
||||
hasRefresh: true,
|
||||
hasHydrate: false,
|
||||
updateCache: false,
|
||||
},
|
||||
{
|
||||
name: "multi-source without annotation - always refreshes",
|
||||
app: &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-app",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Sources: v1alpha1.ApplicationSources{
|
||||
{
|
||||
RepoURL: "https://github.com/jessesuen/test-repo",
|
||||
Path: "helm-charts",
|
||||
TargetRevision: "HEAD",
|
||||
},
|
||||
{
|
||||
RepoURL: "https://github.com/jessesuen/test-repo",
|
||||
Path: "ksapps",
|
||||
TargetRevision: "HEAD",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
changedFile: "ksapps/app.yaml",
|
||||
hasRefresh: true,
|
||||
hasHydrate: false,
|
||||
updateCache: false,
|
||||
},
|
||||
{
|
||||
name: "multi-source with annotation - matching file triggers refresh",
|
||||
app: &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-app",
|
||||
Namespace: "argocd",
|
||||
Annotations: map[string]string{
|
||||
"argocd.argoproj.io/manifest-generate-paths": "components",
|
||||
},
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Sources: v1alpha1.ApplicationSources{
|
||||
{
|
||||
RepoURL: "https://github.com/jessesuen/test-repo",
|
||||
Path: "helm-charts",
|
||||
TargetRevision: "HEAD",
|
||||
},
|
||||
{
|
||||
RepoURL: "https://github.com/jessesuen/test-repo",
|
||||
Path: "ksapps",
|
||||
TargetRevision: "HEAD",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
changedFile: "ksapps/components/app.yaml",
|
||||
hasRefresh: true,
|
||||
hasHydrate: false,
|
||||
updateCache: false,
|
||||
},
|
||||
{
|
||||
name: "source hydrator sync source without annotation - refreshes when sync path matches",
|
||||
app: &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-app",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
SourceHydrator: &v1alpha1.SourceHydrator{
|
||||
DrySource: v1alpha1.DrySource{
|
||||
RepoURL: "https://github.com/jessesuen/test-repo",
|
||||
TargetRevision: "HEAD",
|
||||
Path: "dry/path",
|
||||
},
|
||||
SyncSource: v1alpha1.SyncSource{
|
||||
TargetBranch: "master",
|
||||
Path: "sync/path",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
changedFile: "sync/path/app.yaml",
|
||||
hasRefresh: true,
|
||||
hasHydrate: false,
|
||||
updateCache: false,
|
||||
},
|
||||
{
|
||||
name: "source hydrator dry source without annotation - always refreshes and hydrates",
|
||||
app: &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-app",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
SourceHydrator: &v1alpha1.SourceHydrator{
|
||||
DrySource: v1alpha1.DrySource{
|
||||
RepoURL: "https://github.com/jessesuen/test-repo",
|
||||
TargetRevision: "HEAD",
|
||||
Path: "dry/path",
|
||||
},
|
||||
SyncSource: v1alpha1.SyncSource{
|
||||
TargetBranch: "master",
|
||||
Path: "sync/path",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
changedFile: "other/path/app.yaml",
|
||||
hasRefresh: true,
|
||||
hasHydrate: true,
|
||||
updateCache: false,
|
||||
},
|
||||
{
|
||||
name: "source hydrator sync source with annotation - refresh only",
|
||||
app: &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-app",
|
||||
Namespace: "argocd",
|
||||
Annotations: map[string]string{
|
||||
"argocd.argoproj.io/manifest-generate-paths": "deploy",
|
||||
},
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
SourceHydrator: &v1alpha1.SourceHydrator{
|
||||
DrySource: v1alpha1.DrySource{
|
||||
RepoURL: "https://github.com/jessesuen/test-repo",
|
||||
TargetRevision: "HEAD",
|
||||
Path: "dry/path",
|
||||
},
|
||||
SyncSource: v1alpha1.SyncSource{
|
||||
TargetBranch: "master",
|
||||
Path: "sync/path",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
changedFile: "sync/path/deploy/app.yaml",
|
||||
hasRefresh: true,
|
||||
hasHydrate: false,
|
||||
updateCache: false,
|
||||
},
|
||||
{
|
||||
name: "source hydrator dry source with annotation - refresh and hydrate",
|
||||
app: &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-app",
|
||||
Namespace: "argocd",
|
||||
Annotations: map[string]string{
|
||||
"argocd.argoproj.io/manifest-generate-paths": "deploy",
|
||||
},
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
SourceHydrator: &v1alpha1.SourceHydrator{
|
||||
DrySource: v1alpha1.DrySource{
|
||||
RepoURL: "https://github.com/jessesuen/test-repo",
|
||||
TargetRevision: "HEAD",
|
||||
Path: "dry/path",
|
||||
},
|
||||
SyncSource: v1alpha1.SyncSource{
|
||||
TargetBranch: "master",
|
||||
Path: "sync/path",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
changedFile: "dry/path/deploy/app.yaml",
|
||||
hasRefresh: true,
|
||||
hasHydrate: true,
|
||||
updateCache: false,
|
||||
},
|
||||
{
|
||||
name: "source hydrator dry source with annotation - non-matching file updates cache",
|
||||
app: &v1alpha1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-app",
|
||||
Namespace: "argocd",
|
||||
Annotations: map[string]string{
|
||||
"argocd.argoproj.io/manifest-generate-paths": "deploy",
|
||||
},
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
SourceHydrator: &v1alpha1.SourceHydrator{
|
||||
DrySource: v1alpha1.DrySource{
|
||||
RepoURL: "https://github.com/jessesuen/test-repo",
|
||||
TargetRevision: "HEAD",
|
||||
Path: "dry/path",
|
||||
},
|
||||
SyncSource: v1alpha1.SyncSource{
|
||||
TargetBranch: "master",
|
||||
Path: "sync/path",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
changedFile: "dry/path/other/app.yaml",
|
||||
hasRefresh: false,
|
||||
hasHydrate: false,
|
||||
updateCache: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
ttc := tt
|
||||
t.Run(ttc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var patchData []byte
|
||||
var patched bool
|
||||
reaction := func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
if action.GetVerb() == "patch" {
|
||||
patchAction := action.(kubetesting.PatchAction)
|
||||
patchData = patchAction.GetPatch()
|
||||
patched = true
|
||||
}
|
||||
return true, nil, nil
|
||||
}
|
||||
|
||||
// Setup cache
|
||||
inMemoryCache := cacheutil.NewInMemoryCache(1 * time.Hour)
|
||||
cacheClient := cacheutil.NewCache(inMemoryCache)
|
||||
repoCache := cache.NewCache(
|
||||
cacheClient,
|
||||
1*time.Minute,
|
||||
1*time.Minute,
|
||||
10*time.Second,
|
||||
)
|
||||
|
||||
// Pre-populate cache with beforeSHA if we're testing cache updates
|
||||
if ttc.updateCache {
|
||||
var source *v1alpha1.ApplicationSource
|
||||
if ttc.app.Spec.SourceHydrator != nil {
|
||||
drySource := ttc.app.Spec.SourceHydrator.GetDrySource()
|
||||
source = &drySource
|
||||
} else if len(ttc.app.Spec.Sources) > 0 {
|
||||
source = &ttc.app.Spec.Sources[0]
|
||||
}
|
||||
if source != nil {
|
||||
setupTestCache(t, repoCache, ttc.app.Name, source, []string{"test-manifest"})
|
||||
}
|
||||
}
|
||||
|
||||
// Setup server cache with cluster info
|
||||
serverCache := servercache.NewCache(appstate.NewCache(cacheClient, time.Minute), time.Minute, time.Minute)
|
||||
mockDB := &mocks.ArgoDB{}
|
||||
|
||||
// Set destination if not present (required for cache updates)
|
||||
if ttc.app.Spec.Destination.Server == "" {
|
||||
ttc.app.Spec.Destination.Server = testClusterURL
|
||||
}
|
||||
|
||||
mockDB.EXPECT().GetCluster(mock.Anything, testClusterURL).Return(&v1alpha1.Cluster{
|
||||
Server: testClusterURL,
|
||||
Info: v1alpha1.ClusterInfo{
|
||||
ServerVersion: "1.28.0",
|
||||
ConnectionState: v1alpha1.ConnectionState{Status: v1alpha1.ConnectionStatusSuccessful},
|
||||
APIVersions: []string{},
|
||||
},
|
||||
}, nil).Maybe()
|
||||
|
||||
err := serverCache.SetClusterInfo(testClusterURL, &v1alpha1.ClusterInfo{
|
||||
ServerVersion: "1.28.0",
|
||||
ConnectionState: v1alpha1.ConnectionState{Status: v1alpha1.ConnectionStatusSuccessful},
|
||||
APIVersions: []string{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create handler with reaction
|
||||
appClientset := appclientset.NewSimpleClientset(ttc.app)
|
||||
defaultReactor := appClientset.ReactionChain[0]
|
||||
appClientset.ReactionChain = nil
|
||||
appClientset.AddReactor("list", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return defaultReactor.React(action)
|
||||
})
|
||||
appClientset.AddReactor("patch", "applications", reaction)
|
||||
|
||||
h := NewHandler(
|
||||
"argocd",
|
||||
[]string{},
|
||||
10,
|
||||
appClientset,
|
||||
&fakeAppsLister{clientset: appClientset},
|
||||
&settings.ArgoCDSettings{},
|
||||
&fakeSettingsSrc{},
|
||||
repoCache,
|
||||
serverCache,
|
||||
mockDB,
|
||||
int64(50)*1024*1024,
|
||||
)
|
||||
|
||||
// Create payload with the changed file
|
||||
payload := createTestPayload(ttc.changedFile)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/webhook", http.NoBody)
|
||||
req.Header.Set("X-GitHub-Event", "push")
|
||||
req.Body = io.NopCloser(bytes.NewReader(payload))
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
h.Handler(w, req)
|
||||
close(h.queue)
|
||||
h.Wait()
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
// Verify refresh behavior
|
||||
assert.Equal(t, ttc.hasRefresh, patched, "patch status mismatch for test: %s", ttc.name)
|
||||
if patched && patchData != nil {
|
||||
verifyAnnotations(t, patchData, ttc.hasRefresh, ttc.hasHydrate)
|
||||
}
|
||||
|
||||
// Verify cache update behavior
|
||||
if ttc.updateCache {
|
||||
var source *v1alpha1.ApplicationSource
|
||||
if ttc.app.Spec.SourceHydrator != nil {
|
||||
drySource := ttc.app.Spec.SourceHydrator.GetDrySource()
|
||||
source = &drySource
|
||||
} else if len(ttc.app.Spec.Sources) > 0 {
|
||||
source = &ttc.app.Spec.Sources[0]
|
||||
}
|
||||
if source != nil {
|
||||
// Verify cache was updated with afterSHA
|
||||
clusterInfo := &mockClusterInfo{}
|
||||
var afterManifests cache.CachedManifestResponse
|
||||
err := repoCache.GetManifests(testAfterSHA, source, nil, clusterInfo, "", "", testAppLabelKey, ttc.app.Name, &afterManifests, nil, "")
|
||||
require.NoError(t, err, "cache should be updated with afterSHA")
|
||||
if err == nil {
|
||||
assert.Equal(t, testAfterSHA, afterManifests.ManifestResponse.Revision, "cached revision should match afterSHA")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// createTestPayload creates a GitHub push event payload with the specified changed file
|
||||
func createTestPayload(changedFile string) []byte {
|
||||
payload := fmt.Sprintf(`{
|
||||
"ref": "refs/heads/master",
|
||||
"before": "%s",
|
||||
"after": "%s",
|
||||
"repository": {
|
||||
"html_url": "https://github.com/jessesuen/test-repo",
|
||||
"default_branch": "master"
|
||||
},
|
||||
"commits": [
|
||||
{
|
||||
"added": [],
|
||||
"modified": ["%s"],
|
||||
"removed": []
|
||||
}
|
||||
]
|
||||
}`, testBeforeSHA, testAfterSHA, changedFile)
|
||||
return []byte(payload)
|
||||
}
|
||||
|
||||
func Test_affectedRevisionInfo_bitbucket_changed_files(t *testing.T) {
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
@@ -922,10 +1257,9 @@ func Test_affectedRevisionInfo_bitbucket_changed_files(t *testing.T) {
|
||||
"oldHash": "abcdef",
|
||||
"newHash": "ghijkl",
|
||||
})
|
||||
if err != nil {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
_ = json.Unmarshal(doc.Bytes(), &pl)
|
||||
require.NoError(t, err)
|
||||
err = json.Unmarshal(doc.Bytes(), &pl)
|
||||
require.NoError(t, err)
|
||||
return pl
|
||||
}
|
||||
|
||||
@@ -1238,3 +1572,72 @@ func getDiffstatResponderFn() func(req *http.Request) (*http.Response, error) {
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
// mockClusterInfo implements cache.ClusterRuntimeInfo for testing
|
||||
type mockClusterInfo struct{}
|
||||
|
||||
func (m *mockClusterInfo) GetApiVersions() []string { return []string{} } //nolint:revive // interface method name
|
||||
func (m *mockClusterInfo) GetKubeVersion() string { return "1.28.0" }
|
||||
|
||||
// Common test constants
|
||||
const (
|
||||
testBeforeSHA = "d5c1ffa8e294bc18c639bfb4e0df499251034414"
|
||||
testAfterSHA = "63738bb582c8b540af7bcfc18f87c575c3ed66e0"
|
||||
testClusterURL = "https://kubernetes.default.svc"
|
||||
testAppLabelKey = "mycompany.com/appname"
|
||||
)
|
||||
|
||||
// verifyAnnotations is a helper that checks if the expected annotations are present in patch data
|
||||
func verifyAnnotations(t *testing.T, patchData []byte, expectRefresh bool, expectHydrate bool) {
|
||||
t.Helper()
|
||||
if patchData == nil {
|
||||
if expectRefresh {
|
||||
t.Error("expected app to be patched but patchData is nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var patchMap map[string]any
|
||||
err := json.Unmarshal(patchData, &patchMap)
|
||||
require.NoError(t, err)
|
||||
|
||||
metadata, hasMetadata := patchMap["metadata"].(map[string]any)
|
||||
require.True(t, hasMetadata, "patch should have metadata")
|
||||
|
||||
annotations, hasAnnotations := metadata["annotations"].(map[string]any)
|
||||
require.True(t, hasAnnotations, "patch should have annotations")
|
||||
|
||||
// Check refresh annotation
|
||||
refreshValue, hasRefresh := annotations["argocd.argoproj.io/refresh"]
|
||||
if expectRefresh {
|
||||
assert.True(t, hasRefresh, "should have refresh annotation")
|
||||
assert.Equal(t, "normal", refreshValue, "refresh annotation should be 'normal'")
|
||||
} else {
|
||||
assert.False(t, hasRefresh, "should not have refresh annotation")
|
||||
}
|
||||
|
||||
// Check hydrate annotation
|
||||
hydrateValue, hasHydrate := annotations["argocd.argoproj.io/hydrate"]
|
||||
if expectHydrate {
|
||||
assert.True(t, hasHydrate, "should have hydrate annotation")
|
||||
assert.Equal(t, "normal", hydrateValue, "hydrate annotation should be 'normal'")
|
||||
} else {
|
||||
assert.False(t, hasHydrate, "should not have hydrate annotation")
|
||||
}
|
||||
}
|
||||
|
||||
// setupTestCache is a helper that creates and populates a test cache
|
||||
func setupTestCache(t *testing.T, repoCache *cache.Cache, appName string, source *v1alpha1.ApplicationSource, manifests []string) {
|
||||
t.Helper()
|
||||
clusterInfo := &mockClusterInfo{}
|
||||
dummyManifests := &cache.CachedManifestResponse{
|
||||
ManifestResponse: &apiclient.ManifestResponse{
|
||||
Revision: testBeforeSHA,
|
||||
Manifests: manifests,
|
||||
Namespace: "",
|
||||
Server: testClusterURL,
|
||||
},
|
||||
}
|
||||
err := repoCache.SetManifests(testBeforeSHA, source, nil, clusterInfo, "", "", testAppLabelKey, appName, dummyManifests, nil, "")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user