Compare commits

..

13 Commits

Author SHA1 Message Date
argoproj-renovate[bot]
f04ca4a967 chore(deps): update group node
Signed-off-by: renovate[bot] <renovate[bot]@users.noreply.github.com>
2025-09-17 18:05:40 +00:00
dependabot[bot]
9ef837c326 chore(deps): bump github.com/Azure/azure-sdk-for-go/sdk/azidentity from 1.11.0 to 1.12.0 (#24593)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-17 13:44:21 -04:00
argoproj-renovate[bot]
c11d35a20f chore(deps): update dependency gotestyourself/gotestsum to v1.13.0 (#24610)
Signed-off-by: renovate[bot] <renovate[bot]@users.noreply.github.com>
Co-authored-by: argoproj-renovate[bot] <161757507+argoproj-renovate[bot]@users.noreply.github.com>
2025-09-17 13:43:28 -04:00
renovate[bot]
a7a07e2cd8 chore(deps): update dependency normalize-url to v4.5.1 [security] (#24607)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-17 13:40:30 -04:00
argoproj-renovate[bot]
9faa6098ed chore(deps): update dependency markdown to v3.9 (#24611)
Signed-off-by: renovate[bot] <renovate[bot]@users.noreply.github.com>
Co-authored-by: argoproj-renovate[bot] <161757507+argoproj-renovate[bot]@users.noreply.github.com>
2025-09-17 13:21:32 -04:00
argoproj-renovate[bot]
0fb6c51f9d chore(deps): update group golang to v1.25.1 (#24605)
Signed-off-by: renovate[bot] <renovate[bot]@users.noreply.github.com>
Co-authored-by: argoproj-renovate[bot] <161757507+argoproj-renovate[bot]@users.noreply.github.com>
2025-09-17 13:13:51 -04:00
Siva Sathwik Kommi
dbef22c843 fix: Fixed inconsistent alignment of titles and headings in status panel (#23160)
Signed-off-by: sivasath16 <sivasathwik.kommi@gmail.com>
Signed-off-by: Siva Sathwik Kommi <sivasathwik.kommi@gmail.com>
2025-09-17 21:33:02 +05:30
Michael Crenshaw
47142b89f4 chore(ci): enable Renovate (#24602)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2025-09-17 16:02:30 +00:00
José Maia
98a22612dd docs: Delete dangling word in Source Hydrator docs (#24601)
Signed-off-by: José Maia <josecbmaia@hotmail.com>
2025-09-17 11:34:22 -04:00
Blake Pettersson
6cce4b29b9 chore(ci): don't run renovate on forks (#24600)
Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
2025-09-17 09:43:13 -04:00
Revital Barletz
9087ad7282 docs: fix inconsistency in application health example (#24585)
Signed-off-by: Revital Barletz <Revital.barletz@octopus.com>
Signed-off-by: Dan Garfield <dan@codefresh.io>
Co-authored-by: Dan Garfield <dan@codefresh.io>
2025-09-17 09:08:13 +00:00
Alexandre Gaudreault
c377101491 fix(appset): progressive sync loop when application has sync errors (#24507)
Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
2025-09-16 17:56:55 +00:00
Papapetrou Patroklos
1d13ebc372 chore: bumps redis version to 8.2.1 (#24523)
Signed-off-by: Patroklos Papapetrou <ppapapetrou76@gmail.com>
2025-09-16 09:46:25 -04:00
108 changed files with 2773 additions and 5833 deletions

View File

@@ -14,7 +14,7 @@ on:
env:
# Golang version to use across CI steps
# renovate: datasource=golang-version packageName=golang
GOLANG_VERSION: '1.25.0'
GOLANG_VERSION: '1.25.1'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -308,7 +308,7 @@ jobs:
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
# renovate: datasource=node-version packageName=node versioning=node
node-version: '22.9.0'
node-version: '22.19.0'
- name: Restore node dependency cache
id: cache-dependencies
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
@@ -407,7 +407,7 @@ jobs:
test-e2e:
name: Run end-to-end tests
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: oracle-vm-16cpu-64gb-x86-64
runs-on: ubuntu-latest-16-cores
strategy:
fail-fast: false
matrix:
@@ -426,7 +426,7 @@ jobs:
- build-go
- changes
env:
GOPATH: /home/ubuntu/go
GOPATH: /home/runner/go
ARGOCD_FAKE_IN_CLUSTER: 'true'
ARGOCD_SSH_DATA_PATH: '/tmp/argo-e2e/app/config/ssh'
ARGOCD_TLS_DATA_PATH: '/tmp/argo-e2e/app/config/tls'
@@ -462,9 +462,9 @@ jobs:
set -x
curl -sfL https://get.k3s.io | sh -
sudo chmod -R a+rw /etc/rancher/k3s
sudo mkdir -p $HOME/.kube && sudo chown -R ubuntu $HOME/.kube
sudo mkdir -p $HOME/.kube && sudo chown -R runner $HOME/.kube
sudo k3s kubectl config view --raw > $HOME/.kube/config
sudo chown ubuntu $HOME/.kube/config
sudo chown runner $HOME/.kube/config
sudo chmod go-r $HOME/.kube/config
kubectl version
- name: Restore go build cache
@@ -474,7 +474,7 @@ jobs:
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
- name: Add ~/go/bin to PATH
run: |
echo "/home/ubuntu/go/bin" >> $GITHUB_PATH
echo "/home/runner/go/bin" >> $GITHUB_PATH
- name: Add /usr/local/bin to PATH
run: |
echo "/usr/local/bin" >> $GITHUB_PATH
@@ -496,11 +496,11 @@ jobs:
run: |
docker pull ghcr.io/dexidp/dex:v2.43.0
docker pull argoproj/argo-cd-ci-builder:v1.0.0
docker pull redis:8.2.2-alpine
docker pull redis:8.2.1-alpine
- name: Create target directory for binaries in the build-process
run: |
mkdir -p dist
chown ubuntu dist
chown runner dist
- name: Run E2E server and wait for it being available
timeout-minutes: 30
run: |

View File

@@ -53,7 +53,7 @@ jobs:
with:
# Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations)
# renovate: datasource=golang-version packageName=golang
go-version: 1.25.0
go-version: 1.25.1
platforms: ${{ needs.set-vars.outputs.platforms }}
push: false
@@ -70,7 +70,7 @@ jobs:
ghcr_image_name: ghcr.io/argoproj/argo-cd/argocd:${{ needs.set-vars.outputs.image-tag }}
# Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations)
# renovate: datasource=golang-version packageName=golang
go-version: 1.25.0
go-version: 1.25.1
platforms: ${{ needs.set-vars.outputs.platforms }}
push: true
secrets:

View File

@@ -11,7 +11,7 @@ permissions: {}
env:
# renovate: datasource=golang-version packageName=golang
GOLANG_VERSION: '1.25.0' # Note: go-version must also be set in job argocd-image.with.go-version
GOLANG_VERSION: '1.25.1' # Note: go-version must also be set in job argocd-image.with.go-version
jobs:
argocd-image:
@@ -25,49 +25,13 @@ jobs:
quay_image_name: quay.io/argoproj/argocd:${{ github.ref_name }}
# Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations)
# renovate: datasource=golang-version packageName=golang
go-version: 1.25.0
go-version: 1.25.1
platforms: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
push: true
secrets:
quay_username: ${{ secrets.RELEASE_QUAY_USERNAME }}
quay_password: ${{ secrets.RELEASE_QUAY_TOKEN }}
setup-variables:
name: Setup Release Variables
if: github.repository == 'argoproj/argo-cd'
runs-on: ubuntu-22.04
outputs:
is_pre_release: ${{ steps.var.outputs.is_pre_release }}
is_latest_release: ${{ steps.var.outputs.is_latest_release }}
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup variables
id: var
run: |
set -xue
# Fetch all tag information
git fetch --prune --tags --force
LATEST_RELEASE_TAG=$(git -c 'versionsort.suffix=-rc' tag --list --sort=version:refname | grep -v '-' | tail -n1)
PRE_RELEASE=false
# Check if latest tag is a pre-release
if echo ${{ github.ref_name }} | grep -E -- '-rc[0-9]+$';then
PRE_RELEASE=true
fi
IS_LATEST=false
# Ensure latest release tag matches github.ref_name
if [[ $LATEST_RELEASE_TAG == ${{ github.ref_name }} ]];then
IS_LATEST=true
fi
echo "is_pre_release=$PRE_RELEASE" >> $GITHUB_OUTPUT
echo "is_latest_release=$IS_LATEST" >> $GITHUB_OUTPUT
argocd-image-provenance:
needs: [argocd-image]
permissions:
@@ -86,17 +50,15 @@ jobs:
goreleaser:
needs:
- setup-variables
- argocd-image
- argocd-image-provenance
permissions:
contents: write # used for uploading assets
if: github.repository == 'argoproj/argo-cd'
runs-on: ubuntu-22.04
env:
GORELEASER_MAKE_LATEST: ${{ needs.setup-variables.outputs.is_latest_release }}
outputs:
hashes: ${{ steps.hash.outputs.hashes }}
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
@@ -180,7 +142,7 @@ jobs:
permissions:
contents: write # Needed for release uploads
outputs:
hashes: ${{ steps.sbom-hash.outputs.hashes }}
hashes: ${{ steps.sbom-hash.outputs.hashes}}
if: github.repository == 'argoproj/argo-cd'
runs-on: ubuntu-22.04
steps:
@@ -259,7 +221,6 @@ jobs:
post-release:
needs:
- setup-variables
- argocd-image
- goreleaser
- generate-sbom
@@ -268,8 +229,6 @@ jobs:
pull-requests: write # Needed to create PR for VERSION update.
if: github.repository == 'argoproj/argo-cd'
runs-on: ubuntu-22.04
env:
TAG_STABLE: ${{ needs.setup-variables.outputs.is_latest_release }}
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
@@ -283,6 +242,27 @@ jobs:
git config --global user.email 'ci@argoproj.com'
git config --global user.name 'CI'
- name: Check if tag is the latest version and not a pre-release
run: |
set -xue
# Fetch all tag information
git fetch --prune --tags --force
LATEST_TAG=$(git -c 'versionsort.suffix=-rc' tag --list --sort=version:refname | tail -n1)
PRE_RELEASE=false
# Check if latest tag is a pre-release
if echo $LATEST_TAG | grep -E -- '-rc[0-9]+$';then
PRE_RELEASE=true
fi
# Ensure latest tag matches github.ref_name & not a pre-release
if [[ $LATEST_TAG == ${{ github.ref_name }} ]] && [[ $PRE_RELEASE != 'true' ]];then
echo "TAG_STABLE=true" >> $GITHUB_ENV
else
echo "TAG_STABLE=false" >> $GITHUB_ENV
fi
- name: Update stable tag to latest version
run: |
git tag -f stable ${{ github.ref_name }}

View File

@@ -10,6 +10,7 @@ permissions:
jobs:
renovate:
runs-on: ubuntu-latest
if: github.repository == 'argoproj/argo-cd'
steps:
- name: Get token
id: get_token

View File

@@ -49,14 +49,13 @@ archives:
- argocd-cli
name_template: |-
{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}
formats: [binary]
formats: [ binary ]
checksum:
name_template: 'cli_checksums.txt'
algorithm: sha256
release:
make_latest: '{{ .Env.GORELEASER_MAKE_LATEST }}'
prerelease: auto
draft: false
header: |

View File

@@ -24,6 +24,7 @@ packages:
Renderer: {}
github.com/argoproj/argo-cd/v3/commitserver/apiclient:
interfaces:
Clientset: {}
CommitServiceClient: {}
github.com/argoproj/argo-cd/v3/commitserver/commit:
interfaces:
@@ -34,7 +35,6 @@ packages:
github.com/argoproj/argo-cd/v3/controller/hydrator:
interfaces:
Dependencies: {}
RepoGetter: {}
github.com/argoproj/argo-cd/v3/pkg/apiclient/cluster:
interfaces:
ClusterServiceServer: {}

View File

@@ -4,7 +4,7 @@ ARG BASE_IMAGE=docker.io/library/ubuntu:25.04@sha256:10bb10bb062de665d4dc3e0ea36
# Initial stage which pulls prepares build dependencies and CLI tooling we need for our final image
# Also used as the image in CI jobs so needs all dependencies
####################################################################################################
FROM docker.io/library/golang:1.25.0@sha256:9e56f0d0f043a68bb8c47c819e47dc29f6e8f5129b8885bed9d43f058f7f3ed6 AS builder
FROM docker.io/library/golang:1.25.1@sha256:8305f5fa8ea63c7b5bc85bd223ccc62941f852318ebfbd22f53bbd0b358c07e1 AS builder
WORKDIR /tmp
@@ -85,7 +85,7 @@ WORKDIR /home/argocd
####################################################################################################
# Argo CD UI stage
####################################################################################################
FROM --platform=$BUILDPLATFORM docker.io/library/node:23.0.0@sha256:e643c0b70dca9704dff42e12b17f5b719dbe4f95e6392fc2dfa0c5f02ea8044d AS argocd-ui
FROM --platform=$BUILDPLATFORM docker.io/library/node:23.11.1@sha256:9a25b5a6f9a90218b73a62205f111e71de5e4289aee952b4dd7e86f7498f2544 AS argocd-ui
WORKDIR /src
COPY ["ui/package.json", "ui/yarn.lock", "./"]
@@ -103,7 +103,7 @@ RUN HOST_ARCH=$TARGETARCH NODE_ENV='production' NODE_ONLINE_ENV='online' NODE_OP
####################################################################################################
# Argo CD Build stage which performs the actual build of Argo CD binaries
####################################################################################################
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.25.0@sha256:9e56f0d0f043a68bb8c47c819e47dc29f6e8f5129b8885bed9d43f058f7f3ed6 AS argocd-build
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.25.1@sha256:8305f5fa8ea63c7b5bc85bd223ccc62941f852318ebfbd22f53bbd0b358c07e1 AS argocd-build
WORKDIR /go/src/github.com/argoproj/argo-cd

View File

@@ -1,4 +1,4 @@
FROM docker.io/library/golang:1.25.0@sha256:9e56f0d0f043a68bb8c47c819e47dc29f6e8f5129b8885bed9d43f058f7f3ed6
FROM docker.io/library/golang:1.25.1@sha256:8305f5fa8ea63c7b5bc85bd223ccc62941f852318ebfbd22f53bbd0b358c07e1
ENV DEBIAN_FRONTEND=noninteractive

View File

@@ -37,6 +37,7 @@ import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/retry"
"k8s.io/utils/ptr"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -46,6 +47,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"github.com/argoproj/gitops-engine/pkg/health"
"github.com/argoproj/argo-cd/v3/applicationset/controllers/template"
"github.com/argoproj/argo-cd/v3/applicationset/generators"
"github.com/argoproj/argo-cd/v3/applicationset/metrics"
@@ -100,7 +103,6 @@ type ApplicationSetReconciler struct {
GlobalPreservedAnnotations []string
GlobalPreservedLabels []string
Metrics *metrics.ApplicationsetMetrics
MaxResourcesStatusCount int
}
// +kubebuilder:rbac:groups=argoproj.io,resources=applicationsets,verbs=get;list;watch;create;update;patch;delete
@@ -227,8 +229,6 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
return ctrl.Result{}, fmt.Errorf("failed to get update resources status for application set: %w", err)
}
// appMap is a name->app collection of Applications in this ApplicationSet.
appMap := map[string]argov1alpha1.Application{}
// appSyncMap tracks which apps will be synced during this reconciliation.
appSyncMap := map[string]bool{}
@@ -242,33 +242,11 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
return ctrl.Result{}, fmt.Errorf("failed to clear previous AppSet application statuses for %v: %w", applicationSetInfo.Name, err)
}
} else if isRollingSyncStrategy(&applicationSetInfo) {
// The appset uses progressive sync with `RollingSync` strategy
for _, app := range currentApplications {
appMap[app.Name] = app
}
appSyncMap, err = r.performProgressiveSyncs(ctx, logCtx, applicationSetInfo, currentApplications, generatedApplications, appMap)
appSyncMap, err = r.performProgressiveSyncs(ctx, logCtx, applicationSetInfo, currentApplications, generatedApplications)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to perform progressive sync reconciliation for application set: %w", err)
}
}
} else {
// Progressive Sync is disabled, clear any existing applicationStatus to prevent stale data
if len(applicationSetInfo.Status.ApplicationStatus) > 0 {
logCtx.Infof("Progressive Sync disabled, removing %v AppStatus entries from ApplicationSet %v", len(applicationSetInfo.Status.ApplicationStatus), applicationSetInfo.Name)
err := r.setAppSetApplicationStatus(ctx, logCtx, &applicationSetInfo, []argov1alpha1.ApplicationSetApplicationStatus{})
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to clear AppSet application statuses when Progressive Sync is disabled for %v: %w", applicationSetInfo.Name, err)
}
}
}
var validApps []argov1alpha1.Application
for i := range generatedApplications {
if validateErrors[generatedApplications[i].QualifiedName()] == nil {
validApps = append(validApps, generatedApplications[i])
}
}
if len(validateErrors) > 0 {
@@ -298,13 +276,25 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
)
}
var validApps []argov1alpha1.Application
for i := range generatedApplications {
if validateErrors[generatedApplications[i].QualifiedName()] == nil {
validApps = append(validApps, generatedApplications[i])
}
}
if r.EnableProgressiveSyncs {
// trigger appropriate application syncs if RollingSync strategy is enabled
if progressiveSyncsRollingSyncStrategyEnabled(&applicationSetInfo) {
validApps = r.syncValidApplications(logCtx, &applicationSetInfo, appSyncMap, appMap, validApps)
validApps = r.syncDesiredApplications(logCtx, &applicationSetInfo, appSyncMap, validApps)
}
}
// Sort apps by name so they are updated/created in the same order, and condition errors are the same
sort.Slice(validApps, func(i, j int) bool {
return validApps[i].Name < validApps[j].Name
})
if utils.DefaultPolicy(applicationSetInfo.Spec.SyncPolicy, r.Policy, r.EnablePolicyOverride).AllowUpdate() {
err = r.createOrUpdateInCluster(ctx, logCtx, applicationSetInfo, validApps)
if err != nil {
@@ -336,6 +326,7 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
}
if utils.DefaultPolicy(applicationSetInfo.Spec.SyncPolicy, r.Policy, r.EnablePolicyOverride).AllowDelete() {
// Delete the generatedApplications instead of the validApps because we want to be able to delete applications in error/invalid state
err = r.deleteInCluster(ctx, logCtx, applicationSetInfo, generatedApplications)
if err != nil {
_ = r.setApplicationSetStatusCondition(ctx,
@@ -942,7 +933,7 @@ func (r *ApplicationSetReconciler) removeOwnerReferencesOnDeleteAppSet(ctx conte
return nil
}
func (r *ApplicationSetReconciler) performProgressiveSyncs(ctx context.Context, logCtx *log.Entry, appset argov1alpha1.ApplicationSet, applications []argov1alpha1.Application, desiredApplications []argov1alpha1.Application, appMap map[string]argov1alpha1.Application) (map[string]bool, error) {
func (r *ApplicationSetReconciler) performProgressiveSyncs(ctx context.Context, logCtx *log.Entry, appset argov1alpha1.ApplicationSet, applications []argov1alpha1.Application, desiredApplications []argov1alpha1.Application) (map[string]bool, error) {
appDependencyList, appStepMap := r.buildAppDependencyList(logCtx, appset, desiredApplications)
_, err := r.updateApplicationSetApplicationStatus(ctx, logCtx, &appset, applications, appStepMap)
@@ -951,21 +942,21 @@ func (r *ApplicationSetReconciler) performProgressiveSyncs(ctx context.Context,
}
logCtx.Infof("ApplicationSet %v step list:", appset.Name)
for i, step := range appDependencyList {
logCtx.Infof("step %v: %+v", i+1, step)
for stepIndex, applicationNames := range appDependencyList {
logCtx.Infof("step %v: %+v", stepIndex+1, applicationNames)
}
appSyncMap := r.buildAppSyncMap(appset, appDependencyList, appMap)
logCtx.Infof("Application allowed to sync before maxUpdate?: %+v", appSyncMap)
appsToSync := r.getAppsToSync(appset, appDependencyList, applications)
logCtx.Infof("Application allowed to sync before maxUpdate?: %+v", appsToSync)
_, err = r.updateApplicationSetApplicationStatusProgress(ctx, logCtx, &appset, appSyncMap, appStepMap)
_, err = r.updateApplicationSetApplicationStatusProgress(ctx, logCtx, &appset, appsToSync, appStepMap)
if err != nil {
return nil, fmt.Errorf("failed to update applicationset application status progress: %w", err)
}
_ = r.updateApplicationSetApplicationStatusConditions(ctx, &appset)
return appSyncMap, nil
return appsToSync, nil
}
// this list tracks which Applications belong to each RollingUpdate step
@@ -1039,55 +1030,53 @@ func labelMatchedExpression(logCtx *log.Entry, val string, matchExpression argov
return valueMatched
}
// this map is used to determine which stage of Applications are ready to be updated in the reconciler loop
func (r *ApplicationSetReconciler) buildAppSyncMap(applicationSet argov1alpha1.ApplicationSet, appDependencyList [][]string, appMap map[string]argov1alpha1.Application) map[string]bool {
// getAppsToSync returns a Map of Applications that should be synced in this progressive sync wave
func (r *ApplicationSetReconciler) getAppsToSync(applicationSet argov1alpha1.ApplicationSet, appDependencyList [][]string, currentApplications []argov1alpha1.Application) map[string]bool {
appSyncMap := map[string]bool{}
syncEnabled := true
currentAppsMap := map[string]bool{}
// healthy stages and the first non-healthy stage should have sync enabled
// every stage after should have sync disabled
for _, app := range currentApplications {
currentAppsMap[app.Name] = true
}
for i := range appDependencyList {
for stepIndex := range appDependencyList {
// set the syncEnabled boolean for every Application in the current step
for _, appName := range appDependencyList[i] {
appSyncMap[appName] = syncEnabled
for _, appName := range appDependencyList[stepIndex] {
appSyncMap[appName] = true
}
// detect if we need to halt before progressing to the next step
for _, appName := range appDependencyList[i] {
// evaluate if we need to sync next waves
syncNextWave := true
for _, appName := range appDependencyList[stepIndex] {
// Check if application is created and managed by this AppSet, if it is not created yet, we cannot progress
if _, ok := currentAppsMap[appName]; !ok {
syncNextWave = false
break
}
idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, appName)
if idx == -1 {
// no Application status found, likely because the Application is being newly created
syncEnabled = false
// No Application status found, likely because the Application is being newly created
// This mean this wave is not yet completed
syncNextWave = false
break
}
appStatus := applicationSet.Status.ApplicationStatus[idx]
app, ok := appMap[appName]
if !ok {
// application name not found in the list of applications managed by this ApplicationSet, maybe because it's being deleted
syncEnabled = false
break
}
syncEnabled = appSyncEnabledForNextStep(&applicationSet, app, appStatus)
if !syncEnabled {
if appStatus.Status != argov1alpha1.ProgressiveSyncHealthy {
// At least one application in this wave is not yet healthy. We cannot proceed to the next wave
syncNextWave = false
break
}
}
if !syncNextWave {
break
}
}
return appSyncMap
}
func appSyncEnabledForNextStep(appset *argov1alpha1.ApplicationSet, app argov1alpha1.Application, appStatus argov1alpha1.ApplicationSetApplicationStatus) bool {
if progressiveSyncsRollingSyncStrategyEnabled(appset) {
// we still need to complete the current step if the Application is not yet Healthy or there are still pending Application changes
return isApplicationHealthy(app) && appStatus.Status == "Healthy"
}
return true
}
func isRollingSyncStrategy(appset *argov1alpha1.ApplicationSet) bool {
// It's only RollingSync if the type specifically sets it
return appset.Spec.Strategy != nil && appset.Spec.Strategy.Type == "RollingSync" && appset.Spec.Strategy.RollingSync != nil
@@ -1098,29 +1087,21 @@ func progressiveSyncsRollingSyncStrategyEnabled(appset *argov1alpha1.Application
return isRollingSyncStrategy(appset) && len(appset.Spec.Strategy.RollingSync.Steps) > 0
}
func isProgressiveSyncDeletionOrderReversed(appset *argov1alpha1.ApplicationSet) bool {
// When progressive sync is enabled + deletionOrder is set to Reverse (case-insensitive)
return progressiveSyncsRollingSyncStrategyEnabled(appset) && strings.EqualFold(appset.Spec.Strategy.DeletionOrder, ReverseDeletionOrder)
}
func isApplicationHealthy(app argov1alpha1.Application) bool {
healthStatusString, syncStatusString, operationPhaseString := statusStrings(app)
if healthStatusString == "Healthy" && syncStatusString != "OutOfSync" && (operationPhaseString == "Succeeded" || operationPhaseString == "") {
return true
func isApplicationWithError(app argov1alpha1.Application) bool {
for _, condition := range app.Status.Conditions {
if condition.Type == argov1alpha1.ApplicationConditionInvalidSpecError {
return true
}
if condition.Type == argov1alpha1.ApplicationConditionUnknownError {
return true
}
}
return false
}
func statusStrings(app argov1alpha1.Application) (string, string, string) {
healthStatusString := string(app.Status.Health.Status)
syncStatusString := string(app.Status.Sync.Status)
operationPhaseString := ""
if app.Status.OperationState != nil {
operationPhaseString = string(app.Status.OperationState.Phase)
}
return healthStatusString, syncStatusString, operationPhaseString
func isProgressiveSyncDeletionOrderReversed(appset *argov1alpha1.ApplicationSet) bool {
// When progressive sync is enabled + deletionOrder is set to Reverse (case-insensitive)
return progressiveSyncsRollingSyncStrategyEnabled(appset) && strings.EqualFold(appset.Spec.Strategy.DeletionOrder, ReverseDeletionOrder)
}
func getAppStep(appName string, appStepMap map[string]int) int {
@@ -1139,81 +1120,112 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
appStatuses := make([]argov1alpha1.ApplicationSetApplicationStatus, 0, len(applications))
for _, app := range applications {
healthStatusString, syncStatusString, operationPhaseString := statusStrings(app)
idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, app.Name)
appHealthStatus := app.Status.Health.Status
appSyncStatus := app.Status.Sync.Status
currentAppStatus := argov1alpha1.ApplicationSetApplicationStatus{}
idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, app.Name)
if idx == -1 {
// AppStatus not found, set default status of "Waiting"
currentAppStatus = argov1alpha1.ApplicationSetApplicationStatus{
Application: app.Name,
TargetRevisions: app.Status.GetRevisions(),
LastTransitionTime: &now,
Message: "No Application status found, defaulting status to Waiting.",
Status: "Waiting",
Message: "No Application status found, defaulting status to Waiting",
Status: argov1alpha1.ProgressiveSyncWaiting,
Step: strconv.Itoa(getAppStep(app.Name, appStepMap)),
}
} else {
// we have an existing AppStatus
currentAppStatus = applicationSet.Status.ApplicationStatus[idx]
if !reflect.DeepEqual(currentAppStatus.TargetRevisions, app.Status.GetRevisions()) {
currentAppStatus.Message = "Application has pending changes, setting status to Waiting."
}
}
statusLogCtx := logCtx.WithFields(log.Fields{
"app.name": currentAppStatus.Application,
"app.health": appHealthStatus,
"app.sync": appSyncStatus,
"status.status": currentAppStatus.Status,
"status.message": currentAppStatus.Message,
"status.step": currentAppStatus.Step,
"status.targetRevisions": strings.Join(currentAppStatus.TargetRevisions, ","),
})
newAppStatus := currentAppStatus.DeepCopy()
newAppStatus.Step = strconv.Itoa(getAppStep(newAppStatus.Application, appStepMap))
if !reflect.DeepEqual(currentAppStatus.TargetRevisions, app.Status.GetRevisions()) {
currentAppStatus.TargetRevisions = app.Status.GetRevisions()
currentAppStatus.Status = "Waiting"
currentAppStatus.LastTransitionTime = &now
currentAppStatus.Step = strconv.Itoa(getAppStep(currentAppStatus.Application, appStepMap))
// A new version is available in the application and we need to re-sync the application
newAppStatus.TargetRevisions = app.Status.GetRevisions()
newAppStatus.Message = "Application has pending changes, setting status to Waiting"
newAppStatus.Status = argov1alpha1.ProgressiveSyncWaiting
newAppStatus.LastTransitionTime = &now
}
appOutdated := false
if progressiveSyncsRollingSyncStrategyEnabled(applicationSet) {
appOutdated = syncStatusString == "OutOfSync"
}
if newAppStatus.Status == argov1alpha1.ProgressiveSyncWaiting {
// App has changed to waiting because the TargetRevisions changed or it is a new selected app
// This does not mean we should always sync the app. The app may not be OutOfSync
// and may not require a sync if it does not have differences.
if appSyncStatus == argov1alpha1.SyncStatusCodeSynced {
if app.Status.Health.Status == health.HealthStatusHealthy {
newAppStatus.LastTransitionTime = &now
newAppStatus.Status = argov1alpha1.ProgressiveSyncHealthy
newAppStatus.Message = "Application resource has synced, updating status to Healthy"
} else {
newAppStatus.LastTransitionTime = &now
newAppStatus.Status = argov1alpha1.ProgressiveSyncProgressing
newAppStatus.Message = "Application resource has synced, updating status to Progressing"
}
}
} else {
// The target revision is the same, so we need to evaluate the current revision progress
if currentAppStatus.Status == argov1alpha1.ProgressiveSyncPending {
// No need to evaluate status health further if the application did not change since our last transition
if app.Status.ReconciledAt == nil || (newAppStatus.LastTransitionTime != nil && app.Status.ReconciledAt.After(newAppStatus.LastTransitionTime.Time)) {
// Validate that at least one sync was trigerred after the pending transition time
if app.Status.OperationState != nil && app.Status.OperationState.StartedAt.After(currentAppStatus.LastTransitionTime.Time) {
statusLogCtx = statusLogCtx.WithField("app.operation", app.Status.OperationState.Phase)
newAppStatus.LastTransitionTime = &now
newAppStatus.Status = argov1alpha1.ProgressiveSyncProgressing
if appOutdated && currentAppStatus.Status != "Waiting" && currentAppStatus.Status != "Pending" {
logCtx.Infof("Application %v is outdated, updating its ApplicationSet status to Waiting", app.Name)
currentAppStatus.LastTransitionTime = &now
currentAppStatus.Status = "Waiting"
currentAppStatus.Message = "Application has pending changes, setting status to Waiting."
currentAppStatus.Step = strconv.Itoa(getAppStep(currentAppStatus.Application, appStepMap))
}
switch {
case app.Status.OperationState.Phase.Successful():
newAppStatus.Message = "Application resource completed a sync successfully, updating status from Pending to Progressing"
case app.Status.OperationState.Phase.Completed():
newAppStatus.Message = "Application resource completed a sync, updating status from Pending to Progressing"
default:
// If a sync fails or has errors, the Application should be configured with retry. It is not the appset's job to retry failed syncs
newAppStatus.Message = "Application resource became Progressing, updating status from Pending to Progressing"
}
} else if isApplicationWithError(app) {
// Validate if the application has errors preventing it to be reconciled and perform syncs
// If it does, we move it to progressing.
newAppStatus.LastTransitionTime = &now
newAppStatus.Status = argov1alpha1.ProgressiveSyncProgressing
newAppStatus.Message = "Application resource has error and cannot sync, updating status to Progressing"
}
}
}
if currentAppStatus.Status == "Pending" {
if !appOutdated && operationPhaseString == "Succeeded" {
logCtx.Infof("Application %v has completed a sync successfully, updating its ApplicationSet status to Progressing", app.Name)
currentAppStatus.LastTransitionTime = &now
currentAppStatus.Status = "Progressing"
currentAppStatus.Message = "Application resource completed a sync successfully, updating status from Pending to Progressing."
currentAppStatus.Step = strconv.Itoa(getAppStep(currentAppStatus.Application, appStepMap))
} else if operationPhaseString == "Running" || healthStatusString == "Progressing" {
logCtx.Infof("Application %v has entered Progressing status, updating its ApplicationSet status to Progressing", app.Name)
currentAppStatus.LastTransitionTime = &now
currentAppStatus.Status = "Progressing"
currentAppStatus.Message = "Application resource became Progressing, updating status from Pending to Progressing."
currentAppStatus.Step = strconv.Itoa(getAppStep(currentAppStatus.Application, appStepMap))
if currentAppStatus.Status == argov1alpha1.ProgressiveSyncProgressing {
// If the status has reached progressing, we know a sync has been triggered. No matter the result of that operation,
// we want an the app to reach the Healthy state for the current revision.
if appHealthStatus == health.HealthStatusHealthy && appSyncStatus == argov1alpha1.SyncStatusCodeSynced {
newAppStatus.LastTransitionTime = &now
newAppStatus.Status = argov1alpha1.ProgressiveSyncHealthy
newAppStatus.Message = "Application resource became Healthy, updating status from Progressing to Healthy"
}
}
}
if currentAppStatus.Status == "Waiting" && isApplicationHealthy(app) {
logCtx.Infof("Application %v is already synced and healthy, updating its ApplicationSet status to Healthy", app.Name)
currentAppStatus.LastTransitionTime = &now
currentAppStatus.Status = healthStatusString
currentAppStatus.Message = "Application resource is already Healthy, updating status from Waiting to Healthy."
currentAppStatus.Step = strconv.Itoa(getAppStep(currentAppStatus.Application, appStepMap))
if newAppStatus.LastTransitionTime == &now {
statusLogCtx.WithFields(log.Fields{
"new_status.status": newAppStatus.Status,
"new_status.message": newAppStatus.Message,
"new_status.step": newAppStatus.Step,
"new_status.targetRevisions": strings.Join(newAppStatus.TargetRevisions, ","),
}).Info("Progressive sync application changed status")
}
if currentAppStatus.Status == "Progressing" && isApplicationHealthy(app) {
logCtx.Infof("Application %v has completed Progressing status, updating its ApplicationSet status to Healthy", app.Name)
currentAppStatus.LastTransitionTime = &now
currentAppStatus.Status = healthStatusString
currentAppStatus.Message = "Application resource became Healthy, updating status from Progressing to Healthy."
currentAppStatus.Step = strconv.Itoa(getAppStep(currentAppStatus.Application, appStepMap))
}
appStatuses = append(appStatuses, currentAppStatus)
appStatuses = append(appStatuses, *newAppStatus)
}
err := r.setAppSetApplicationStatus(ctx, logCtx, applicationSet, appStatuses)
@@ -1225,7 +1237,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
}
// check Applications that are in Waiting status and promote them to Pending if needed
func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress(ctx context.Context, logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appStepMap map[string]int) ([]argov1alpha1.ApplicationSetApplicationStatus, error) {
func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress(ctx context.Context, logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, appsToSync map[string]bool, appStepMap map[string]int) ([]argov1alpha1.ApplicationSetApplicationStatus, error) {
now := metav1.Now()
appStatuses := make([]argov1alpha1.ApplicationSetApplicationStatus, 0, len(applicationSet.Status.ApplicationStatus))
@@ -1241,12 +1253,20 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
for _, appStatus := range applicationSet.Status.ApplicationStatus {
totalCountMap[appStepMap[appStatus.Application]]++
if appStatus.Status == "Pending" || appStatus.Status == "Progressing" {
if appStatus.Status == argov1alpha1.ProgressiveSyncPending || appStatus.Status == argov1alpha1.ProgressiveSyncProgressing {
updateCountMap[appStepMap[appStatus.Application]]++
}
}
for _, appStatus := range applicationSet.Status.ApplicationStatus {
statusLogCtx := logCtx.WithFields(log.Fields{
"app.name": appStatus.Application,
"status.status": appStatus.Status,
"status.message": appStatus.Message,
"status.step": appStatus.Step,
"status.targetRevisions": strings.Join(appStatus.TargetRevisions, ","),
})
maxUpdateAllowed := true
maxUpdate := &intstr.IntOrString{}
if progressiveSyncsRollingSyncStrategyEnabled(applicationSet) {
@@ -1257,7 +1277,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
if maxUpdate != nil {
maxUpdateVal, err := intstr.GetScaledValueFromIntOrPercent(maxUpdate, totalCountMap[appStepMap[appStatus.Application]], false)
if err != nil {
logCtx.Warnf("AppSet '%v' has a invalid maxUpdate value '%+v', ignoring maxUpdate logic for this step: %v", applicationSet.Name, maxUpdate, err)
statusLogCtx.Warnf("AppSet has a invalid maxUpdate value '%+v', ignoring maxUpdate logic for this step: %v", maxUpdate, err)
}
// ensure that percentage values greater than 0% always result in at least 1 Application being selected
@@ -1267,16 +1287,21 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
if updateCountMap[appStepMap[appStatus.Application]] >= maxUpdateVal {
maxUpdateAllowed = false
logCtx.Infof("Application %v is not allowed to update yet, %v/%v Applications already updating in step %v in AppSet %v", appStatus.Application, updateCountMap[appStepMap[appStatus.Application]], maxUpdateVal, getAppStep(appStatus.Application, appStepMap), applicationSet.Name)
statusLogCtx.Infof("Application is not allowed to update yet, %v/%v Applications already updating in step %v", updateCountMap[appStepMap[appStatus.Application]], maxUpdateVal, getAppStep(appStatus.Application, appStepMap))
}
}
if appStatus.Status == "Waiting" && appSyncMap[appStatus.Application] && maxUpdateAllowed {
logCtx.Infof("Application %v moved to Pending status, watching for the Application to start Progressing", appStatus.Application)
if appStatus.Status == argov1alpha1.ProgressiveSyncWaiting && appsToSync[appStatus.Application] && maxUpdateAllowed {
appStatus.LastTransitionTime = &now
appStatus.Status = "Pending"
appStatus.Message = "Application moved to Pending status, watching for the Application resource to start Progressing."
appStatus.Step = strconv.Itoa(getAppStep(appStatus.Application, appStepMap))
appStatus.Status = argov1alpha1.ProgressiveSyncPending
appStatus.Message = "Application moved to Pending status, watching for the Application resource to start Progressing"
statusLogCtx.WithFields(log.Fields{
"new_status.status": appStatus.Status,
"new_status.message": appStatus.Message,
"new_status.step": appStatus.Step,
"new_status.targetRevisions": strings.Join(appStatus.TargetRevisions, ","),
}).Info("Progressive sync application changed status")
updateCountMap[appStepMap[appStatus.Application]]++
}
@@ -1301,9 +1326,9 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusConditio
completedWaves := map[string]bool{}
for _, appStatus := range applicationSet.Status.ApplicationStatus {
if v, ok := completedWaves[appStatus.Step]; !ok {
completedWaves[appStatus.Step] = appStatus.Status == "Healthy"
completedWaves[appStatus.Step] = appStatus.Status == argov1alpha1.ProgressiveSyncHealthy
} else {
completedWaves[appStatus.Step] = v && appStatus.Status == "Healthy"
completedWaves[appStatus.Step] = v && appStatus.Status == argov1alpha1.ProgressiveSyncHealthy
}
}
@@ -1409,13 +1434,7 @@ func (r *ApplicationSetReconciler) updateResourcesStatus(ctx context.Context, lo
sort.Slice(statuses, func(i, j int) bool {
return statuses[i].Name < statuses[j].Name
})
resourcesCount := int64(len(statuses))
if r.MaxResourcesStatusCount > 0 && len(statuses) > r.MaxResourcesStatusCount {
logCtx.Warnf("Truncating ApplicationSet %s resource status from %d to max allowed %d entries", appset.Name, len(statuses), r.MaxResourcesStatusCount)
statuses = statuses[:r.MaxResourcesStatusCount]
}
appset.Status.Resources = statuses
appset.Status.ResourcesCount = resourcesCount
// DefaultRetry will retry 5 times with a backoff factor of 1, jitter of 0.1 and a duration of 10ms
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
namespacedName := types.NamespacedName{Namespace: appset.Namespace, Name: appset.Name}
@@ -1428,7 +1447,6 @@ func (r *ApplicationSetReconciler) updateResourcesStatus(ctx context.Context, lo
}
updatedAppset.Status.Resources = appset.Status.Resources
updatedAppset.Status.ResourcesCount = resourcesCount
// Update the newly fetched object with new status resources
err := r.Client.Status().Update(ctx, updatedAppset)
@@ -1525,30 +1543,31 @@ func (r *ApplicationSetReconciler) setAppSetApplicationStatus(ctx context.Contex
return nil
}
func (r *ApplicationSetReconciler) syncValidApplications(logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appMap map[string]argov1alpha1.Application, validApps []argov1alpha1.Application) []argov1alpha1.Application {
func (r *ApplicationSetReconciler) syncDesiredApplications(logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, appsToSync map[string]bool, desiredApplications []argov1alpha1.Application) []argov1alpha1.Application {
rolloutApps := []argov1alpha1.Application{}
for i := range validApps {
for i := range desiredApplications {
pruneEnabled := false
// ensure that Applications generated with RollingSync do not have an automated sync policy, since the AppSet controller will handle triggering the sync operation instead
if validApps[i].Spec.SyncPolicy != nil && validApps[i].Spec.SyncPolicy.IsAutomatedSyncEnabled() {
pruneEnabled = validApps[i].Spec.SyncPolicy.Automated.Prune
validApps[i].Spec.SyncPolicy.Automated = nil
if desiredApplications[i].Spec.SyncPolicy != nil && desiredApplications[i].Spec.SyncPolicy.IsAutomatedSyncEnabled() {
pruneEnabled = desiredApplications[i].Spec.SyncPolicy.Automated.Prune
desiredApplications[i].Spec.SyncPolicy.Automated.Enabled = ptr.To(false)
}
appSetStatusPending := false
idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, validApps[i].Name)
if idx > -1 && applicationSet.Status.ApplicationStatus[idx].Status == "Pending" {
idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, desiredApplications[i].Name)
if idx > -1 && applicationSet.Status.ApplicationStatus[idx].Status == argov1alpha1.ProgressiveSyncPending {
// only trigger a sync for Applications that are in Pending status, since this is governed by maxUpdate
appSetStatusPending = true
}
// check appSyncMap to determine which Applications are ready to be updated and which should be skipped
if appSyncMap[validApps[i].Name] && appMap[validApps[i].Name].Status.Sync.Status == "OutOfSync" && appSetStatusPending {
logCtx.Infof("triggering sync for application: %v, prune enabled: %v", validApps[i].Name, pruneEnabled)
validApps[i] = syncApplication(validApps[i], pruneEnabled)
// check appsToSync to determine which Applications are ready to be updated and which should be skipped
if appsToSync[desiredApplications[i].Name] && appSetStatusPending {
logCtx.Infof("triggering sync for application: %v, prune enabled: %v", desiredApplications[i].Name, pruneEnabled)
desiredApplications[i] = syncApplication(desiredApplications[i], pruneEnabled)
}
rolloutApps = append(rolloutApps, validApps[i])
rolloutApps = append(rolloutApps, desiredApplications[i])
}
return rolloutApps
}

File diff suppressed because it is too large Load Diff

View File

@@ -26,14 +26,10 @@ import (
"github.com/go-playground/webhooks/v6/github"
"github.com/go-playground/webhooks/v6/gitlab"
log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v3/util/guard"
)
const payloadQueueSize = 50000
const panicMsgAppSet = "panic while processing applicationset-controller webhook event"
type WebhookHandler struct {
sync.WaitGroup // for testing
github *github.Webhook
@@ -106,7 +102,6 @@ func NewWebhookHandler(webhookParallelism int, argocdSettingsMgr *argosettings.S
}
func (h *WebhookHandler) startWorkerPool(webhookParallelism int) {
compLog := log.WithField("component", "applicationset-webhook")
for i := 0; i < webhookParallelism; i++ {
h.Add(1)
go func() {
@@ -116,7 +111,7 @@ func (h *WebhookHandler) startWorkerPool(webhookParallelism int) {
if !ok {
return
}
guard.RecoverAndLog(func() { h.HandleEvent(payload) }, compLog, panicMsgAppSet)
h.HandleEvent(payload)
}
}()
}

11
assets/swagger.json generated
View File

@@ -7077,7 +7077,7 @@
},
"status": {
"type": "string",
"title": "Status contains the AppSet's perceived status of the managed Application resource: (Waiting, Pending, Progressing, Healthy)"
"title": "Status contains the AppSet's perceived status of the managed Application resource"
},
"step": {
"type": "string",
@@ -7322,11 +7322,6 @@
"items": {
"$ref": "#/definitions/applicationv1alpha1ResourceStatus"
}
},
"resourcesCount": {
"description": "ResourcesCount is the total number of resources managed by this application set. The count may be higher than actual number of items in the Resources field when\nthe number of managed resources exceeds the limit imposed by the controller (to avoid making the status field too large).",
"type": "integer",
"format": "int64"
}
}
},
@@ -10577,8 +10572,8 @@
"type": "string"
},
"targetBranch": {
"description": "TargetBranch is the branch from which hydrated manifests will be synced.\nIf HydrateTo is not set, this is also the branch to which hydrated manifests are committed.",
"type": "string"
"type": "string",
"title": "TargetBranch is the branch to which hydrated manifests should be committed"
}
}
},

View File

@@ -14,7 +14,6 @@ import (
"github.com/argoproj/argo-cd/v3/reposerver/apiclient"
logutils "github.com/argoproj/argo-cd/v3/util/log"
"github.com/argoproj/argo-cd/v3/util/profile"
"github.com/argoproj/argo-cd/v3/util/tls"
"github.com/argoproj/argo-cd/v3/applicationset/controllers"
@@ -80,7 +79,6 @@ func NewCommand() *cobra.Command {
enableScmProviders bool
webhookParallelism int
tokenRefStrictMode bool
maxResourcesStatusCount int
)
scheme := runtime.NewScheme()
_ = clientgoscheme.AddToScheme(scheme)
@@ -171,15 +169,6 @@ func NewCommand() *cobra.Command {
log.Error(err, "unable to start manager")
os.Exit(1)
}
pprofMux := http.NewServeMux()
profile.RegisterProfiler(pprofMux)
// This looks a little strange. Eg, not using ctrl.Options PprofBindAddress and then adding the pprof mux
// to the metrics server. However, it allows for the controller to dynamically expose the pprof endpoints
// and use the existing metrics server, the same pattern that the application controller and api-server follow.
if err = mgr.AddMetricsServerExtraHandler("/debug/pprof/", pprofMux); err != nil {
log.Error(err, "failed to register pprof handlers")
}
dynamicClient, err := dynamic.NewForConfig(mgr.GetConfig())
errors.CheckError(err)
k8sClient, err := kubernetes.NewForConfig(mgr.GetConfig())
@@ -242,7 +231,6 @@ func NewCommand() *cobra.Command {
GlobalPreservedAnnotations: globalPreservedAnnotations,
GlobalPreservedLabels: globalPreservedLabels,
Metrics: &metrics,
MaxResourcesStatusCount: maxResourcesStatusCount,
}).SetupWithManager(mgr, enableProgressiveSyncs, maxConcurrentReconciliations); err != nil {
log.Error(err, "unable to create controller", "controller", "ApplicationSet")
os.Exit(1)
@@ -287,7 +275,6 @@ func NewCommand() *cobra.Command {
command.Flags().IntVar(&webhookParallelism, "webhook-parallelism-limit", env.ParseNumFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_WEBHOOK_PARALLELISM_LIMIT", 50, 1, 1000), "Number of webhook requests processed concurrently")
command.Flags().StringSliceVar(&metricsAplicationsetLabels, "metrics-applicationset-labels", []string{}, "List of Application labels that will be added to the argocd_applicationset_labels metric")
command.Flags().BoolVar(&enableGitHubAPIMetrics, "enable-github-api-metrics", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_GITHUB_API_METRICS", false), "Enable GitHub API metrics for generators that use the GitHub API")
command.Flags().IntVar(&maxResourcesStatusCount, "max-resources-status-count", env.ParseNumFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_MAX_RESOURCES_STATUS_COUNT", 0, 0, math.MaxInt), "Max number of resources stored in appset status.")
return &command
}

View File

@@ -30,12 +30,11 @@ func NewNotificationsCommand() *cobra.Command {
)
var argocdService service.Service
toolsCommand := cmd.NewToolsCommand(
"notifications",
"argocd admin notifications",
applications,
settings.GetFactorySettingsForCLI(func() service.Service { return argocdService }, "argocd-notifications-secret", "argocd-notifications-cm", false),
settings.GetFactorySettingsForCLI(argocdService, "argocd-notifications-secret", "argocd-notifications-cm", false),
func(clientConfig clientcmd.ClientConfig) {
k8sCfg, err := clientConfig.ClientConfig()
if err != nil {

View File

@@ -1,14 +1,101 @@
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
package mocks
import (
"github.com/argoproj/argo-cd/v3/commitserver/apiclient"
utilio "github.com/argoproj/argo-cd/v3/util/io"
"github.com/argoproj/argo-cd/v3/util/io"
mock "github.com/stretchr/testify/mock"
)
type Clientset struct {
CommitServiceClient apiclient.CommitServiceClient
// NewClientset creates a new instance of Clientset. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewClientset(t interface {
mock.TestingT
Cleanup(func())
}) *Clientset {
mock := &Clientset{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
func (c *Clientset) NewCommitServerClient() (utilio.Closer, apiclient.CommitServiceClient, error) {
return utilio.NopCloser, c.CommitServiceClient, nil
// Clientset is an autogenerated mock type for the Clientset type
type Clientset struct {
mock.Mock
}
type Clientset_Expecter struct {
mock *mock.Mock
}
func (_m *Clientset) EXPECT() *Clientset_Expecter {
return &Clientset_Expecter{mock: &_m.Mock}
}
// NewCommitServerClient provides a mock function for the type Clientset
func (_mock *Clientset) NewCommitServerClient() (io.Closer, apiclient.CommitServiceClient, error) {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for NewCommitServerClient")
}
var r0 io.Closer
var r1 apiclient.CommitServiceClient
var r2 error
if returnFunc, ok := ret.Get(0).(func() (io.Closer, apiclient.CommitServiceClient, error)); ok {
return returnFunc()
}
if returnFunc, ok := ret.Get(0).(func() io.Closer); ok {
r0 = returnFunc()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(io.Closer)
}
}
if returnFunc, ok := ret.Get(1).(func() apiclient.CommitServiceClient); ok {
r1 = returnFunc()
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(apiclient.CommitServiceClient)
}
}
if returnFunc, ok := ret.Get(2).(func() error); ok {
r2 = returnFunc()
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// Clientset_NewCommitServerClient_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'NewCommitServerClient'
type Clientset_NewCommitServerClient_Call struct {
*mock.Call
}
// NewCommitServerClient is a helper method to define mock.On call
func (_e *Clientset_Expecter) NewCommitServerClient() *Clientset_NewCommitServerClient_Call {
return &Clientset_NewCommitServerClient_Call{Call: _e.mock.On("NewCommitServerClient")}
}
func (_c *Clientset_NewCommitServerClient_Call) Run(run func()) *Clientset_NewCommitServerClient_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *Clientset_NewCommitServerClient_Call) Return(closer io.Closer, commitServiceClient apiclient.CommitServiceClient, err error) *Clientset_NewCommitServerClient_Call {
_c.Call.Return(closer, commitServiceClient, err)
return _c
}
func (_c *Clientset_NewCommitServerClient_Call) RunAndReturn(run func() (io.Closer, apiclient.CommitServiceClient, error)) *Clientset_NewCommitServerClient_Call {
_c.Call.Return(run)
return _c
}

View File

@@ -1878,7 +1878,7 @@ func (ctrl *ApplicationController) processAppHydrateQueueItem() (processNext boo
return
}
ctrl.hydrator.ProcessAppHydrateQueueItem(origApp.DeepCopy())
ctrl.hydrator.ProcessAppHydrateQueueItem(origApp)
log.WithFields(applog.GetAppLogFields(origApp)).Debug("Successfully processed app hydrate queue item")
return

View File

@@ -4,9 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"maps"
"path/filepath"
"slices"
"sync"
"time"
@@ -101,41 +99,47 @@ func NewHydrator(dependencies Dependencies, statusRefreshTimeout time.Duration,
// It's likely that multiple applications will trigger hydration at the same time. The hydration queue key is meant to
// dedupe these requests.
func (h *Hydrator) ProcessAppHydrateQueueItem(origApp *appv1.Application) {
origApp = origApp.DeepCopy()
app := origApp.DeepCopy()
if app.Spec.SourceHydrator == nil {
return
}
logCtx := log.WithFields(applog.GetAppLogFields(app))
logCtx.Debug("Processing app hydrate queue item")
needsHydration, reason := appNeedsHydration(app)
if needsHydration {
app.Status.SourceHydrator.CurrentOperation = &appv1.HydrateOperation{
StartedAt: metav1.Now(),
FinishedAt: nil,
Phase: appv1.HydrateOperationPhaseHydrating,
SourceHydrator: *app.Spec.SourceHydrator,
}
h.dependencies.PersistAppHydratorStatus(origApp, &app.Status.SourceHydrator)
// TODO: don't reuse statusRefreshTimeout. Create a new timeout for hydration.
needsHydration, reason := appNeedsHydration(origApp, h.statusRefreshTimeout)
if !needsHydration {
return
}
needsRefresh := app.Status.SourceHydrator.CurrentOperation.Phase == appv1.HydrateOperationPhaseHydrating && metav1.Now().Sub(app.Status.SourceHydrator.CurrentOperation.StartedAt.Time) > h.statusRefreshTimeout
if needsHydration || needsRefresh {
logCtx.WithField("reason", reason).Info("Hydrating app")
h.dependencies.AddHydrationQueueItem(getHydrationQueueKey(app))
} else {
logCtx.WithField("reason", reason).Debug("Skipping hydration")
logCtx.WithField("reason", reason).Info("Hydrating app")
app.Status.SourceHydrator.CurrentOperation = &appv1.HydrateOperation{
StartedAt: metav1.Now(),
FinishedAt: nil,
Phase: appv1.HydrateOperationPhaseHydrating,
SourceHydrator: *app.Spec.SourceHydrator,
}
h.dependencies.PersistAppHydratorStatus(origApp, &app.Status.SourceHydrator)
origApp.Status.SourceHydrator = app.Status.SourceHydrator
h.dependencies.AddHydrationQueueItem(getHydrationQueueKey(app))
logCtx.Debug("Successfully processed app hydrate queue item")
}
func getHydrationQueueKey(app *appv1.Application) types.HydrationQueueKey {
destinationBranch := app.Spec.SourceHydrator.SyncSource.TargetBranch
if app.Spec.SourceHydrator.HydrateTo != nil {
destinationBranch = app.Spec.SourceHydrator.HydrateTo.TargetBranch
}
key := types.HydrationQueueKey{
SourceRepoURL: git.NormalizeGitURLAllowInvalid(app.Spec.SourceHydrator.DrySource.RepoURL),
SourceTargetRevision: app.Spec.SourceHydrator.DrySource.TargetRevision,
DestinationBranch: app.Spec.GetHydrateToSource().TargetRevision,
DestinationBranch: destinationBranch,
}
return key
}
@@ -144,92 +148,43 @@ func getHydrationQueueKey(app *appv1.Application) types.HydrationQueueKey {
// hydration key, hydrates their latest commit, and updates their status accordingly. If the hydration fails, it marks
// the operation as failed and logs the error. If successful, it updates the operation to indicate that hydration was
// successful and requests a refresh of the applications to pick up the new hydrated commit.
func (h *Hydrator) ProcessHydrationQueueItem(hydrationKey types.HydrationQueueKey) {
func (h *Hydrator) ProcessHydrationQueueItem(hydrationKey types.HydrationQueueKey) (processNext bool) {
logCtx := log.WithFields(log.Fields{
"sourceRepoURL": hydrationKey.SourceRepoURL,
"sourceTargetRevision": hydrationKey.SourceTargetRevision,
"destinationBranch": hydrationKey.DestinationBranch,
})
// Get all applications sharing the same hydration key
apps, err := h.getAppsForHydrationKey(hydrationKey)
if err != nil {
// If we get an error here, we cannot proceed with hydration and we do not know
// which apps to update with the failure. The best we can do is log an error in
// the controller and wait for statusRefreshTimeout to retry
logCtx.WithError(err).Error("failed to get apps for hydration")
relevantApps, drySHA, hydratedSHA, err := h.hydrateAppsLatestCommit(logCtx, hydrationKey)
if len(relevantApps) == 0 {
// return early if there are no relevant apps found to hydrate
// otherwise you'll be stuck in hydrating
logCtx.Info("Skipping hydration since there are no relevant apps found to hydrate")
return
}
logCtx.WithField("appCount", len(apps))
// FIXME: we might end up in a race condition here where an HydrationQueueItem is processed
// before all applications had their CurrentOperation set by ProcessAppHydrateQueueItem.
// This would cause this method to update "old" CurrentOperation.
// It should only start hydration if all apps are in the HydrateOperationPhaseHydrating phase.
raceDetected := false
for _, app := range apps {
if app.Status.SourceHydrator.CurrentOperation == nil || app.Status.SourceHydrator.CurrentOperation.Phase != appv1.HydrateOperationPhaseHydrating {
raceDetected = true
break
}
}
if raceDetected {
logCtx.Warn("race condition detected: not all apps are in HydrateOperationPhaseHydrating phase")
}
// validate all the applications to make sure they are all correctly configured.
// All applications sharing the same hydration key must succeed for the hydration to be processed.
projects, validationErrors := h.validateApplications(apps)
if len(validationErrors) > 0 {
// For the applications that have an error, set the specific error in their status.
// Applications without error will still fail with a generic error since the hydration cannot be partial
genericError := genericHydrationError(validationErrors)
for _, app := range apps {
if err, ok := validationErrors[app.QualifiedName()]; ok {
logCtx = logCtx.WithFields(applog.GetAppLogFields(app))
logCtx.Errorf("failed to validate hydration app: %v", err)
h.setAppHydratorError(app, err)
} else {
h.setAppHydratorError(app, genericError)
}
}
return
}
// Hydrate all the apps
drySHA, hydratedSHA, appErrors, err := h.hydrate(logCtx, apps, projects)
if err != nil {
// If there is a single error, it affects each applications
for i := range apps {
appErrors[apps[i].QualifiedName()] = err
}
}
if drySHA != "" {
logCtx = logCtx.WithField("drySHA", drySHA)
}
if len(appErrors) > 0 {
// For the applications that have an error, set the specific error in their status.
// Applications without error will still fail with a generic error since the hydration cannot be partial
genericError := genericHydrationError(appErrors)
for _, app := range apps {
if drySHA != "" {
// If we have a drySHA, we can set it on the app status
app.Status.SourceHydrator.CurrentOperation.DrySHA = drySHA
}
if err, ok := appErrors[app.QualifiedName()]; ok {
logCtx = logCtx.WithFields(applog.GetAppLogFields(app))
logCtx.Errorf("failed to hydrate app: %v", err)
h.setAppHydratorError(app, err)
} else {
h.setAppHydratorError(app, genericError)
}
if err != nil {
logCtx.WithField("appCount", len(relevantApps)).WithError(err).Error("Failed to hydrate apps")
for _, app := range relevantApps {
origApp := app.DeepCopy()
app.Status.SourceHydrator.CurrentOperation.Phase = appv1.HydrateOperationPhaseFailed
failedAt := metav1.Now()
app.Status.SourceHydrator.CurrentOperation.FinishedAt = &failedAt
app.Status.SourceHydrator.CurrentOperation.Message = fmt.Sprintf("Failed to hydrate revision %q: %v", drySHA, err.Error())
// We may or may not have gotten far enough in the hydration process to get a non-empty SHA, but set it just
// in case we did.
app.Status.SourceHydrator.CurrentOperation.DrySHA = drySHA
h.dependencies.PersistAppHydratorStatus(origApp, &app.Status.SourceHydrator)
logCtx = logCtx.WithFields(applog.GetAppLogFields(app))
logCtx.Errorf("Failed to hydrate app: %v", err)
}
return
}
logCtx.Debug("Successfully hydrated apps")
logCtx.WithField("appCount", len(relevantApps)).Debug("Successfully hydrated apps")
finishedAt := metav1.Now()
for _, app := range apps {
for _, app := range relevantApps {
origApp := app.DeepCopy()
operation := &appv1.HydrateOperation{
StartedAt: app.Status.SourceHydrator.CurrentOperation.StartedAt,
@@ -247,123 +202,118 @@ func (h *Hydrator) ProcessHydrationQueueItem(hydrationKey types.HydrationQueueKe
SourceHydrator: app.Status.SourceHydrator.CurrentOperation.SourceHydrator,
}
h.dependencies.PersistAppHydratorStatus(origApp, &app.Status.SourceHydrator)
// Request a refresh since we pushed a new commit.
err := h.dependencies.RequestAppRefresh(app.Name, app.Namespace)
if err != nil {
logCtx.WithFields(applog.GetAppLogFields(app)).WithError(err).Error("Failed to request app refresh after hydration")
logCtx.WithField("app", app.QualifiedName()).WithError(err).Error("Failed to request app refresh after hydration")
}
}
return
}
// setAppHydratorError updates the CurrentOperation with the error information.
func (h *Hydrator) setAppHydratorError(app *appv1.Application, err error) {
// if the operation is not in progress, we do not update the status
if app.Status.SourceHydrator.CurrentOperation.Phase != appv1.HydrateOperationPhaseHydrating {
return
func (h *Hydrator) hydrateAppsLatestCommit(logCtx *log.Entry, hydrationKey types.HydrationQueueKey) ([]*appv1.Application, string, string, error) {
relevantApps, projects, err := h.getRelevantAppsAndProjectsForHydration(logCtx, hydrationKey)
if err != nil {
return nil, "", "", fmt.Errorf("failed to get relevant apps for hydration: %w", err)
}
origApp := app.DeepCopy()
app.Status.SourceHydrator.CurrentOperation.Phase = appv1.HydrateOperationPhaseFailed
failedAt := metav1.Now()
app.Status.SourceHydrator.CurrentOperation.FinishedAt = &failedAt
app.Status.SourceHydrator.CurrentOperation.Message = fmt.Sprintf("Failed to hydrate: %v", err.Error())
h.dependencies.PersistAppHydratorStatus(origApp, &app.Status.SourceHydrator)
dryRevision, hydratedRevision, err := h.hydrate(logCtx, relevantApps, projects)
if err != nil {
return relevantApps, dryRevision, "", fmt.Errorf("failed to hydrate apps: %w", err)
}
return relevantApps, dryRevision, hydratedRevision, nil
}
// getAppsForHydrationKey returns the applications matching the hydration key.
func (h *Hydrator) getAppsForHydrationKey(hydrationKey types.HydrationQueueKey) ([]*appv1.Application, error) {
func (h *Hydrator) getRelevantAppsAndProjectsForHydration(logCtx *log.Entry, hydrationKey types.HydrationQueueKey) ([]*appv1.Application, map[string]*appv1.AppProject, error) {
// Get all apps
apps, err := h.dependencies.GetProcessableApps()
if err != nil {
return nil, fmt.Errorf("failed to list apps: %w", err)
return nil, nil, fmt.Errorf("failed to list apps: %w", err)
}
var relevantApps []*appv1.Application
projects := make(map[string]*appv1.AppProject)
uniquePaths := make(map[string]bool, len(apps.Items))
for _, app := range apps.Items {
if app.Spec.SourceHydrator == nil {
continue
}
appKey := getHydrationQueueKey(&app)
if appKey != hydrationKey {
if !git.SameURL(app.Spec.SourceHydrator.DrySource.RepoURL, hydrationKey.SourceRepoURL) ||
app.Spec.SourceHydrator.DrySource.TargetRevision != hydrationKey.SourceTargetRevision {
continue
}
relevantApps = append(relevantApps, &app)
}
return relevantApps, nil
}
// validateApplications checks that all applications are valid for hydration.
func (h *Hydrator) validateApplications(apps []*appv1.Application) (map[string]*appv1.AppProject, map[string]error) {
projects := make(map[string]*appv1.AppProject)
errors := make(map[string]error)
uniquePaths := make(map[string]string, len(apps))
for _, app := range apps {
// Get the project for the app and validate if the app is allowed to use the source.
// We can't short-circuit this even if we have seen this project before, because we need to verify that this
// particular app is allowed to use this project.
proj, err := h.dependencies.GetProcessableAppProj(app)
if err != nil {
errors[app.QualifiedName()] = fmt.Errorf("failed to get project %q: %w", app.Spec.Project, err)
destinationBranch := app.Spec.SourceHydrator.SyncSource.TargetBranch
if app.Spec.SourceHydrator.HydrateTo != nil {
destinationBranch = app.Spec.SourceHydrator.HydrateTo.TargetBranch
}
if destinationBranch != hydrationKey.DestinationBranch {
continue
}
path := app.Spec.SourceHydrator.SyncSource.Path
// ensure that the path is always set to a path that doesn't resolve to the root of the repo
if IsRootPath(path) {
return nil, nil, fmt.Errorf("app %q has path %q which resolves to repository root", app.QualifiedName(), path)
}
var proj *appv1.AppProject
// We can't short-circuit this even if we have seen this project before, because we need to verify that this
// particular app is allowed to use this project. That logic is in GetProcessableAppProj.
proj, err = h.dependencies.GetProcessableAppProj(&app)
if err != nil {
return nil, nil, fmt.Errorf("failed to get project %q for app %q: %w", app.Spec.Project, app.QualifiedName(), err)
}
permitted := proj.IsSourcePermitted(app.Spec.GetSource())
if !permitted {
errors[app.QualifiedName()] = fmt.Errorf("application repo %s is not permitted in project '%s'", app.Spec.GetSource().RepoURL, proj.Name)
// Log and skip. We don't want to fail the entire operation because of one app.
logCtx.Warnf("App %q is not permitted to use source %q", app.QualifiedName(), app.Spec.Source.String())
continue
}
projects[app.Spec.Project] = proj
// Disallow hydrating to the repository root.
// Hydrating to root would overwrite or delete files at the top level of the repo,
// which can break other applications or shared configuration.
// Every hydrated app must write into a subdirectory instead.
destPath := app.Spec.SourceHydrator.SyncSource.Path
if IsRootPath(destPath) {
errors[app.QualifiedName()] = fmt.Errorf("app is configured to hydrate to the repository root (branch %q, path %q) which is not allowed", app.Spec.GetHydrateToSource().TargetRevision, destPath)
continue
}
// TODO: test the dupe detection
// TODO: normalize the path to avoid "path/.." from being treated as different from "."
if appName, ok := uniquePaths[destPath]; ok {
errors[app.QualifiedName()] = fmt.Errorf("app %s hydrator use the same destination: %v", appName, app.Spec.SourceHydrator.SyncSource.Path)
errors[appName] = fmt.Errorf("app %s hydrator use the same destination: %v", app.QualifiedName(), app.Spec.SourceHydrator.SyncSource.Path)
continue
if _, ok := uniquePaths[path]; ok {
return nil, nil, fmt.Errorf("multiple app hydrators use the same destination: %v", app.Spec.SourceHydrator.SyncSource.Path)
}
uniquePaths[destPath] = app.QualifiedName()
}
uniquePaths[path] = true
// If there are any errors, return nil for projects to avoid possible partial processing.
if len(errors) > 0 {
projects = nil
relevantApps = append(relevantApps, &app)
}
return projects, errors
return relevantApps, projects, nil
}
func (h *Hydrator) hydrate(logCtx *log.Entry, apps []*appv1.Application, projects map[string]*appv1.AppProject) (string, string, map[string]error, error) {
errors := make(map[string]error)
func (h *Hydrator) hydrate(logCtx *log.Entry, apps []*appv1.Application, projects map[string]*appv1.AppProject) (string, string, error) {
if len(apps) == 0 {
return "", "", nil, nil
return "", "", nil
}
// These values are the same for all apps being hydrated together, so just get them from the first app.
repoURL := apps[0].Spec.GetHydrateToSource().RepoURL
targetBranch := apps[0].Spec.GetHydrateToSource().TargetRevision
// FIXME: As a convenience, the commit server will create the syncBranch if it does not exist. If the
// targetBranch does not exist, it will create it based on the syncBranch. On the next line, we take
// the `syncBranch` from the first app and assume that they're all configured the same. Instead, if any
// app has a different syncBranch, we should send the commit server an empty string and allow it to
// create the targetBranch as an orphan since we can't reliable determine a reasonable base.
repoURL := apps[0].Spec.SourceHydrator.DrySource.RepoURL
syncBranch := apps[0].Spec.SourceHydrator.SyncSource.TargetBranch
targetBranch := apps[0].Spec.GetHydrateToSource().TargetRevision
// Disallow hydrating to the repository root.
// Hydrating to root would overwrite or delete files at the top level of the repo,
// which can break other applications or shared configuration.
// Every hydrated app must write into a subdirectory instead.
for _, app := range apps {
destPath := app.Spec.SourceHydrator.SyncSource.Path
if IsRootPath(destPath) {
return "", "", fmt.Errorf(
"app %q is configured to hydrate to the repository root (branch %q, path %q) which is not allowed",
app.QualifiedName(), targetBranch, destPath,
)
}
}
// Get a static SHA revision from the first app so that all apps are hydrated from the same revision.
targetRevision, pathDetails, err := h.getManifests(context.Background(), apps[0], "", projects[apps[0].Spec.Project])
if err != nil {
errors[apps[0].QualifiedName()] = fmt.Errorf("failed to get manifests: %w", err)
return "", "", errors, nil
return "", "", fmt.Errorf("failed to get manifests for app %q: %w", apps[0].QualifiedName(), err)
}
paths := []*commitclient.PathDetails{pathDetails}
@@ -374,18 +324,18 @@ func (h *Hydrator) hydrate(logCtx *log.Entry, apps []*appv1.Application, project
app := app
eg.Go(func() error {
_, pathDetails, err = h.getManifests(ctx, app, targetRevision, projects[app.Spec.Project])
mu.Lock()
defer mu.Unlock()
if err != nil {
errors[app.QualifiedName()] = fmt.Errorf("failed to get manifests: %w", err)
return errors[app.QualifiedName()]
return fmt.Errorf("failed to get manifests for app %q: %w", app.QualifiedName(), err)
}
mu.Lock()
paths = append(paths, pathDetails)
mu.Unlock()
return nil
})
}
if err := eg.Wait(); err != nil {
return targetRevision, "", errors, nil
err = eg.Wait()
if err != nil {
return "", "", fmt.Errorf("failed to get manifests for apps: %w", err)
}
// If all the apps are under the same project, use that project. Otherwise, use an empty string to indicate that we
@@ -394,19 +344,18 @@ func (h *Hydrator) hydrate(logCtx *log.Entry, apps []*appv1.Application, project
if len(projects) == 1 {
for p := range projects {
project = p
break
}
}
// Get the commit metadata for the target revision.
revisionMetadata, err := h.getRevisionMetadata(context.Background(), repoURL, project, targetRevision)
if err != nil {
return targetRevision, "", errors, fmt.Errorf("failed to get revision metadata for %q: %w", targetRevision, err)
return "", "", fmt.Errorf("failed to get revision metadata for %q: %w", targetRevision, err)
}
repo, err := h.dependencies.GetWriteCredentials(context.Background(), repoURL, project)
if err != nil {
return targetRevision, "", errors, fmt.Errorf("failed to get hydrator credentials: %w", err)
return "", "", fmt.Errorf("failed to get hydrator credentials: %w", err)
}
if repo == nil {
// Try without credentials.
@@ -418,11 +367,11 @@ func (h *Hydrator) hydrate(logCtx *log.Entry, apps []*appv1.Application, project
// get the commit message template
commitMessageTemplate, err := h.dependencies.GetHydratorCommitMessageTemplate()
if err != nil {
return targetRevision, "", errors, fmt.Errorf("failed to get hydrated commit message template: %w", err)
return "", "", fmt.Errorf("failed to get hydrated commit message template: %w", err)
}
commitMessage, errMsg := getTemplatedCommitMessage(repoURL, targetRevision, commitMessageTemplate, revisionMetadata)
if errMsg != nil {
return targetRevision, "", errors, fmt.Errorf("failed to get hydrator commit templated message: %w", errMsg)
return "", "", fmt.Errorf("failed to get hydrator commit templated message: %w", errMsg)
}
manifestsRequest := commitclient.CommitHydratedManifestsRequest{
@@ -437,14 +386,14 @@ func (h *Hydrator) hydrate(logCtx *log.Entry, apps []*appv1.Application, project
closer, commitService, err := h.commitClientset.NewCommitServerClient()
if err != nil {
return targetRevision, "", errors, fmt.Errorf("failed to create commit service: %w", err)
return targetRevision, "", fmt.Errorf("failed to create commit service: %w", err)
}
defer utilio.Close(closer)
resp, err := commitService.CommitHydratedManifests(context.Background(), &manifestsRequest)
if err != nil {
return targetRevision, "", errors, fmt.Errorf("failed to commit hydrated manifests: %w", err)
return targetRevision, "", fmt.Errorf("failed to commit hydrated manifests: %w", err)
}
return targetRevision, resp.HydratedSha, errors, nil
return targetRevision, resp.HydratedSha, nil
}
// getManifests gets the manifests for the given application and target revision. It returns the resolved revision
@@ -507,27 +456,34 @@ func (h *Hydrator) getRevisionMetadata(ctx context.Context, repoURL, project, re
}
// appNeedsHydration answers if application needs manifests hydrated.
func appNeedsHydration(app *appv1.Application) (needsHydration bool, reason string) {
switch {
case app.Spec.SourceHydrator == nil:
func appNeedsHydration(app *appv1.Application, statusHydrateTimeout time.Duration) (needsHydration bool, reason string) {
if app.Spec.SourceHydrator == nil {
return false, "source hydrator not configured"
case app.Status.SourceHydrator.CurrentOperation == nil:
return true, "no previous hydrate operation"
case app.Status.SourceHydrator.CurrentOperation.Phase == appv1.HydrateOperationPhaseHydrating:
return false, "hydration operation already in progress"
}
var hydratedAt *metav1.Time
if app.Status.SourceHydrator.CurrentOperation != nil {
hydratedAt = &app.Status.SourceHydrator.CurrentOperation.StartedAt
}
switch {
case app.IsHydrateRequested():
return true, "hydrate requested"
case app.Status.SourceHydrator.CurrentOperation == nil:
return true, "no previous hydrate operation"
case !app.Spec.SourceHydrator.DeepEquals(app.Status.SourceHydrator.CurrentOperation.SourceHydrator):
return true, "spec.sourceHydrator differs"
case app.Status.SourceHydrator.CurrentOperation.Phase == appv1.HydrateOperationPhaseFailed && metav1.Now().Sub(app.Status.SourceHydrator.CurrentOperation.FinishedAt.Time) > 2*time.Minute:
return true, "previous hydrate operation failed more than 2 minutes ago"
case hydratedAt == nil || hydratedAt.Add(statusHydrateTimeout).Before(time.Now().UTC()):
return true, "hydration expired"
}
return false, "hydration not needed"
return false, ""
}
// getTemplatedCommitMessage gets the multi-line commit message based on the template defined in the configmap. It is a two step process:
// 1. Get the metadata template engine would use to render the template
// Gets the multi-line commit message based on the template defined in the configmap. It is a two step process:
// 1. Get the metadata template engine would use to render the template
// 2. Pass the output of Step 1 and Step 2 to template Render
func getTemplatedCommitMessage(repoURL, revision, commitMessageTemplate string, dryCommitMetadata *appv1.RevisionMetadata) (string, error) {
hydratorCommitMetadata, err := hydrator.GetCommitMetadata(repoURL, revision, dryCommitMetadata)
@@ -541,20 +497,6 @@ func getTemplatedCommitMessage(repoURL, revision, commitMessageTemplate string,
return templatedCommitMsg, nil
}
// genericHydrationError returns an error that summarizes the hydration errors for all applications.
func genericHydrationError(validationErrors map[string]error) error {
if len(validationErrors) == 0 {
return nil
}
keys := slices.Sorted(maps.Keys(validationErrors))
remainder := "has an error"
if len(keys) > 1 {
remainder = fmt.Sprintf("and %d more have errors", len(keys)-1)
}
return fmt.Errorf("cannot hydrate because application %s %s", keys[0], remainder)
}
// IsRootPath returns whether the path references a root path
func IsRootPath(path string) bool {
clean := filepath.Clean(path)

File diff suppressed because it is too large Load Diff

View File

@@ -1,113 +0,0 @@
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
package mocks
import (
"context"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
mock "github.com/stretchr/testify/mock"
)
// NewRepoGetter creates a new instance of RepoGetter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewRepoGetter(t interface {
mock.TestingT
Cleanup(func())
}) *RepoGetter {
mock := &RepoGetter{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
// RepoGetter is an autogenerated mock type for the RepoGetter type
type RepoGetter struct {
mock.Mock
}
type RepoGetter_Expecter struct {
mock *mock.Mock
}
func (_m *RepoGetter) EXPECT() *RepoGetter_Expecter {
return &RepoGetter_Expecter{mock: &_m.Mock}
}
// GetRepository provides a mock function for the type RepoGetter
func (_mock *RepoGetter) GetRepository(ctx context.Context, repoURL string, project string) (*v1alpha1.Repository, error) {
ret := _mock.Called(ctx, repoURL, project)
if len(ret) == 0 {
panic("no return value specified for GetRepository")
}
var r0 *v1alpha1.Repository
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) (*v1alpha1.Repository, error)); ok {
return returnFunc(ctx, repoURL, project)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) *v1alpha1.Repository); ok {
r0 = returnFunc(ctx, repoURL, project)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.Repository)
}
}
if returnFunc, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
r1 = returnFunc(ctx, repoURL, project)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RepoGetter_GetRepository_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRepository'
type RepoGetter_GetRepository_Call struct {
*mock.Call
}
// GetRepository is a helper method to define mock.On call
// - ctx context.Context
// - repoURL string
// - project string
func (_e *RepoGetter_Expecter) GetRepository(ctx interface{}, repoURL interface{}, project interface{}) *RepoGetter_GetRepository_Call {
return &RepoGetter_GetRepository_Call{Call: _e.mock.On("GetRepository", ctx, repoURL, project)}
}
func (_c *RepoGetter_GetRepository_Call) Run(run func(ctx context.Context, repoURL string, project string)) *RepoGetter_GetRepository_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 context.Context
if args[0] != nil {
arg0 = args[0].(context.Context)
}
var arg1 string
if args[1] != nil {
arg1 = args[1].(string)
}
var arg2 string
if args[2] != nil {
arg2 = args[2].(string)
}
run(
arg0,
arg1,
arg2,
)
})
return _c
}
func (_c *RepoGetter_GetRepository_Call) Return(repository *v1alpha1.Repository, err error) *RepoGetter_GetRepository_Call {
_c.Call.Return(repository, err)
return _c
}
func (_c *RepoGetter_GetRepository_Call) RunAndReturn(run func(ctx context.Context, repoURL string, project string) (*v1alpha1.Repository, error)) *RepoGetter_GetRepository_Call {
_c.Call.Return(run)
return _c
}

View File

@@ -1,18 +1,21 @@
# Progressive Syncs
!!! warning "Alpha Feature (Since v2.6.0)"
This is an experimental, [alpha-quality](https://github.com/argoproj/argoproj/blob/main/community/feature-status.md#alpha)
feature that allows you to control the order in which the ApplicationSet controller will create or update the Applications
This is an experimental, [alpha-quality](https://github.com/argoproj/argoproj/blob/main/community/feature-status.md#alpha)
feature that allows you to control the order in which the ApplicationSet controller will create or update the Applications
owned by an ApplicationSet resource. It may be removed in future releases or modified in backwards-incompatible ways.
## Use Cases
The Progressive Syncs feature set is intended to be light and flexible. The feature only interacts with the health of managed Applications. It is not intended to support direct integrations with other Rollout controllers (such as the native ReplicaSet controller or Argo Rollouts).
* Progressive Syncs watch for the managed Application resources to become "Healthy" before proceeding to the next stage.
* Deployments, DaemonSets, StatefulSets, and [Argo Rollouts](https://argoproj.github.io/argo-rollouts/) are all supported, because the Application enters a "Progressing" state while pods are being rolled out. In fact, any resource with a health check that can report a "Progressing" status is supported.
* [Argo CD Resource Hooks](../../user-guide/resource_hooks.md) are supported. We recommend this approach for users that need advanced functionality when an Argo Rollout cannot be used, such as smoke testing after a DaemonSet change.
- Progressive Syncs watch for the managed Application resources to become "Healthy" before proceeding to the next stage.
- Deployments, DaemonSets, StatefulSets, and [Argo Rollouts](https://argoproj.github.io/argo-rollouts/) are all supported, because the Application enters a "Progressing" state while pods are being rolled out. In fact, any resource with a health check that can report a "Progressing" status is supported.
- [Argo CD Resource Hooks](../../user-guide/resource_hooks.md) are supported. We recommend this approach for users that need advanced functionality when an Argo Rollout cannot be used, such as smoke testing after a DaemonSet change.
## Enabling Progressive Syncs
As an experimental feature, progressive syncs must be explicitly enabled, in one of these ways.
1. Pass `--enable-progressive-syncs` to the ApplicationSet controller args.
@@ -23,17 +26,18 @@ As an experimental feature, progressive syncs must be explicitly enabled, in one
ApplicationSet strategies control both how applications are created (or updated) and deleted. These operations are configured using two separate fields:
* **Creation Strategy** (`type` field): Controls application creation and updates
* **Deletion Strategy** (`deletionOrder` field): Controls application deletion order
- **Creation Strategy** (`type` field): Controls application creation and updates
- **Deletion Strategy** (`deletionOrder` field): Controls application deletion order
### Creation Strategies
The `type` field controls how applications are created and updated. Available values:
* **AllAtOnce** (default)
* **RollingSync**
- **AllAtOnce** (default)
- **RollingSync**
#### AllAtOnce
This default Application update behavior is unchanged from the original ApplicationSet implementation.
All Applications managed by the ApplicationSet resource are updated simultaneously when the ApplicationSet is updated.
@@ -41,25 +45,25 @@ All Applications managed by the ApplicationSet resource are updated simultaneous
```yaml
spec:
strategy:
type: AllAtOnce # explicit, but this is the default
type: AllAtOnce # explicit, but this is the default
```
#### RollingSync
This update strategy allows you to group Applications by labels present on the generated Application resources.
When the ApplicationSet changes, the changes will be applied to each group of Application resources sequentially.
* Application groups are selected using their labels and `matchExpressions`.
* All `matchExpressions` must be true for an Application to be selected (multiple expressions match with AND behavior).
* The `In` and `NotIn` operators must match at least one value to be considered true (OR behavior).
* The `NotIn` operator has priority in the event that both a `NotIn` and `In` operator produce a match.
* All Applications in each group must become Healthy before the ApplicationSet controller will proceed to update the next group of Applications.
* The number of simultaneous Application updates in a group will not exceed its `maxUpdate` parameter (default is 100%, unbounded).
* RollingSync will capture external changes outside the ApplicationSet resource, since it relies on watching the OutOfSync status of the managed Applications.
* RollingSync will force all generated Applications to have autosync disabled. Warnings are printed in the applicationset-controller logs for any Application specs with an automated syncPolicy enabled.
* Sync operations are triggered the same way as if they were triggered by the UI or CLI (by directly setting the `operation` status field on the Application resource). This means that a RollingSync will respect sync windows just as if a user had clicked the "Sync" button in the Argo UI.
* When a sync is triggered, the sync is performed with the same syncPolicy configured for the Application. For example, this preserves the Application's retry settings.
* If an Application is considered "Pending" for `applicationsetcontroller.default.application.progressing.timeout` seconds, the Application is automatically moved to Healthy status (default 300).
* If an Application is not selected in any step, it will be excluded from the rolling sync and needs to be manually synced through the CLI or UI.
- Application groups are selected using their labels and `matchExpressions`.
- All `matchExpressions` must be true for an Application to be selected (multiple expressions match with AND behavior).
- The `In` and `NotIn` operators must match at least one value to be considered true (OR behavior).
- The `NotIn` operator has priority in the event that both a `NotIn` and `In` operator produce a match.
- All Applications in each group must become Healthy before the ApplicationSet controller will proceed to update the next group of Applications.
- The number of simultaneous Application updates in a group will not exceed its `maxUpdate` parameter (default is 100%, unbounded).
- RollingSync will capture external changes outside the ApplicationSet resource, since it relies on watching the OutOfSync status of the managed Applications.
- RollingSync will force all generated Applications to have autosync disabled. Warnings are printed in the applicationset-controller logs for any Application specs with an automated syncPolicy enabled.
- Sync operations are triggered the same way as if they were triggered by the UI or CLI (by directly setting the `operation` status field on the Application resource). This means that a RollingSync will respect sync windows just as if a user had clicked the "Sync" button in the Argo UI.
- When a sync is triggered, the sync is performed with the same syncPolicy configured for the Application. For example, this preserves the Application's retry settings.
- If an Application is not selected in any step, it will be excluded from the rolling sync and needs to be manually synced through the CLI or UI.
```yaml
spec:
@@ -84,25 +88,28 @@ spec:
The `deletionOrder` field controls the order in which applications are deleted when they are removed from the ApplicationSet. Available values:
* **AllAtOnce** (default)
* **Reverse**
- **AllAtOnce** (default)
- **Reverse**
#### AllAtOnce Deletion
This is the default behavior where all applications that need to be deleted are removed simultaneously. This works with both `AllAtOnce` and `RollingSync` creation strategies.
```yaml
spec:
strategy:
type: RollingSync # or AllAtOnce
deletionOrder: AllAtOnce # explicit, but this is the default
type: RollingSync # or AllAtOnce
deletionOrder: AllAtOnce # explicit, but this is the default
```
#### Reverse Deletion
When using `deletionOrder: Reverse` with RollingSync strategy, applications are deleted in reverse order of the steps defined in `rollingSync.steps`. This ensures that applications deployed in later steps are deleted before applications deployed in earlier steps.
This strategy is particularly useful when you need to tear down dependent services in the particular sequence.
**Requirements for Reverse deletion:**
- Must be used with `type: RollingSync`
- Must be used with `type: RollingSync`
- Requires `rollingSync.steps` to be defined
- Applications are deleted in reverse order of step sequence
@@ -119,28 +126,30 @@ spec:
- key: envLabel
operator: In
values:
- env-dev # Step 1: Created first, deleted last
- env-dev # Step 1: Created first, deleted last
- matchExpressions:
- key: envLabel
- key: envLabel
operator: In
values:
- env-prod # Step 2: Created second, deleted first
- env-prod # Step 2: Created second, deleted first
```
In this example, when applications are deleted:
1. `env-prod` applications (Step 2) are deleted first
2. `env-dev` applications (Step 1) are deleted second
This deletion order is useful for scenarios where you need to tear down dependent services in the correct sequence, such as deleting frontend services before backend dependencies.
#### Example
The following example illustrates how to stage a progressive sync over Applications with explicitly configured environment labels.
Once a change is pushed, the following will happen in order.
* All `env-dev` Applications will be updated simultaneously.
* The rollout will wait for all `env-qa` Applications to be manually synced via the `argocd` CLI or by clicking the Sync button in the UI.
* 10% of all `env-prod` Applications will be updated at a time until all `env-prod` Applications have been updated.
- All `env-dev` Applications will be updated simultaneously.
- The rollout will wait for all `env-qa` Applications to be manually synced via the `argocd` CLI or by clicking the Sync button in the UI.
- 10% of all `env-prod` Applications will be updated at a time until all `env-prod` Applications have been updated.
```yaml
apiVersion: argoproj.io/v1alpha1
@@ -149,20 +158,20 @@ metadata:
name: guestbook
spec:
generators:
- list:
elements:
- cluster: engineering-dev
url: https://1.2.3.4
env: env-dev
- cluster: engineering-qa
url: https://2.4.6.8
env: env-qa
- cluster: engineering-prod
url: https://9.8.7.6/
env: env-prod
- list:
elements:
- cluster: engineering-dev
url: https://1.2.3.4
env: env-dev
- cluster: engineering-qa
url: https://2.4.6.8
env: env-qa
- cluster: engineering-prod
url: https://9.8.7.6/
env: env-prod
strategy:
type: RollingSync
deletionOrder: Reverse # Applications will be deleted in reverse order of steps
deletionOrder: Reverse # Applications will be deleted in reverse order of steps
rollingSync:
steps:
- matchExpressions:
@@ -176,15 +185,15 @@ spec:
operator: In
values:
- env-qa
maxUpdate: 0 # if 0, no matched applications will be updated
maxUpdate: 0 # if 0, no matched applications will be updated
- matchExpressions:
- key: envLabel
operator: In
values:
- env-prod
maxUpdate: 10% # maxUpdate supports both integer and percentage string values (rounds down, but floored at 1 Application for >0%)
maxUpdate: 10% # maxUpdate supports both integer and percentage string values (rounds down, but floored at 1 Application for >0%)
goTemplate: true
goTemplateOptions: ["missingkey=error"]
goTemplateOptions: ['missingkey=error']
template:
metadata:
name: '{{.cluster}}-guestbook'

View File

@@ -3,5 +3,5 @@
An example of an argocd-cmd-params-cm.yaml file:
```yaml
{ !docs/operator-manual/argocd-cmd-params-cm.yaml! }
```
{!docs/operator-manual/argocd-cmd-params-cm.yaml!}
```

View File

@@ -292,10 +292,6 @@ data:
applicationsetcontroller.global.preserved.labels: "acme.com/label1,acme.com/label2"
# Enable GitHub API metrics for generators that use GitHub API
applicationsetcontroller.enable.github.api.metrics: "false"
# The maximum number of resources stored in the status of an ApplicationSet. This is a safeguard to prevent the status from growing too large.
applicationsetcontroller.status.max.resources.count: "5000"
# Enables profile endpoint on the internal metrics port
applicationsetcontroller.profile.enabled: "false"
## Argo CD Notifications Controller Properties
# Set the logging level. One of: debug|info|warn|error (default "info")

View File

@@ -223,10 +223,10 @@ The following resources have Go-based health checks:
## Health Checks
An Argo CD App's health is inferred from the health of its immediate child resources (the resources represented in
source control). The App health will be the worst health of its immediate child sources. The priority of most to least
healthy statuses is: `Healthy`, `Suspended`, `Progressing`, `Missing`, `Degraded`, `Unknown`. So, for example, if an App
has a `Missing` resource and a `Degraded` resource, the App's health will be `Missing`.
Argo CD App health is inferred from the health of its immediate child resources as represented in the application source.
The App health will be the **worst health of its immediate child resources**, based on the following priority (from most to least healthy):
**Healthy, Suspended, Progressing, Missing, Degraded, Unknown.**
For example, if an App has a Missing resource and a Degraded resource, the App's health will be **Degraded**.
But the health of a resource is not inherited from child resources - it is calculated using only information about the
resource itself. A resource's status field may or may not contain information about the health of a child resource, and

View File

@@ -398,16 +398,13 @@ Not all HTTP responses are eligible for retries. The following conditions will n
## CPU/Memory Profiling
Argo CD optionally exposes a profiling endpoint that can be used to profile the CPU and memory usage of the Argo CD
component.
The profiling endpoint is available on metrics port of each component. See [metrics](./metrics.md) for more information
about the port.
For security reasons, the profiling endpoint is disabled by default. The endpoint can be enabled by setting the
`server.profile.enabled`, `applicationsetcontroller.profile.enabled`, or `controller.profile.enabled` key
of [argocd-cmd-params-cm](argocd-cmd-params-cm.yaml) ConfigMap to `true`.
Once the endpoint is enabled, you can use go profile tool to collect the CPU and memory profiles. Example:
Argo CD optionally exposes a profiling endpoint that can be used to profile the CPU and memory usage of the Argo CD component.
The profiling endpoint is available on metrics port of each component. See [metrics](./metrics.md) for more information about the port.
For security reasons the profiling endpoint is disabled by default. The endpoint can be enabled by setting the `server.profile.enabled`
or `controller.profile.enabled` key of [argocd-cmd-params-cm](argocd-cmd-params-cm.yaml) ConfigMap to `true`.
Once the endpoint is enabled you can use go profile tool to collect the CPU and memory profiles. Example:
```bash
$ kubectl port-forward svc/argocd-metrics 8082:8082
$ go tool pprof http://localhost:8082/debug/pprof/heap
```
```

View File

@@ -37,7 +37,6 @@ argocd-applicationset-controller [flags]
--kubeconfig string Path to a kube config. Only required if out-of-cluster
--logformat string Set the logging format. One of: json|text (default "json")
--loglevel string Set the logging level. One of: debug|info|warn|error (default "info")
--max-resources-status-count int Max number of resources stored in appset status.
--metrics-addr string The address the metric endpoint binds to. (default ":8080")
--metrics-applicationset-labels strings List of Application labels that will be added to the argocd_applicationset_labels metric
-n, --namespace string If present, the namespace scope for this CLI request

View File

@@ -1,5 +1,2 @@
| Argo CD version | Kubernetes versions |
|-----------------|---------------------|
| 3.2 | v1.33, v1.32, v1.31, v1.30 |
| 3.1 | v1.33, v1.32, v1.31, v1.30 |
| 3.0 | v1.32, v1.31, v1.30, v1.29 |
This page is populated for released Argo CD versions. Use the version selector to view this table for a specific
version.

View File

@@ -86,9 +86,3 @@ If you do not want your CronJob to affect the Application's aggregated Health, y
Due to security reasons ([GHSA-786q-9hcg-v9ff](https://github.com/argoproj/argo-cd/security/advisories/GHSA-786q-9hcg-v9ff)),
the project API response was sanitized to remove sensitive information. This includes
credentials of project-scoped repositories and clusters.
## ApplicationSet `resources` field of `status` resource is limited to 5000 elements by default
The `resources` field of the `status` resource of an ApplicationSet is now limited to 5000 elements by default. This is
to prevent status bloat and exceeding etcd limits. The limit can be configured by setting the `applicationsetcontroller.status.max.resources.count`
field in the `argocd-cmd-params-cm` ConfigMap.

View File

@@ -5,5 +5,5 @@ mkdocs-material==7.1.8
markdown_include==0.8.1
pygments==2.19.2
jinja2==3.1.6
markdown==3.8.2
markdown==3.9
pymdown-extensions==10.16.1

View File

@@ -297,7 +297,6 @@ data:
{{- if .metadata.author }}
Co-authored-by: {{ .metadata.author }}
{{- end }}
```
### Credential Templates

8
go.mod
View File

@@ -6,13 +6,13 @@ require (
code.gitea.io/sdk/gitea v0.22.0
dario.cat/mergo v1.0.2
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0
github.com/Azure/kubelogin v0.2.10
github.com/Masterminds/semver/v3 v3.4.0
github.com/Masterminds/sprig/v3 v3.3.0
github.com/TomOnTime/utfutil v1.0.0
github.com/alicebob/miniredis/v2 v2.35.0
github.com/argoproj/gitops-engine v0.7.1-0.20251006172252-b89b0871b414
github.com/argoproj/gitops-engine v0.7.1-0.20250908182407-97ad5b59a627
github.com/argoproj/notifications-engine v0.4.1-0.20250908182349-da04400446ff
github.com/argoproj/pkg v0.13.6
github.com/argoproj/pkg/v2 v2.0.1
@@ -115,7 +115,7 @@ require (
layeh.com/gopher-json v0.0.0-20190114024228-97fed8db8427
oras.land/oras-go/v2 v2.6.0
sigs.k8s.io/controller-runtime v0.21.0
sigs.k8s.io/structured-merge-diff/v6 v6.3.1-0.20251003215857-446d8398e19c
sigs.k8s.io/structured-merge-diff/v6 v6.3.0
sigs.k8s.io/yaml v1.6.0
)
@@ -134,7 +134,7 @@ require (
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect

15
go.sum
View File

@@ -46,8 +46,8 @@ github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs=
github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 h1:5YTBM8QDVIBN3sxBil89WfdAAqDZbyJTgh688DSxX5w=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0 h1:MhRfI58HblXzCtWEZCO0feHs8LweePB3s90r7WaR1KU=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0/go.mod h1:okZ+ZURbArNdlJ+ptXoyHNuOETzOl1Oww19rm8I2WLA=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0 h1:wL5IEG5zb7BVv1Kv0Xm92orq+5hB5Nipn3B5tn4Rqfk=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0/go.mod h1:J7MUC/wtRpfGVbQ5sIItY5/FuVWmvzlY21WAOfQnq/I=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
@@ -74,8 +74,8 @@ github.com/Azure/kubelogin v0.2.10 h1:6CBXJt/RtnTPI1R1E4cfEdL+BnCKMuywtglX//FZPD
github.com/Azure/kubelogin v0.2.10/go.mod h1:JtR+7h3NHAwQPZ+CagUZ+F1Uk3/JU0eRFwpESSnRNGU=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 h1:XkkQbfMyuH2jTSjQjSoihryI8GINRcs4xp8lNawg0FI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Jeffail/gabs v1.4.0 h1://5fYRRTq1edjfIrQGvdkcd22pkYUrHZ5YC/H2GJVAo=
@@ -113,8 +113,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/appscode/go v0.0.0-20191119085241-0887d8ec2ecc/go.mod h1:OawnOmAL4ZX3YaPdN+8HTNwBveT1jMsqP74moa9XUbE=
github.com/argoproj/gitops-engine v0.7.1-0.20251006172252-b89b0871b414 h1:2w1vd2VZja7Mlf/rblJkp6/Eq8fNDuM7p6pI4PTAJhg=
github.com/argoproj/gitops-engine v0.7.1-0.20251006172252-b89b0871b414/go.mod h1:2nqYZBhj8CfVZb3ATakZpi1KNb/yc7mpadIHslicTFI=
github.com/argoproj/gitops-engine v0.7.1-0.20250908182407-97ad5b59a627 h1:yntvA+uaFz62HRfWGGwlvs4ErdxoLQjCpDXufdEt2FI=
github.com/argoproj/gitops-engine v0.7.1-0.20250908182407-97ad5b59a627/go.mod h1:yJ3t/GRn9Gx2LEyMrh9X0roL7zzVlk3nvuJt6G1o6jI=
github.com/argoproj/notifications-engine v0.4.1-0.20250908182349-da04400446ff h1:pGGAeHIktPuYCRl1Z540XdxPFnedqyUhJK4VgpyJZfY=
github.com/argoproj/notifications-engine v0.4.1-0.20250908182349-da04400446ff/go.mod h1:d1RazGXWvKRFv9//rg4MRRR7rbvbE7XLgTSMT5fITTE=
github.com/argoproj/pkg v0.13.6 h1:36WPD9MNYECHcO1/R1pj6teYspiK7uMQLCgLGft2abM=
@@ -1461,9 +1461,8 @@ sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HR
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.2.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/structured-merge-diff/v6 v6.3.1-0.20251003215857-446d8398e19c h1:RCkxmWwPjOw2O1RiDgBgI6tfISvB07jAh+GEztp7TWk=
sigs.k8s.io/structured-merge-diff/v6 v6.3.1-0.20251003215857-446d8398e19c/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=

View File

@@ -10,7 +10,7 @@ PATH="${INSTALL_PATH}:${PATH}"
[ -d "$INSTALL_PATH" ] || mkdir -p "$INSTALL_PATH"
# renovate: datasource=github-releases depName=gotestyourself/gotestsum packageName=gotestyourself/gotestsum
GOTESTSUM_VERSION=1.12.3
GOTESTSUM_VERSION=1.13.0
OS=$(go env GOOS)
ARCH=$(go env GOARCH)

View File

@@ -187,12 +187,6 @@ spec:
name: argocd-cmd-params-cm
key: applicationsetcontroller.requeue.after
optional: true
- name: ARGOCD_APPLICATIONSET_CONTROLLER_MAX_RESOURCES_STATUS_COUNT
valueFrom:
configMapKeyRef:
name: argocd-cmd-params-cm
key: applicationsetcontroller.status.max.resources.count
optional: true
volumeMounts:
- mountPath: /app/config/ssh
name: ssh-known-hosts
@@ -206,8 +200,6 @@ spec:
name: tmp
- name: argocd-repo-server-tls
mountPath: /app/config/reposerver/tls
- name: argocd-cmd-params-cm
mountPath: /home/argocd/params
securityContext:
capabilities:
drop:
@@ -243,12 +235,5 @@ spec:
path: tls.key
- key: ca.crt
path: ca.crt
- name: argocd-cmd-params-cm
configMap:
optional: true
name: argocd-cmd-params-cm
items:
- key: applicationsetcontroller.profile.enabled
path: profiler.enabled
nodeSelector:
kubernetes.io/os: linux

View File

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

View File

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

View File

@@ -40,7 +40,7 @@ spec:
serviceAccountName: argocd-redis
containers:
- name: redis
image: redis:8.2.2-alpine
image: redis:8.2.1-alpine
imagePullPolicy: Always
args:
- "--save"

View File

@@ -1485,9 +1485,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path
@@ -4901,9 +4900,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path
@@ -4984,9 +4982,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path
@@ -23740,9 +23737,6 @@ spec:
type: string
type: object
type: array
resourcesCount:
format: int64
type: integer
type: object
required:
- metadata
@@ -24844,13 +24838,7 @@ spec:
key: applicationsetcontroller.requeue.after
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATIONSET_CONTROLLER_MAX_RESOURCES_STATUS_COUNT
valueFrom:
configMapKeyRef:
key: applicationsetcontroller.status.max.resources.count
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -24880,8 +24868,6 @@ spec:
name: tmp
- mountPath: /app/config/reposerver/tls
name: argocd-repo-server-tls
- mountPath: /home/argocd/params
name: argocd-cmd-params-cm
nodeSelector:
kubernetes.io/os: linux
serviceAccountName: argocd-applicationset-controller
@@ -24910,13 +24896,6 @@ spec:
path: ca.crt
optional: true
secretName: argocd-repo-server-tls
- configMap:
items:
- key: applicationsetcontroller.profile.enabled
path: profiler.enabled
name: argocd-cmd-params-cm
optional: true
name: argocd-cmd-params-cm
---
apiVersion: apps/v1
kind: Deployment
@@ -24985,7 +24964,7 @@ spec:
key: log.format.timestamp
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -25097,7 +25076,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: Always
name: redis
ports:
@@ -25113,7 +25092,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -25404,7 +25383,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -25456,7 +25435,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -25804,7 +25783,7 @@ spec:
optional: true
- name: KUBECACHEDIR
value: /tmp/kubecache
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -1485,9 +1485,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path
@@ -4901,9 +4900,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path
@@ -4984,9 +4982,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path
@@ -23740,9 +23737,6 @@ spec:
type: string
type: object
type: array
resourcesCount:
format: int64
type: integer
type: object
required:
- metadata
@@ -24812,13 +24806,7 @@ spec:
key: applicationsetcontroller.requeue.after
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATIONSET_CONTROLLER_MAX_RESOURCES_STATUS_COUNT
valueFrom:
configMapKeyRef:
key: applicationsetcontroller.status.max.resources.count
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -24848,8 +24836,6 @@ spec:
name: tmp
- mountPath: /app/config/reposerver/tls
name: argocd-repo-server-tls
- mountPath: /home/argocd/params
name: argocd-cmd-params-cm
nodeSelector:
kubernetes.io/os: linux
serviceAccountName: argocd-applicationset-controller
@@ -24878,13 +24864,6 @@ spec:
path: ca.crt
optional: true
secretName: argocd-repo-server-tls
- configMap:
items:
- key: applicationsetcontroller.profile.enabled
path: profiler.enabled
name: argocd-cmd-params-cm
optional: true
name: argocd-cmd-params-cm
---
apiVersion: apps/v1
kind: Deployment
@@ -24931,7 +24910,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: Always
name: redis
ports:
@@ -24947,7 +24926,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -25238,7 +25217,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -25290,7 +25269,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -25638,7 +25617,7 @@ spec:
optional: true
- name: KUBECACHEDIR
value: /tmp/kubecache
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
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: v3.2.0
newTag: latest

View File

@@ -1484,9 +1484,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path
@@ -4900,9 +4899,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path
@@ -4983,9 +4981,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path

View File

@@ -17823,9 +17823,6 @@ spec:
type: string
type: object
type: array
resourcesCount:
format: int64
type: integer
type: object
required:
- metadata

View File

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

View File

@@ -1270,7 +1270,7 @@ spec:
automountServiceAccountToken: false
initContainers:
- name: config-init
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: IfNotPresent
resources:
{}
@@ -1310,7 +1310,7 @@ spec:
containers:
- name: redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: IfNotPresent
command:
- redis-server
@@ -1384,7 +1384,7 @@ spec:
- /bin/sh
- /readonly-config/trigger-failover-if-master.sh
- name: sentinel
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: IfNotPresent
command:
- redis-sentinel
@@ -1457,7 +1457,7 @@ spec:
- sleep 30; redis-cli -p 26379 sentinel reset argocd
- name: split-brain-fix
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: IfNotPresent
command:
- sh

View File

@@ -27,7 +27,7 @@ redis-ha:
serviceAccount:
automountToken: true
image:
tag: 8.2.2-alpine
tag: 8.2.1-alpine
sentinel:
bind: '0.0.0.0'
lifecycle:

View File

@@ -1485,9 +1485,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path
@@ -4901,9 +4900,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path
@@ -4984,9 +4982,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path
@@ -23740,9 +23737,6 @@ spec:
type: string
type: object
type: array
resourcesCount:
format: int64
type: integer
type: object
required:
- metadata
@@ -26210,13 +26204,7 @@ spec:
key: applicationsetcontroller.requeue.after
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATIONSET_CONTROLLER_MAX_RESOURCES_STATUS_COUNT
valueFrom:
configMapKeyRef:
key: applicationsetcontroller.status.max.resources.count
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -26246,8 +26234,6 @@ spec:
name: tmp
- mountPath: /app/config/reposerver/tls
name: argocd-repo-server-tls
- mountPath: /home/argocd/params
name: argocd-cmd-params-cm
nodeSelector:
kubernetes.io/os: linux
serviceAccountName: argocd-applicationset-controller
@@ -26276,13 +26262,6 @@ spec:
path: ca.crt
optional: true
secretName: argocd-repo-server-tls
- configMap:
items:
- key: applicationsetcontroller.profile.enabled
path: profiler.enabled
name: argocd-cmd-params-cm
optional: true
name: argocd-cmd-params-cm
---
apiVersion: apps/v1
kind: Deployment
@@ -26351,7 +26330,7 @@ spec:
key: log.format.timestamp
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -26502,7 +26481,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -26598,7 +26577,7 @@ spec:
key: notificationscontroller.repo.server.plaintext
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -26722,7 +26701,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -27039,7 +27018,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -27091,7 +27070,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -27465,7 +27444,7 @@ spec:
key: server.sync.replace.allowed
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -27849,7 +27828,7 @@ spec:
optional: true
- name: KUBECACHEDIR
value: /tmp/kubecache
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-application-controller
ports:
@@ -27947,7 +27926,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
@@ -28018,7 +27997,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: IfNotPresent
lifecycle:
postStart:
@@ -28093,7 +28072,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: IfNotPresent
name: split-brain-fix
resources: {}
@@ -28128,7 +28107,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: IfNotPresent
name: config-init
securityContext:

View File

@@ -1485,9 +1485,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path
@@ -4901,9 +4900,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path
@@ -4984,9 +4982,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path
@@ -23740,9 +23737,6 @@ spec:
type: string
type: object
type: array
resourcesCount:
format: int64
type: integer
type: object
required:
- metadata
@@ -26180,13 +26174,7 @@ spec:
key: applicationsetcontroller.requeue.after
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATIONSET_CONTROLLER_MAX_RESOURCES_STATUS_COUNT
valueFrom:
configMapKeyRef:
key: applicationsetcontroller.status.max.resources.count
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -26216,8 +26204,6 @@ spec:
name: tmp
- mountPath: /app/config/reposerver/tls
name: argocd-repo-server-tls
- mountPath: /home/argocd/params
name: argocd-cmd-params-cm
nodeSelector:
kubernetes.io/os: linux
serviceAccountName: argocd-applicationset-controller
@@ -26246,13 +26232,6 @@ spec:
path: ca.crt
optional: true
secretName: argocd-repo-server-tls
- configMap:
items:
- key: applicationsetcontroller.profile.enabled
path: profiler.enabled
name: argocd-cmd-params-cm
optional: true
name: argocd-cmd-params-cm
---
apiVersion: apps/v1
kind: Deployment
@@ -26338,7 +26317,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -26434,7 +26413,7 @@ spec:
key: notificationscontroller.repo.server.plaintext
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -26558,7 +26537,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -26875,7 +26854,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -26927,7 +26906,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -27301,7 +27280,7 @@ spec:
key: server.sync.replace.allowed
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -27685,7 +27664,7 @@ spec:
optional: true
- name: KUBECACHEDIR
value: /tmp/kubecache
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-application-controller
ports:
@@ -27783,7 +27762,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
@@ -27854,7 +27833,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: IfNotPresent
lifecycle:
postStart:
@@ -27929,7 +27908,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: IfNotPresent
name: split-brain-fix
resources: {}
@@ -27964,7 +27943,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: IfNotPresent
name: config-init
securityContext:

View File

@@ -1891,13 +1891,7 @@ spec:
key: applicationsetcontroller.requeue.after
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATIONSET_CONTROLLER_MAX_RESOURCES_STATUS_COUNT
valueFrom:
configMapKeyRef:
key: applicationsetcontroller.status.max.resources.count
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -1927,8 +1921,6 @@ spec:
name: tmp
- mountPath: /app/config/reposerver/tls
name: argocd-repo-server-tls
- mountPath: /home/argocd/params
name: argocd-cmd-params-cm
nodeSelector:
kubernetes.io/os: linux
serviceAccountName: argocd-applicationset-controller
@@ -1957,13 +1949,6 @@ spec:
path: ca.crt
optional: true
secretName: argocd-repo-server-tls
- configMap:
items:
- key: applicationsetcontroller.profile.enabled
path: profiler.enabled
name: argocd-cmd-params-cm
optional: true
name: argocd-cmd-params-cm
---
apiVersion: apps/v1
kind: Deployment
@@ -2032,7 +2017,7 @@ spec:
key: log.format.timestamp
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -2183,7 +2168,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -2279,7 +2264,7 @@ spec:
key: notificationscontroller.repo.server.plaintext
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -2403,7 +2388,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -2720,7 +2705,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -2772,7 +2757,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -3146,7 +3131,7 @@ spec:
key: server.sync.replace.allowed
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -3530,7 +3515,7 @@ spec:
optional: true
- name: KUBECACHEDIR
value: /tmp/kubecache
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-application-controller
ports:
@@ -3628,7 +3613,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
@@ -3699,7 +3684,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: IfNotPresent
lifecycle:
postStart:
@@ -3774,7 +3759,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: IfNotPresent
name: split-brain-fix
resources: {}
@@ -3809,7 +3794,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: IfNotPresent
name: config-init
securityContext:

View File

@@ -1861,13 +1861,7 @@ spec:
key: applicationsetcontroller.requeue.after
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATIONSET_CONTROLLER_MAX_RESOURCES_STATUS_COUNT
valueFrom:
configMapKeyRef:
key: applicationsetcontroller.status.max.resources.count
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -1897,8 +1891,6 @@ spec:
name: tmp
- mountPath: /app/config/reposerver/tls
name: argocd-repo-server-tls
- mountPath: /home/argocd/params
name: argocd-cmd-params-cm
nodeSelector:
kubernetes.io/os: linux
serviceAccountName: argocd-applicationset-controller
@@ -1927,13 +1919,6 @@ spec:
path: ca.crt
optional: true
secretName: argocd-repo-server-tls
- configMap:
items:
- key: applicationsetcontroller.profile.enabled
path: profiler.enabled
name: argocd-cmd-params-cm
optional: true
name: argocd-cmd-params-cm
---
apiVersion: apps/v1
kind: Deployment
@@ -2019,7 +2004,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -2115,7 +2100,7 @@ spec:
key: notificationscontroller.repo.server.plaintext
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -2239,7 +2224,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -2556,7 +2541,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -2608,7 +2593,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -2982,7 +2967,7 @@ spec:
key: server.sync.replace.allowed
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -3366,7 +3351,7 @@ spec:
optional: true
- name: KUBECACHEDIR
value: /tmp/kubecache
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-application-controller
ports:
@@ -3464,7 +3449,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
@@ -3535,7 +3520,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: IfNotPresent
lifecycle:
postStart:
@@ -3610,7 +3595,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: IfNotPresent
name: split-brain-fix
resources: {}
@@ -3645,7 +3630,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: IfNotPresent
name: config-init
securityContext:

View File

@@ -1485,9 +1485,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path
@@ -4901,9 +4900,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path
@@ -4984,9 +4982,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path
@@ -23740,9 +23737,6 @@ spec:
type: string
type: object
type: array
resourcesCount:
format: int64
type: integer
type: object
required:
- metadata
@@ -25288,13 +25282,7 @@ spec:
key: applicationsetcontroller.requeue.after
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATIONSET_CONTROLLER_MAX_RESOURCES_STATUS_COUNT
valueFrom:
configMapKeyRef:
key: applicationsetcontroller.status.max.resources.count
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -25324,8 +25312,6 @@ spec:
name: tmp
- mountPath: /app/config/reposerver/tls
name: argocd-repo-server-tls
- mountPath: /home/argocd/params
name: argocd-cmd-params-cm
nodeSelector:
kubernetes.io/os: linux
serviceAccountName: argocd-applicationset-controller
@@ -25354,13 +25340,6 @@ spec:
path: ca.crt
optional: true
secretName: argocd-repo-server-tls
- configMap:
items:
- key: applicationsetcontroller.profile.enabled
path: profiler.enabled
name: argocd-cmd-params-cm
optional: true
name: argocd-cmd-params-cm
---
apiVersion: apps/v1
kind: Deployment
@@ -25429,7 +25408,7 @@ spec:
key: log.format.timestamp
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -25580,7 +25559,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -25676,7 +25655,7 @@ spec:
key: notificationscontroller.repo.server.plaintext
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -25762,7 +25741,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: Always
name: redis
ports:
@@ -25778,7 +25757,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -26069,7 +26048,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -26121,7 +26100,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -26493,7 +26472,7 @@ spec:
key: server.sync.replace.allowed
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -26877,7 +26856,7 @@ spec:
optional: true
- name: KUBECACHEDIR
value: /tmp/kubecache
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-application-controller
ports:

51
manifests/install.yaml generated
View File

@@ -1485,9 +1485,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path
@@ -4901,9 +4900,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path
@@ -4984,9 +4982,8 @@ spec:
pattern: ^.{2,}|[^./]$
type: string
targetBranch:
description: |-
TargetBranch is the branch from which hydrated manifests will be synced.
If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
description: TargetBranch is the branch to which hydrated
manifests should be committed
type: string
required:
- path
@@ -23740,9 +23737,6 @@ spec:
type: string
type: object
type: array
resourcesCount:
format: int64
type: integer
type: object
required:
- metadata
@@ -25256,13 +25250,7 @@ spec:
key: applicationsetcontroller.requeue.after
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATIONSET_CONTROLLER_MAX_RESOURCES_STATUS_COUNT
valueFrom:
configMapKeyRef:
key: applicationsetcontroller.status.max.resources.count
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -25292,8 +25280,6 @@ spec:
name: tmp
- mountPath: /app/config/reposerver/tls
name: argocd-repo-server-tls
- mountPath: /home/argocd/params
name: argocd-cmd-params-cm
nodeSelector:
kubernetes.io/os: linux
serviceAccountName: argocd-applicationset-controller
@@ -25322,13 +25308,6 @@ spec:
path: ca.crt
optional: true
secretName: argocd-repo-server-tls
- configMap:
items:
- key: applicationsetcontroller.profile.enabled
path: profiler.enabled
name: argocd-cmd-params-cm
optional: true
name: argocd-cmd-params-cm
---
apiVersion: apps/v1
kind: Deployment
@@ -25414,7 +25393,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -25510,7 +25489,7 @@ spec:
key: notificationscontroller.repo.server.plaintext
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -25596,7 +25575,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: Always
name: redis
ports:
@@ -25612,7 +25591,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -25903,7 +25882,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -25955,7 +25934,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -26327,7 +26306,7 @@ spec:
key: server.sync.replace.allowed
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -26711,7 +26690,7 @@ spec:
optional: true
- name: KUBECACHEDIR
value: /tmp/kubecache
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -969,13 +969,7 @@ spec:
key: applicationsetcontroller.requeue.after
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATIONSET_CONTROLLER_MAX_RESOURCES_STATUS_COUNT
valueFrom:
configMapKeyRef:
key: applicationsetcontroller.status.max.resources.count
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -1005,8 +999,6 @@ spec:
name: tmp
- mountPath: /app/config/reposerver/tls
name: argocd-repo-server-tls
- mountPath: /home/argocd/params
name: argocd-cmd-params-cm
nodeSelector:
kubernetes.io/os: linux
serviceAccountName: argocd-applicationset-controller
@@ -1035,13 +1027,6 @@ spec:
path: ca.crt
optional: true
secretName: argocd-repo-server-tls
- configMap:
items:
- key: applicationsetcontroller.profile.enabled
path: profiler.enabled
name: argocd-cmd-params-cm
optional: true
name: argocd-cmd-params-cm
---
apiVersion: apps/v1
kind: Deployment
@@ -1110,7 +1095,7 @@ spec:
key: log.format.timestamp
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -1261,7 +1246,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -1357,7 +1342,7 @@ spec:
key: notificationscontroller.repo.server.plaintext
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -1443,7 +1428,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: Always
name: redis
ports:
@@ -1459,7 +1444,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -1750,7 +1735,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -1802,7 +1787,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -2174,7 +2159,7 @@ spec:
key: server.sync.replace.allowed
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2558,7 +2543,7 @@ spec:
optional: true
- name: KUBECACHEDIR
value: /tmp/kubecache
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -937,13 +937,7 @@ spec:
key: applicationsetcontroller.requeue.after
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATIONSET_CONTROLLER_MAX_RESOURCES_STATUS_COUNT
valueFrom:
configMapKeyRef:
key: applicationsetcontroller.status.max.resources.count
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -973,8 +967,6 @@ spec:
name: tmp
- mountPath: /app/config/reposerver/tls
name: argocd-repo-server-tls
- mountPath: /home/argocd/params
name: argocd-cmd-params-cm
nodeSelector:
kubernetes.io/os: linux
serviceAccountName: argocd-applicationset-controller
@@ -1003,13 +995,6 @@ spec:
path: ca.crt
optional: true
secretName: argocd-repo-server-tls
- configMap:
items:
- key: applicationsetcontroller.profile.enabled
path: profiler.enabled
name: argocd-cmd-params-cm
optional: true
name: argocd-cmd-params-cm
---
apiVersion: apps/v1
kind: Deployment
@@ -1095,7 +1080,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -1191,7 +1176,7 @@ spec:
key: notificationscontroller.repo.server.plaintext
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -1277,7 +1262,7 @@ spec:
secretKeyRef:
key: auth
name: argocd-redis
image: public.ecr.aws/docker/library/redis:8.2.2-alpine
image: public.ecr.aws/docker/library/redis:8.2.1-alpine
imagePullPolicy: Always
name: redis
ports:
@@ -1293,7 +1278,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -1584,7 +1569,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -1636,7 +1621,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -2008,7 +1993,7 @@ spec:
key: server.sync.replace.allowed
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2392,7 +2377,7 @@ spec:
optional: true
- name: KUBECACHEDIR
value: /tmp/kubecache
image: quay.io/argoproj/argocd:v3.2.0
image: quay.io/argoproj/argocd:latest
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -805,9 +805,6 @@ type ApplicationSetStatus struct {
ApplicationStatus []ApplicationSetApplicationStatus `json:"applicationStatus,omitempty" protobuf:"bytes,2,name=applicationStatus"`
// Resources is a list of Applications resources managed by this application set.
Resources []ResourceStatus `json:"resources,omitempty" protobuf:"bytes,3,opt,name=resources"`
// ResourcesCount is the total number of resources managed by this application set. The count may be higher than actual number of items in the Resources field when
// the number of managed resources exceeds the limit imposed by the controller (to avoid making the status field too large).
ResourcesCount int64 `json:"resourcesCount,omitempty" protobuf:"varint,4,opt,name=resourcesCount"`
}
// ApplicationSetCondition contains details about an applicationset condition, which is usually an error or warning
@@ -870,6 +867,20 @@ const (
ApplicationSetReasonSyncApplicationError = "SyncApplicationError"
)
// Represents resource health status
type ProgressiveSyncStatusCode string
const (
// Indicates that an Application sync is waiting to be trigerred
ProgressiveSyncWaiting ProgressiveSyncStatusCode = "Waiting"
// Indicates that a sync has been trigerred, but the application did not report any status
ProgressiveSyncPending ProgressiveSyncStatusCode = "Pending"
// Indicates that the application has not yet reached an Healthy state in regards to the requested sync
ProgressiveSyncProgressing ProgressiveSyncStatusCode = "Progressing"
// Indicates that the application has reached an Healthy state in regards to the requested sync
ProgressiveSyncHealthy ProgressiveSyncStatusCode = "Healthy"
)
// ApplicationSetApplicationStatus contains details about each Application managed by the ApplicationSet
type ApplicationSetApplicationStatus struct {
// Application contains the name of the Application resource
@@ -878,8 +889,8 @@ type ApplicationSetApplicationStatus struct {
LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,2,opt,name=lastTransitionTime"`
// Message contains human-readable message indicating details about the status
Message string `json:"message" protobuf:"bytes,3,opt,name=message"`
// Status contains the AppSet's perceived status of the managed Application resource: (Waiting, Pending, Progressing, Healthy)
Status string `json:"status" protobuf:"bytes,4,opt,name=status"`
// Status contains the AppSet's perceived status of the managed Application resource
Status ProgressiveSyncStatusCode `json:"status" protobuf:"bytes,4,opt,name=status"`
// Step tracks which step this Application should be updated in
Step string `json:"step" protobuf:"bytes,5,opt,name=step"`
// TargetRevision tracks the desired revisions the Application should be synced to.

File diff suppressed because it is too large Load Diff

View File

@@ -222,7 +222,7 @@ message ApplicationSetApplicationStatus {
// Message contains human-readable message indicating details about the status
optional string message = 3;
// Status contains the AppSet's perceived status of the managed Application resource: (Waiting, Pending, Progressing, Healthy)
// Status contains the AppSet's perceived status of the managed Application resource
optional string status = 4;
// Step tracks which step this Application should be updated in
@@ -369,10 +369,6 @@ message ApplicationSetStatus {
// Resources is a list of Applications resources managed by this application set.
repeated ResourceStatus resources = 3;
// ResourcesCount is the total number of resources managed by this application set. The count may be higher than actual number of items in the Resources field when
// the number of managed resources exceeds the limit imposed by the controller (to avoid making the status field too large).
optional int64 resourcesCount = 4;
}
// ApplicationSetStrategy configures how generated Applications are updated in sequence.
@@ -2639,8 +2635,7 @@ message SyncPolicyAutomated {
// SyncSource specifies a location from which hydrated manifests may be synced. RepoURL is assumed based on the
// associated DrySource config in the SourceHydrator.
message SyncSource {
// TargetBranch is the branch from which hydrated manifests will be synced.
// If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
// TargetBranch is the branch to which hydrated manifests should be committed
optional string targetBranch = 1;
// Path is a directory path within the git repository where hydrated manifests should be committed to and synced

View File

@@ -443,8 +443,7 @@ type DrySource struct {
// SyncSource specifies a location from which hydrated manifests may be synced. RepoURL is assumed based on the
// associated DrySource config in the SourceHydrator.
type SyncSource struct {
// TargetBranch is the branch from which hydrated manifests will be synced.
// If HydrateTo is not set, this is also the branch to which hydrated manifests are committed.
// TargetBranch is the branch to which hydrated manifests should be committed
TargetBranch string `json:"targetBranch" protobuf:"bytes,1,name=targetBranch"`
// Path is a directory path within the git repository where hydrated manifests should be committed to and synced
// from. The Path should never point to the root of the repo. If hydrateTo is set, this is just the path from which

3
renovate.json Normal file
View File

@@ -0,0 +1,3 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}

View File

@@ -1,4 +1,4 @@
-- Health check copied from here: https://github.com/crossplane/docs/blob/709889c5dbe6e5a2ea3dffd66fe276cf465b47b5/content/master/guides/crossplane-with-argo-cd.md
-- Health check copied from here: https://github.com/crossplane/docs/blob/bd701357e9d5eecf529a0b42f23a78850a6d1d87/content/master/guides/crossplane-with-argo-cd.md
health_status = {
status = "Progressing",
@@ -18,10 +18,9 @@ local has_no_status = {
"Composition",
"CompositionRevision",
"DeploymentRuntimeConfig",
"ClusterProviderConfig",
"ControllerConfig",
"ProviderConfig",
"ProviderConfigUsage",
"ControllerConfig" -- Added to ensure that healthcheck is backwards-compatible with Crossplane v1
"ProviderConfigUsage"
}
if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.kind) then
health_status.status = "Healthy"
@@ -30,7 +29,7 @@ if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.
end
if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then
if (obj.kind == "ProviderConfig" or obj.kind == "ClusterProviderConfig") and obj.status.users ~= nil then
if obj.kind == "ProviderConfig" and obj.status.users ~= nil then
health_status.status = "Healthy"
health_status.message = "Resource is in use."
return health_status
@@ -55,7 +54,7 @@ for i, condition in ipairs(obj.status.conditions) do
end
end
if contains({"Ready", "Healthy", "Offered", "Established", "ValidPipeline", "RevisionHealthy"}, condition.type) then
if contains({"Ready", "Healthy", "Offered", "Established"}, condition.type) then
if condition.status == "True" then
health_status.status = "Healthy"
health_status.message = "Resource is up-to-date."

View File

@@ -3,7 +3,3 @@ tests:
status: Healthy
message: "Resource is up-to-date."
inputPath: testdata/composition_healthy.yaml
- healthStatus:
status: Healthy
message: "Resource is up-to-date."
inputPath: testdata/configurationrevision_healthy.yaml

View File

@@ -1,22 +0,0 @@
apiVersion: pkg.crossplane.io/v1
kind: ConfigurationRevision
metadata:
annotations:
meta.crossplane.io/license: Apache-2.0
meta.crossplane.io/maintainer: Upbound <support@upbound.io>
meta.crossplane.io/source: github.com/upbound/configuration-getting-started
name: upbound-configuration-getting-started-869bca254eb1
spec:
desiredState: Active
ignoreCrossplaneConstraints: false
image: xpkg.upbound.io/upbound/configuration-getting-started:v0.3.0
packagePullPolicy: IfNotPresent
revision: 1
skipDependencyResolution: false
status:
conditions:
- lastTransitionTime: "2025-09-29T18:06:40Z"
observedGeneration: 1
reason: HealthyPackageRevision
status: "True"
type: RevisionHealthy

View File

@@ -1,4 +1,4 @@
-- Health check copied from here: https://github.com/crossplane/docs/blob/709889c5dbe6e5a2ea3dffd66fe276cf465b47b5/content/master/guides/crossplane-with-argo-cd.md
-- Health check copied from here: https://github.com/crossplane/docs/blob/bd701357e9d5eecf529a0b42f23a78850a6d1d87/content/master/guides/crossplane-with-argo-cd.md
health_status = {
status = "Progressing",
@@ -15,7 +15,6 @@ local function contains (table, val)
end
local has_no_status = {
"ClusterProviderConfig",
"ProviderConfig",
"ProviderConfigUsage"
}
@@ -27,7 +26,7 @@ if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.
end
if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then
if (obj.kind == "ProviderConfig" or obj.kind == "ClusterProviderConfig") and obj.status.users ~= nil then
if obj.kind == "ProviderConfig" and obj.status.users ~= nil then
health_status.status = "Healthy"
health_status.message = "Resource is in use."
return health_status

View File

@@ -7,6 +7,3 @@ discoveryTests:
- inputPath: testdata/external-secret.yaml
result:
- name: "refresh"
- inputPath: testdata/external-secret-refresh-policy.yaml
result:
- name: "refresh"

View File

@@ -3,11 +3,10 @@ local actions = {}
local disable_refresh = false
local time_units = {"ns", "us", "µs", "ms", "s", "m", "h"}
local digits = obj.spec.refreshInterval
local policy = obj.spec.refreshPolicy
if digits ~= nil then
digits = tostring(digits)
for _, time_unit in ipairs(time_units) do
if (digits == "0" or digits == "0" .. time_unit) and policy ~= "OnChange" then
if digits == "0" or digits == "0" .. time_unit then
disable_refresh = true
break
end

View File

@@ -1,55 +0,0 @@
apiVersion: external-secrets.io/v1alpha1
kind: ExternalSecret
metadata:
creationTimestamp: '2021-11-16T21:59:33Z'
generation: 1
name: test-healthy
namespace: argocd
resourceVersion: '136487331'
selfLink: /apis/external-secrets.io/v1alpha1/namespaces/argocd/externalsecrets/test-healthy
uid: 1e754a7e-0781-4d57-932d-4651d5b19586
spec:
data:
- remoteRef:
key: secret/sa/example
property: api.address
secretKey: url
- remoteRef:
key: secret/sa/example
property: ca.crt
secretKey: ca
- remoteRef:
key: secret/sa/example
property: token
secretKey: token
refreshInterval: 0
refreshPolicy: OnChange
secretStoreRef:
kind: SecretStore
name: example
target:
creationPolicy: Owner
template:
data:
config: |
{
"bearerToken": "{{ .token | base64decode | toString }}",
"tlsClientConfig": {
"insecure": false,
"caData": "{{ .ca | toString }}"
}
}
name: cluster-test
server: '{{ .url | toString }}'
metadata:
labels:
argocd.argoproj.io/secret-type: cluster
status:
conditions:
- lastTransitionTime: '2021-11-16T21:59:34Z'
message: Secret was synced
reason: SecretSynced
status: 'True'
type: Ready
refreshTime: '2021-11-29T18:32:24Z'
syncedResourceVersion: 1-519a61da0dc68b2575b4f8efada70e42

View File

@@ -9,39 +9,10 @@ function checkConditions(conditions, conditionType)
return true
end
-- isParentGenerationObserved checks if a parent's conditions match the current resource generation
-- For HTTPRoute, observedGeneration is stored in each condition within a parent
function isParentGenerationObserved(obj, parent)
if obj.metadata.generation == nil then
-- If no generation is set, accept all conditions
return true
end
if parent.conditions == nil or #parent.conditions == 0 then
return false
end
-- Check if all conditions have observedGeneration matching current generation
for _, condition in ipairs(parent.conditions) do
if condition.observedGeneration ~= nil then
if condition.observedGeneration ~= obj.metadata.generation then
return false
end
end
end
return true
end
if obj.status ~= nil then
if obj.status.parents ~= nil then
for _, parent in ipairs(obj.status.parents) do
if parent.conditions ~= nil then
-- Skip this parent if it's not from the current generation
if not isParentGenerationObserved(obj, parent) then
goto continue
end
local resolvedRefsFalse, resolvedRefsMsg = checkConditions(parent.conditions, "ResolvedRefs")
local acceptedFalse, acceptedMsg = checkConditions(parent.conditions, "Accepted")
@@ -73,20 +44,15 @@ if obj.status ~= nil then
hs.message = "Parent " .. (parent.parentRef.name or "") .. ": " .. progressingMsg
return hs
end
::continue::
end
end
if #obj.status.parents > 0 then
for _, parent in ipairs(obj.status.parents) do
if parent.conditions ~= nil and #parent.conditions > 0 then
-- Only mark as healthy if we found a parent from the current generation
if isParentGenerationObserved(obj, parent) then
hs.status = "Healthy"
hs.message = "HTTPRoute is healthy"
return hs
end
hs.status = "Healthy"
hs.message = "HTTPRoute is healthy"
return hs
end
end
end

View File

@@ -14,8 +14,4 @@ tests:
- healthStatus:
status: Progressing
message: "Parent example-gateway: Route is still being programmed"
inputPath: testdata/progressing.yaml
- healthStatus:
status: Healthy
message: HTTPRoute is healthy
inputPath: testdata/healthy_multiple_generations.yaml
inputPath: testdata/progressing.yaml

View File

@@ -1,59 +0,0 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: example-httproute
generation: 2
spec:
parentRefs:
- kind: Gateway
name: eg
namespace: envoy-gateway-system
sectionName: foo-nonexistent
hostnames:
- "example-httproute.example.com"
rules:
- backendRefs:
- name: example-service
port: 8080
status:
parents:
- conditions:
- lastTransitionTime: "2025-10-14T11:19:41Z"
message: No listeners match this parent ref
observedGeneration: 1
reason: NoMatchingParent
status: "False"
type: Accepted
- lastTransitionTime: "2025-10-14T11:19:41Z"
message: Resolved all the Object references for the Route
observedGeneration: 1
reason: ResolvedRefs
status: "True"
type: ResolvedRefs
controllerName: gateway.envoyproxy.io/gatewayclass-controller
parentRef:
group: gateway.networking.k8s.io
kind: Gateway
name: eg
namespace: envoy-gateway-system
sectionName: foo-nonexistent
- conditions:
- lastTransitionTime: "2025-10-14T11:25:18Z"
message: Route is accepted
observedGeneration: 2
reason: Accepted
status: "True"
type: Accepted
- lastTransitionTime: "2025-10-14T11:25:18Z"
message: Resolved all the Object references for the Route
observedGeneration: 2
reason: ResolvedRefs
status: "True"
type: ResolvedRefs
controllerName: gateway.envoyproxy.io/gatewayclass-controller
parentRef:
group: gateway.networking.k8s.io
kind: Gateway
name: eg
namespace: envoy-gateway-system
sectionName: https-net

View File

@@ -25,17 +25,9 @@ if obj.status.conditions then
hs.message = "Waiting for Argo CD commit status spec update to be observed"
return hs
end
-- Check for any False condition status
if condition.status == "False" then
if condition.status == "False" and condition.reason == "ReconciliationError" then
hs.status = "Degraded"
local msg = condition.message or "Unknown error"
local reason = condition.reason or "Unknown"
-- Don't include ReconciliationError in the message since it's redundant
if reason == "ReconciliationError" then
hs.message = "Argo CD commit status reconciliation failed: " .. msg
else
hs.message = "Argo CD commit status reconciliation failed (" .. reason .. "): " .. msg
end
hs.message = "Argo CD commit status reconciliation failed: " .. (condition.message or "Unknown error")
return hs
end
end

View File

@@ -15,10 +15,6 @@ tests:
status: Degraded
message: "Argo CD commit status reconciliation failed: Something went wrong"
inputPath: testdata/reconcile-error.yaml
- healthStatus:
status: Degraded
message: "Argo CD commit status reconciliation failed: Failed to query Argo CD applications: connection timeout"
inputPath: testdata/non-reconciliation-error.yaml
- healthStatus:
status: Progressing
message: Argo CD commit status is not ready yet

View File

@@ -1,21 +0,0 @@
apiVersion: promoter.argoproj.io/v1alpha1
kind: ArgoCDCommitStatus
metadata:
name: test-commit-status
namespace: test
generation: 1
spec:
applicationSelector:
matchLabels:
environment: production
promotionStrategyRef:
name: test
status:
conditions:
- lastTransitionTime: '2025-10-15T16:00:00Z'
message: 'Failed to query Argo CD applications: connection timeout'
observedGeneration: 1
reason: ReconciliationError
status: 'False'
type: Ready

View File

@@ -26,17 +26,9 @@ if obj.status.conditions then
hs.message = "Waiting for change transfer policy spec update to be observed"
return hs
end
-- Check for any False condition status
if condition.status == "False" then
if condition.status == "False" and condition.reason == "ReconciliationError" then
hs.status = "Degraded"
local msg = condition.message or "Unknown error"
local reason = condition.reason or "Unknown"
-- Don't include ReconciliationError in the message since it's redundant
if reason == "ReconciliationError" then
hs.message = "Change transfer policy reconciliation failed: " .. msg
else
hs.message = "Change transfer policy reconciliation failed (" .. reason .. "): " .. msg
end
hs.message = "Change transfer policy reconciliation failed: " .. (condition.message or "Unknown error")
return hs
end
end

View File

@@ -39,7 +39,3 @@ tests:
status: Healthy
message: "Environment is up-to-date, but there are non-successful active commit statuses: 1 pending, 0 successful, 0 failed. Pending commit statuses: argocd-health. "
inputPath: testdata/non-successful-environments.yaml
- healthStatus:
status: Degraded
message: "Change transfer policy reconciliation failed (PullRequestNotReady): PullRequest \"deployment-environments-qal-usw2-next-environments-qal-usw2-7a8e7b70\" is not Ready because \"ReconciliationError\": Reconciliation failed: failed to merge pull request: failed to merge pull request: failed to merge pull request: PUT https://github.example.com/api/v3/repos/org/deployment/pulls/3/merge: 405 At least 2 approving reviews are required by reviewers with write access. Required status check \"continuous-integration/jenkins/pr-head\" is expected. You're not authorized to push to this branch."
inputPath: testdata/missing-sha-and-not-ready.yaml

View File

@@ -1,87 +0,0 @@
apiVersion: promoter.argoproj.io/v1alpha1
kind: ChangeTransferPolicy
metadata:
annotations:
promoter.argoproj.io/reconcile-at: '2025-10-15T17:11:51.672763364Z'
creationTimestamp: '2025-10-15T16:26:17Z'
generation: 1
labels:
promoter.argoproj.io/environment: environments-qal-usw2
promoter.argoproj.io/promotion-strategy: strategy
name: strategy-environments-qal-usw2-27894e05
namespace: test
ownerReferences:
- apiVersion: promoter.argoproj.io/v1alpha1
blockOwnerDeletion: true
controller: true
kind: PromotionStrategy
name: strategy
uid: 146aaaba-72d5-4155-bf5b-b5fd03b8a6d0
resourceVersion: '116412858'
uid: 62e1ed12-d808-4c78-9217-f80822f930ae
spec:
activeBranch: environments/qal-usw2
activeCommitStatuses:
- key: argocd-health
autoMerge: true
gitRepositoryRef:
name: repo
proposedBranch: environments/qal-usw2-next
status:
active:
commitStatuses:
- key: argocd-health
phase: pending
dry: {}
hydrated:
author: ServiceAccount
commitTime: '2025-10-15T15:38:20Z'
sha: b060fc7f5079a5aa7364a3844cfe26ee3084e282
subject: '[Changed] - infrastructure deployment'
conditions:
- lastTransitionTime: '2025-10-15T17:34:14Z'
message: 'PullRequest "deployment-environments-qal-usw2-next-environments-qal-usw2-7a8e7b70" is not Ready because "ReconciliationError": Reconciliation failed: failed to merge pull request: failed to merge pull request: failed to merge pull request: PUT https://github.example.com/api/v3/repos/org/deployment/pulls/3/merge: 405 At least 2 approving reviews are required by reviewers with write access. Required status check "continuous-integration/jenkins/pr-head" is expected. You''re not authorized to push to this branch.'
observedGeneration: 1
reason: PullRequestNotReady
status: 'False'
type: Ready
history:
- active:
dry: {}
hydrated:
author: ServiceAccount
commitTime: '2025-10-15T15:38:20Z'
sha: b060fc7f5079a5aa7364a3844cfe26ee3084e282
subject: '[Changed] - infrastructure deployment'
proposed:
hydrated: {}
pullRequest: {}
- active:
dry: {}
hydrated:
author: ServiceAccount
commitTime: '2025-10-15T15:15:27Z'
sha: bb607f8854d83de1724bcc5515f685adf9d8a704
subject: Initial commit
proposed:
hydrated: {}
pullRequest: {}
proposed:
dry:
author: user <user@example.com>
commitTime: '2025-10-15T16:24:35Z'
repoURL: https://github.example.com/org/deployment.git
sha: ac7866685e376f32aaf09dced3cf2d39dc1ba50e
subject: Create promotion-strategy.yaml
hydrated:
author: Argo CD
body: 'Co-authored-by: user <user@example.com>'
commitTime: '2025-10-15T16:26:18Z'
sha: 2e8097c049a97c05e6f787f46aabfe3cc2a8af21
subject: 'ac78666: Create promotion-strategy.yaml'
pullRequest:
id: '3'
prCreationTime: '2025-10-15T17:11:53Z'
state: open
url: https://github.example.com/org/deployment/pull/3

View File

@@ -25,17 +25,9 @@ if obj.status.conditions then
hs.message = "Waiting for commit status spec update to be observed"
return hs
end
-- Check for any False condition status
if condition.status == "False" then
if condition.status == "False" and condition.reason == "ReconciliationError" then
hs.status = "Degraded"
local msg = condition.message or "Unknown error"
local reason = condition.reason or "Unknown"
-- Don't include ReconciliationError in the message since it's redundant
if reason == "ReconciliationError" then
hs.message = "Commit status reconciliation failed: " .. msg
else
hs.message = "Commit status reconciliation failed (" .. reason .. "): " .. msg
end
hs.message = "Commit status reconciliation failed: " .. (condition.message or "Unknown error")
return hs
end
end

View File

@@ -15,10 +15,6 @@ tests:
status: Degraded
message: "Commit status reconciliation failed: Something went wrong"
inputPath: testdata/reconcile-error.yaml
- healthStatus:
status: Degraded
message: "Commit status reconciliation failed: Failed to update commit status: API request failed"
inputPath: testdata/non-reconciliation-error.yaml
- healthStatus:
status: Progressing
message: Commit status is not ready yet

View File

@@ -1,25 +0,0 @@
apiVersion: promoter.argoproj.io/v1alpha1
kind: CommitStatus
metadata:
name: test-commit-status
namespace: test
generation: 1
spec:
sha: abc1234567890
url: https://example.com/ci/test
gitRepositoryRef:
name: repo
description: example
name: example
phase: success
status:
conditions:
- lastTransitionTime: '2025-10-15T16:00:00Z'
message: 'Failed to update commit status: API request failed'
observedGeneration: 1
reason: ReconciliationError
status: 'False'
type: Ready
id: "1"
sha: abc1234567890

View File

@@ -26,17 +26,9 @@ if obj.status.conditions then
hs.message = "Waiting for promotion strategy spec update to be observed"
return hs
end
-- Check for any False condition status
if condition.status == "False" then
if condition.status == "False" and condition.reason == "ReconciliationError" then
hs.status = "Degraded"
local msg = condition.message or "Unknown error"
local reason = condition.reason or "Unknown"
-- Don't include ReconciliationError in the message since it's redundant
if reason == "ReconciliationError" then
hs.message = "Promotion strategy reconciliation failed: " .. msg
else
hs.message = "Promotion strategy reconciliation failed (" .. reason .. "): " .. msg
end
hs.message = "Promotion strategy reconciliation failed: " .. (condition.message or "Unknown error")
return hs
end
end
@@ -76,7 +68,7 @@ local proposedSha = obj.status.environments[1].proposed.dry.sha -- Don't panic,
for _, env in ipairs(obj.status.environments) do
if env.proposed.dry.sha ~= proposedSha then
hs.status = "Progressing"
hs.message = "Not all environments have the same proposed commit SHA. This likely means the hydrator has not run for all environments yet."
hs.status = "Not all environments have the same proposed commit SHA. This likely means the hydrator has not run for all environments yet."
return hs
end
end

View File

@@ -39,11 +39,3 @@ tests:
status: Healthy
message: All environments are up-to-date on commit 'abc1234'.
inputPath: testdata/all-healthy.yaml
- healthStatus:
status: Progressing
message: Not all environments have the same proposed commit SHA. This likely means the hydrator has not run for all environments yet.
inputPath: testdata/different-proposed-commits.yaml
- healthStatus:
status: Degraded
message: "Promotion strategy reconciliation failed (ChangeTransferPolicyNotReady): ChangeTransferPolicy \"strategy-environments-qal-usw2-27894e05\" is not Ready because \"ReconciliationError\": Reconciliation failed: failed to calculate ChangeTransferPolicy status: failed to get SHAs for proposed branch \"environments/qal-usw2-next\": exit status 128: fatal: 'origin/environments/qal-usw2-next' is not a commit and a branch 'environments/qal-usw2-next' cannot be created from it"
inputPath: testdata/missing-sha-and-not-ready.yaml

View File

@@ -1,412 +0,0 @@
apiVersion: promoter.argoproj.io/v1alpha1
kind: PromotionStrategy
metadata:
name: promotion-strategy
namespace: gitops-promoter
spec:
activeCommitStatuses:
- key: argocd-health
environments:
- autoMerge: false
branch: wave/0/west
- autoMerge: false
branch: wave/1/west
- autoMerge: false
branch: wave/2/west
- autoMerge: false
branch: wave/3/west
- autoMerge: false
branch: wave/4/west
- autoMerge: false
branch: wave/5/west
gitRepositoryRef:
name: cdp-deployments
status:
conditions:
- lastTransitionTime: '2025-08-02T06:43:44Z'
message: Reconciliation succeeded
observedGeneration: 6
reason: ReconciliationSuccess
status: 'True'
type: Ready
environments:
- active:
commitStatuses:
- key: argocd-health
phase: pending
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-24T14:13:27Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: f94c9d42993145d9f10bb2c404b74da14ee3f74e
subject: 'chore: fake new commit message'
hydrated:
author: GitOps Promoter
commitTime: '2025-09-24T14:14:21Z'
sha: 6b1095752ea7bef9c5c4251cb0722ce33dfd68d1
subject: >-
This is a no-op commit merging from wave/0/west-next into
wave/0/west
branch: wave/0/west
history:
- active:
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-23T17:33:12Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: 28f351384c8b8e58624726c4c3713979d0143775
subject: 'chore: fake old commit message'
hydrated:
author: argo-cd[bot]
body: "Fake commit message"
commitTime: '2025-09-23T17:36:40Z'
sha: 3ebd45b702bc23619c8ae6724e81955afc644555
subject: Promote 28f35 to `wave/0/west` (#2929)
proposed:
hydrated: {}
pullRequest: {}
- active:
dry:
author: asingh51 <fake@example.com>
commitTime: '2025-09-22T21:04:40Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: 9dc9529ddba077fcdf8cffef716b7d20e1e8b844
subject: >-
Onboarding new cluster to argocd-genai on wave 5 [ARGO-2499]
(#2921)
hydrated:
author: argo-cd[bot]
body: "Fake commit message"
commitTime: '2025-09-22T21:56:33Z'
sha: 2fefa5165e46988666cbb1d2d4f39e21871fa671
subject: Promote 9dc95 to `wave/0/west` (#2925)
proposed:
hydrated: {}
pullRequest: {}
- active:
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-22T19:29:53Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: 7f91282cc8146644d0721efde1b9069ccbd475a1
subject: >-
chore: temporarily disable server-side diff on argo-argo
(ARGO-2528) (#2917)
hydrated:
author: argo-cd[bot]
body: "Fake commit message"
commitTime: '2025-09-22T19:32:09Z'
sha: 6c6493caa0d5b7211bb09c93a7047c90db393cd6
subject: Promote 7f912 to `wave/0/west` (#2922)
proposed:
hydrated: {}
pullRequest: {}
proposed:
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-24T14:13:27Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: f94c9d42993145d9f10bb2c404b74da14ee3f74e
subject: 'chore: fake new commit message'
hydrated:
author: GitOps Promoter
commitTime: '2025-09-24T14:14:16Z'
sha: 9dfd6245b7b2e86660a13743ecc85d26bf3df04c
subject: Merge branch 'wave/0/west' into wave/0/west-next
- active:
commitStatuses:
- key: argocd-health
phase: pending
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-24T14:13:27Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: f94c9d42993145d9f10bb2c404b74da14ee3f74e
subject: 'chore: fake new commit message'
hydrated:
author: GitOps Promoter
commitTime: '2025-09-24T14:14:01Z'
sha: 580bbde2b775a8c6dc1484c4ba970426029e63b4
subject: >-
This is a no-op commit merging from wave/1/west-next into
wave/1/west
branch: wave/1/west
history:
- active:
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-23T17:33:12Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: 28f351384c8b8e58624726c4c3713979d0143775
subject: 'chore: fake old commit message'
hydrated:
author: argo-cd[bot]
body: "Fake commit message"
commitTime: '2025-09-23T17:37:46Z'
sha: c2b69cd2186df736ee4e27a6de5590135a3c5823
subject: Promote 28f35 to `wave/1/west` (#2928)
proposed:
hydrated: {}
pullRequest: {}
- active:
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-22T19:29:53Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: 7f91282cc8146644d0721efde1b9069ccbd475a1
subject: >-
chore: temporarily disable server-side diff on argo-argo
(ARGO-2528) (#2917)
hydrated:
author: argo-cd[bot]
body: "Fake commit message"
commitTime: '2025-09-22T20:15:13Z'
sha: baf2f21f5085adaa7fe4720ca41d4f7ff918d4fd
subject: Promote 7f912 to `wave/1/west` (#2883)
proposed:
hydrated: {}
pullRequest: {}
- active:
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-16T16:32:32Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: 789df516a84b1d3beb2f8ffe56e052b5b411aa74
subject: >-
chore: upgrade to 3.2.0-rc1, argo and team instances (ARGO-2412)
(#2875)
hydrated:
author: argo-cd[bot]
body: "Fake commit message"
commitTime: '2025-09-16T16:45:40Z'
sha: 728e1a237732a20281f986916301a5dca254e3a4
subject: Promote 789df to `wave/1/west` (#2876)
proposed:
hydrated: {}
pullRequest: {}
proposed:
commitStatuses:
- key: promoter-previous-environment
phase: pending
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-24T14:13:27Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: f94c9d42993145d9f10bb2c404b74da14ee3f74e
subject: 'chore: fake new commit message'
hydrated:
author: GitOps Promoter
commitTime: '2025-09-24T14:13:56Z'
sha: 50b7c73718813b9b92a0e1e166798cbd55874bbd
subject: Merge branch 'wave/1/west' into wave/1/west-next
- active:
commitStatuses:
- key: argocd-health
phase: pending
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-23T17:33:12Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: 28f351384c8b8e58624726c4c3713979d0143775
subject: 'chore: fake old commit message'
hydrated:
author: GitOps Promoter
commitTime: '2025-09-23T17:34:05Z'
sha: 169626417d1863434901c9225ded78963b7bd691
subject: >-
This is a no-op commit merging from wave/2/west-next into
wave/2/west
branch: wave/2/west
history:
- active:
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-22T19:29:53Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: 7f91282cc8146644d0721efde1b9069ccbd475a1
subject: >-
chore: temporarily disable server-side diff on argo-argo
(ARGO-2528) (#2917)
hydrated:
author: argo-cd[bot]
body: "Fake commit message"
commitTime: '2025-09-22T20:59:45Z'
sha: 1bfe993b41fe27b298c0aab9a5f185b29a478178
subject: Promote 7f912 to `wave/2/west` (#2884)
proposed:
hydrated: {}
pullRequest: {}
proposed:
commitStatuses:
- key: promoter-previous-environment
phase: pending
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-23T17:33:12Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: 28f351384c8b8e58624726c4c3713979d0143775
subject: 'chore: fake old commit message'
hydrated:
author: Argo CD
body: "Fake commit message"
commitTime: '2025-09-23T17:33:50Z'
sha: 6e0e11f0102faa5779223159a2b16ccee63a8ac0
subject: '28f3513: chore: fake old commit message'
- active:
commitStatuses:
- key: argocd-health
phase: pending
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-24T14:13:27Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: f94c9d42993145d9f10bb2c404b74da14ee3f74e
subject: 'chore: fake new commit message'
hydrated:
author: GitOps Promoter
commitTime: '2025-09-24T14:14:08Z'
sha: 73cf41db84a6e97a74c967243e65ffce66dd4198
subject: >-
This is a no-op commit merging from wave/3/west-next into
wave/3/west
branch: wave/3/west
history:
- active:
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-22T19:29:53Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: 7f91282cc8146644d0721efde1b9069ccbd475a1
subject: >-
chore: fake super old commit message
hydrated:
author: argo-cd[bot]
body: "Fake commit message"
commitTime: '2025-09-22T21:00:07Z'
sha: e7456f8ff4a5943ace3bb425e2d4e7dd43a53e46
subject: Promote 7f912 to `wave/3/west` (#2887)
proposed:
hydrated: {}
pullRequest: {}
proposed:
commitStatuses:
- key: promoter-previous-environment
phase: pending
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-24T14:13:27Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: f94c9d42993145d9f10bb2c404b74da14ee3f74e
subject: 'chore: fake new commit message'
hydrated:
author: Argo CD
body: "Fake commit message"
commitTime: '2025-09-24T14:13:49Z'
sha: daf0534e9db36334c82870cbb2e5d14cc687e0f5
subject: 'f94c9d4: chore: fake new commit message'
- active:
commitStatuses:
- key: argocd-health
phase: pending
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-24T14:13:27Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: f94c9d42993145d9f10bb2c404b74da14ee3f74e
subject: 'chore: fake new commit message'
hydrated:
author: GitOps Promoter
commitTime: '2025-09-24T14:14:30Z'
sha: 5abc8f1fd4056ad9f78723f3a83ac2291e87c821
subject: >-
This is a no-op commit merging from wave/4/west-next into
wave/4/west
branch: wave/4/west
history:
- active:
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-22T19:29:53Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: 7f91282cc8146644d0721efde1b9069ccbd475a1
subject: >-
chore: temporarily disable server-side diff on argo-argo
(ARGO-2528) (#2917)
hydrated:
author: argo-cd[bot]
body: "Fake commit message"
commitTime: '2025-09-22T21:00:29Z'
sha: 1d6e2b279108f6430825c889f6d2248fe4388aba
subject: Promote 7f912 to `wave/4/west` (#2885)
proposed:
hydrated: {}
pullRequest: {}
proposed:
commitStatuses:
- key: promoter-previous-environment
phase: pending
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-24T14:13:27Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: f94c9d42993145d9f10bb2c404b74da14ee3f74e
subject: 'chore: fake new commit message'
hydrated:
author: Argo CD
body: "Fake commit message"
commitTime: '2025-09-24T14:14:09Z'
sha: 91d942a1da3799d6027a7c407eee98b1f9cdcde0
subject: 'f94c9d4: chore: fake new commit message'
- active:
commitStatuses:
- key: argocd-health
phase: pending
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-24T14:13:27Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: f94c9d42993145d9f10bb2c404b74da14ee3f74e
subject: 'chore: fake new commit message'
hydrated:
author: GitOps Promoter
commitTime: '2025-09-24T14:14:40Z'
sha: 7bee0fcb985ac4ecccddf70a8b2d02cc0c41f231
subject: >-
This is a no-op commit merging from wave/5/west-next into
wave/5/west
branch: wave/5/west
history:
- active:
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-22T19:29:53Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: 7f91282cc8146644d0721efde1b9069ccbd475a1
subject: >-
chore: temporarily disable server-side diff on argo-argo
(ARGO-2528) (#2917)
hydrated:
author: argo-cd[bot]
body: "Fake commit message"
commitTime: '2025-09-22T21:00:59Z'
sha: a9d26ef79a1cc786a2a90446f7e8bbef2e5be653
subject: Promote 7f912 to `wave/5/west` (#2888)
proposed:
hydrated: {}
pullRequest: {}
proposed:
commitStatuses:
- key: promoter-previous-environment
phase: pending
dry:
author: Fake Person <fake@example.com>
commitTime: '2025-09-24T14:13:27Z'
repoURL: https://git.example.com/fake-org/fake-repo
sha: f94c9d42993145d9f10bb2c404b74da14ee3f74e
subject: 'chore: fake new commit message'
hydrated:
author: Argo CD
body: "Fake commit message"
commitTime: '2025-09-24T14:14:24Z'
sha: 20c41602695addbf7760236eff562f5692e585aa
subject: 'f94c9d4: chore: fake new commit message'

View File

@@ -1,38 +0,0 @@
apiVersion: promoter.argoproj.io/v1alpha1
kind: PromotionStrategy
metadata:
name: strategy
namespace: test
spec:
activeCommitStatuses:
- key: argocd-health
environments:
- autoMerge: true
branch: environments/qal-usw2
- autoMerge: true
branch: environments/e2e-usw2
gitRepositoryRef:
name: repo
status:
conditions:
- lastTransitionTime: '2025-10-15T16:31:47Z'
message: 'ChangeTransferPolicy "strategy-environments-qal-usw2-27894e05" is not Ready because "ReconciliationError": Reconciliation failed: failed to calculate ChangeTransferPolicy status: failed to get SHAs for proposed branch "environments/qal-usw2-next": exit status 128: fatal: ''origin/environments/qal-usw2-next'' is not a commit and a branch ''environments/qal-usw2-next'' cannot be created from it'
observedGeneration: 1
reason: ChangeTransferPolicyNotReady
status: 'False'
type: Ready
environments:
- active:
dry: {}
hydrated: {}
branch: environments/qal-usw2
proposed:
dry: {}
hydrated: {}
- active:
dry: {}
hydrated: {}
branch: environments/e2e-usw2
proposed:
dry: {}
hydrated: {}

View File

@@ -26,16 +26,9 @@ if obj.status.conditions then
hs.message = "Waiting for pull request spec update to be observed"
return hs
end
if condition.status == "False" then
if condition.status == "False" and (condition.reason == "ReconcileError" or condition.reason == "Failed") then
hs.status = "Degraded"
local msg = condition.message or "Unknown error"
local reason = condition.reason or "Unknown"
-- Don't include ReconciliationError in the message since it's redundant
if reason == "ReconciliationError" then
hs.message = "Pull request reconciliation failed: " .. msg
else
hs.message = "Pull request reconciliation failed (" .. reason .. "): " .. msg
end
hs.message = "Pull request reconciliation failed: " .. (condition.message or "Unknown error")
return hs
end
end

View File

@@ -14,11 +14,7 @@ tests:
- healthStatus:
status: Degraded
message: "Pull request reconciliation failed: Something went wrong"
inputPath: testdata/reconciliation-error.yaml
- healthStatus:
status: Degraded
message: "Pull request reconciliation failed: Failed to create pull request: authentication failed"
inputPath: testdata/non-reconciliation-error.yaml
inputPath: testdata/reconcile-error.yaml
- healthStatus:
status: Progressing
message: Pull request is not ready yet

View File

@@ -1,22 +0,0 @@
apiVersion: promoter.argoproj.io/v1alpha1
kind: PullRequest
metadata:
name: test-pr
namespace: test
generation: 1
spec:
sourceBranch: app-next
targetBranch: app
gitRepositoryRef:
name: repo
title: Test Pull Request
state: open
status:
conditions:
- lastTransitionTime: '2025-10-15T16:00:00Z'
message: 'Failed to create pull request: authentication failed'
observedGeneration: 1
reason: ReconciliationError
status: 'False'
type: Ready

View File

@@ -7,7 +7,7 @@ status:
conditions:
- type: Ready
status: "False"
reason: ReconciliationError
reason: ReconcileError
message: Something went wrong
observedGeneration: 2

View File

@@ -774,8 +774,9 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*v1a
return nil, err
}
s.inferResourcesStatusHealth(a)
if q.Refresh == nil {
s.inferResourcesStatusHealth(a)
return a, nil
}
@@ -854,9 +855,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*v1a
annotations = make(map[string]string)
}
if _, ok := annotations[v1alpha1.AnnotationKeyRefresh]; !ok {
refreshedApp := event.Application.DeepCopy()
s.inferResourcesStatusHealth(refreshedApp)
return refreshedApp, nil
return event.Application.DeepCopy(), nil
}
}
}

View File

@@ -2711,99 +2711,6 @@ func TestGetAppRefresh_HardRefresh(t *testing.T) {
}
}
func TestGetApp_HealthStatusPropagation(t *testing.T) {
newServerWithTree := func(t *testing.T) (*Server, *v1alpha1.Application) {
t.Helper()
cacheClient := cache.NewCache(cache.NewInMemoryCache(1 * time.Hour))
testApp := newTestApp()
testApp.Status.ResourceHealthSource = v1alpha1.ResourceHealthLocationAppTree
testApp.Status.Resources = []v1alpha1.ResourceStatus{
{
Group: "apps",
Kind: "Deployment",
Name: "guestbook",
Namespace: "default",
},
}
appServer := newTestAppServer(t, testApp)
appStateCache := appstate.NewCache(cacheClient, time.Minute)
appInstanceName := testApp.InstanceName(appServer.appNamespaceOrDefault(testApp.Namespace))
err := appStateCache.SetAppResourcesTree(appInstanceName, &v1alpha1.ApplicationTree{
Nodes: []v1alpha1.ResourceNode{{
ResourceRef: v1alpha1.ResourceRef{
Group: "apps",
Kind: "Deployment",
Name: "guestbook",
Namespace: "default",
},
Health: &v1alpha1.HealthStatus{Status: health.HealthStatusDegraded},
}},
})
require.NoError(t, err)
appServer.cache = servercache.NewCache(appStateCache, time.Minute, time.Minute)
return appServer, testApp
}
t.Run("propagated health status on get with no refresh", func(t *testing.T) {
appServer, testApp := newServerWithTree(t)
fetchedApp, err := appServer.Get(t.Context(), &application.ApplicationQuery{
Name: &testApp.Name,
})
require.NoError(t, err)
assert.Equal(t, health.HealthStatusDegraded, fetchedApp.Status.Resources[0].Health.Status)
})
t.Run("propagated health status on normal refresh", func(t *testing.T) {
appServer, testApp := newServerWithTree(t)
var patched int32
ch := make(chan string, 1)
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
go refreshAnnotationRemover(t, ctx, &patched, appServer, testApp.Name, ch)
fetchedApp, err := appServer.Get(t.Context(), &application.ApplicationQuery{
Name: &testApp.Name,
Refresh: ptr.To(string(v1alpha1.RefreshTypeNormal)),
})
require.NoError(t, err)
select {
case <-ch:
assert.Equal(t, int32(1), atomic.LoadInt32(&patched))
case <-time.After(10 * time.Second):
assert.Fail(t, "Out of time ( 10 seconds )")
}
assert.Equal(t, health.HealthStatusDegraded, fetchedApp.Status.Resources[0].Health.Status)
})
t.Run("propagated health status on hard refresh", func(t *testing.T) {
appServer, testApp := newServerWithTree(t)
var patched int32
ch := make(chan string, 1)
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
go refreshAnnotationRemover(t, ctx, &patched, appServer, testApp.Name, ch)
fetchedApp, err := appServer.Get(t.Context(), &application.ApplicationQuery{
Name: &testApp.Name,
Refresh: ptr.To(string(v1alpha1.RefreshTypeHard)),
})
require.NoError(t, err)
select {
case <-ch:
assert.Equal(t, int32(1), atomic.LoadInt32(&patched))
case <-time.After(10 * time.Second):
assert.Fail(t, "Out of time ( 10 seconds )")
}
assert.Equal(t, health.HealthStatusDegraded, fetchedApp.Status.Resources[0].Health.Status)
})
}
func TestInferResourcesStatusHealth(t *testing.T) {
cacheClient := cache.NewCache(cache.NewInMemoryCache(1 * time.Hour))

View File

@@ -1257,7 +1257,7 @@ func (server *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWeb
// Webhook handler for git events (Note: cache timeouts are hardcoded because API server does not write to cache and not really using them)
argoDB := db.NewDB(server.Namespace, server.settingsMgr, server.KubeClientset)
acdWebhookHandler := webhook.NewHandler(server.Namespace, server.ApplicationNamespaces, server.WebhookParallelism, server.AppClientset, server.appLister, server.settings, server.settingsMgr, server.RepoServerCache, server.Cache, argoDB, server.settingsMgr.GetMaxWebhookPayloadSize())
acdWebhookHandler := webhook.NewHandler(server.Namespace, server.ApplicationNamespaces, server.WebhookParallelism, server.AppClientset, server.settings, server.settingsMgr, server.RepoServerCache, server.Cache, argoDB, server.settingsMgr.GetMaxWebhookPayloadSize())
mux.HandleFunc("/api/webhook", acdWebhookHandler.Handler)

View File

@@ -1,4 +1,4 @@
FROM docker.io/library/node:23.0.0@sha256:e643c0b70dca9704dff42e12b17f5b719dbe4f95e6392fc2dfa0c5f02ea8044d as node
FROM docker.io/library/node:23.11.1@sha256:9a25b5a6f9a90218b73a62205f111e71de5e4289aee952b4dd7e86f7498f2544 as node
RUN apt-get update && apt-get install --no-install-recommends -y \
software-properties-common

View File

@@ -1 +1 @@
22.9.0
22.19.0

View File

@@ -60,7 +60,7 @@
"resolutions": {
"@types/react": "^16.9.3",
"@types/react-dom": "^16.8.2",
"normalize-url": "4.3.0",
"normalize-url": "4.5.1",
"rxjs": "6.6.7"
},
"devDependencies": {

View File

@@ -41,7 +41,6 @@
&__conditions {
display: flex;
max-width: 250px;
margin: auto;
flex-wrap: wrap;
justify-content: center;
line-height: 1.5em;
@@ -98,10 +97,6 @@
@include responsive-widths();
}
&:first-child {
margin-top: 7px;
}
&:not(:first-child) {
@include themify($themes) {
border-left: 1px solid themed('border');
@@ -139,7 +134,7 @@
&__item-value {
display: flex;
align-items: center;
align-items: baseline;
margin-bottom: 0.5em;
font-weight: 500;
padding: 2px 0px;

View File

@@ -39,15 +39,19 @@ interface SectionInfo {
}
const sectionLabel = (info: SectionInfo) => (
<label style={{fontSize: '12px', fontWeight: 600, color: ARGO_GRAY6_COLOR}}>
<label style={{display: 'flex', alignItems: 'flex-start', fontSize: '12px', fontWeight: 600, color: ARGO_GRAY6_COLOR, minHeight: '18px'}}>
{info.title}
{info.helpContent && <HelpIcon title={info.helpContent} />}
{info.helpContent && (
<span style={{marginLeft: '5px'}}>
<HelpIcon title={info.helpContent} />
</span>
)}
</label>
);
const sectionHeader = (info: SectionInfo, onClick?: () => any) => {
return (
<div style={{display: 'flex', alignItems: 'center', marginBottom: '0.5em'}}>
<div style={{display: 'flex', alignItems: 'center'}}>
{sectionLabel(info)}
{onClick && (
<button className='argo-button application-status-panel__more-button' onClick={onClick}>
@@ -58,12 +62,16 @@ const sectionHeader = (info: SectionInfo, onClick?: () => any) => {
);
};
const getApplicationSetOwnerRef = (application: models.Application) => {
return application.metadata.ownerReferences?.find(ref => ref.kind === 'ApplicationSet');
const hasRollingSyncEnabled = (application: models.Application): boolean => {
return application.metadata.ownerReferences?.some(ref => ref.kind === 'ApplicationSet') || false;
};
const ProgressiveSyncStatus = ({application}: {application: models.Application}) => {
const appSetRef = getApplicationSetOwnerRef(application);
if (!hasRollingSyncEnabled(application)) {
return null;
}
const appSetRef = application.metadata.ownerReferences.find(ref => ref.kind === 'ApplicationSet');
if (!appSetRef) {
return null;
}
@@ -71,39 +79,12 @@ const ProgressiveSyncStatus = ({application}: {application: models.Application})
return (
<DataLoader
input={application}
errorRenderer={() => {
// For any errors, show a minimal error state
return (
<div className='application-status-panel__item'>
{sectionHeader({
title: 'PROGRESSIVE SYNC',
helpContent: 'Shows the current status of progressive sync for applications managed by an ApplicationSet.'
})}
<div className='application-status-panel__item-value'>
<i className='fa fa-exclamation-triangle' style={{color: COLORS.sync.unknown}} /> Error
</div>
<div className='application-status-panel__item-name'>Unable to load Progressive Sync status</div>
</div>
);
}}
load={async () => {
// Find ApplicationSet by searching all namespaces dynamically
const appSetList = await services.applications.listApplicationSets();
const appSet = appSetList.items?.find(item => item.metadata.name === appSetRef.name);
return {appSet};
const appSet = await services.applications.getApplicationSet(appSetRef.name, application.metadata.namespace);
return appSet?.spec?.strategy?.type === 'RollingSync' ? appSet : null;
}}>
{({appSet}: {appSet: models.ApplicationSet}) => {
// Hide panel if: Progressive Sync disabled, no permission, or not RollingSync strategy
if (!appSet || !appSet.status?.applicationStatus || appSet?.spec?.strategy?.type !== 'RollingSync') {
return null;
}
// Get the current application's status from the ApplicationSet applicationStatus
const appResource = appSet.status?.applicationStatus?.find(status => status.application === application.metadata.name);
// If no application status is found, show a default status
if (!appResource) {
{(appSet: models.ApplicationSet) => {
if (!appSet) {
return (
<div className='application-status-panel__item'>
{sectionHeader({
@@ -111,15 +92,14 @@ const ProgressiveSyncStatus = ({application}: {application: models.Application})
helpContent: 'Shows the current status of progressive sync for applications managed by an ApplicationSet with RollingSync strategy.'
})}
<div className='application-status-panel__item-value'>
<i className='fa fa-clock' style={{color: COLORS.sync.out_of_sync}} /> Waiting
<i className='fa fa-question-circle' style={{color: COLORS.sync.unknown}} /> Unknown
</div>
<div className='application-status-panel__item-name'>Application status not yet available from ApplicationSet</div>
</div>
);
}
// Get last transition time from application status
const lastTransitionTime = appResource?.lastTransitionTime;
// Get the current application's status from the ApplicationSet resources
const appResource = appSet.status?.applicationStatus?.find(status => status.application === application.metadata.name);
return (
<div className='application-status-panel__item'>
@@ -130,14 +110,12 @@ const ProgressiveSyncStatus = ({application}: {application: models.Application})
<div className='application-status-panel__item-value' style={{color: getProgressiveSyncStatusColor(appResource.status)}}>
{getProgressiveSyncStatusIcon({status: appResource.status})}&nbsp;{appResource.status}
</div>
{appResource?.step && <div className='application-status-panel__item-value'>Wave: {appResource.step}</div>}
{lastTransitionTime && (
<div className='application-status-panel__item-name' style={{marginBottom: '0.5em'}}>
Last Transition: <br />
<Timestamp date={lastTransitionTime} />
</div>
)}
{appResource?.message && <div className='application-status-panel__item-name'>{appResource.message}</div>}
<div className='application-status-panel__item-value'>Wave: {appResource.step}</div>
<div className='application-status-panel__item-name' style={{marginBottom: '0.5em'}}>
Last Transition: <br />
<Timestamp date={appResource.lastTransitionTime} />
</div>
{appResource.message && <div className='application-status-panel__item-name'>{appResource.message}</div>}
</div>
);
}}
@@ -149,9 +127,7 @@ export const ApplicationStatusPanel = ({application, showDiff, showOperation, sh
const [showProgressiveSync, setShowProgressiveSync] = React.useState(false);
React.useEffect(() => {
// Only show Progressive Sync if the application has an ApplicationSet parent
// The actual strategy validation will be done inside ProgressiveSyncStatus component
setShowProgressiveSync(!!getApplicationSetOwnerRef(application));
setShowProgressiveSync(hasRollingSyncEnabled(application));
}, [application]);
const today = new Date();
@@ -184,7 +160,7 @@ export const ApplicationStatusPanel = ({application, showDiff, showOperation, sh
return (
<div className='application-status-panel row'>
<div className='application-status-panel__item'>
<div style={{lineHeight: '19.5px', marginBottom: '0.3em'}}>{sectionLabel({title: 'APP HEALTH', helpContent: 'The health status of your app'})}</div>
{sectionHeader({title: 'APP HEALTH', helpContent: 'The health status of your app'})}
<div className='application-status-panel__item-value'>
<HealthStatusIcon state={application.status.health} />
&nbsp;
@@ -310,7 +286,7 @@ export const ApplicationStatusPanel = ({application, showDiff, showOperation, sh
)}
{application.status.conditions && (
<div className={`application-status-panel__item`}>
{sectionLabel({title: 'APP CONDITIONS'})}
{sectionHeader({title: 'APP CONDITIONS'})}
<div className='application-status-panel__item-value application-status-panel__conditions' onClick={() => showConditions && showConditions()}>
{infos && (
<a className='info'>

View File

@@ -60,13 +60,3 @@ i.utils-health-status-icon {
transform: rotate(360deg);
}
}
.delete-dialog-icon {
&.warning {
color: #f4c030;
}
&.info {
color: #0DADEA;
}
}

View File

@@ -65,18 +65,8 @@ export const SpinningIcon = ({color, qeId}: {color: string; qeId: string}) => {
);
};
export async function deleteApplication(appName: string, appNamespace: string, apis: ContextApis, application?: appModels.Application): Promise<boolean> {
export async function deleteApplication(appName: string, appNamespace: string, apis: ContextApis): Promise<boolean> {
let confirmed = false;
// Use common child application detection logic if application object is provided
const isChildApp = application ? isChildApplication(application) : false;
const dialogTitle = isChildApp ? 'Delete child application' : 'Delete application';
const appType = isChildApp ? 'child Application' : 'Application';
const confirmLabel = isChildApp ? 'child application' : 'application';
// Check if this is being called from resource tree context
const isFromResourceTree = application !== undefined;
const propagationPolicies: {name: string; message: string}[] = [
{
name: 'Foreground',
@@ -92,27 +82,18 @@ export async function deleteApplication(appName: string, appNamespace: string, a
}
];
await apis.popup.prompt(
dialogTitle,
'Delete application',
api => (
<div>
<p>
Are you sure you want to delete the <strong>{appType}</strong> <kbd>{appName}</kbd>?
</p>
{isFromResourceTree && (
<p>
<strong>
<i className='fa fa-warning delete-dialog-icon warning' /> Note:
</strong>{' '}
You are about to delete an Application from the resource tree. This uses the same deletion behavior as the Applications list page.
</p>
)}
<p>
Are you sure you want to delete the <strong>Application</strong> <kbd>{appName}</kbd>?
<span style={{display: 'block', marginBottom: '10px'}} />
Deleting the application in <kbd>foreground</kbd> or <kbd>background</kbd> mode will delete all the application's managed resources, which can be{' '}
<strong>dangerous</strong>. Be sure you understand the effects of deleting this resource before continuing. Consider asking someone to review the change first.
</p>
<div className='argo-form-row'>
<FormField
label={`Please type '${appName}' to confirm the deletion of the ${confirmLabel}`}
label={`Please type '${appName}' to confirm the deletion of the resource`}
formApi={api}
field='applicationName'
qeId='name-field-delete-confirmation'
@@ -454,17 +435,6 @@ export const deleteSourceAction = (app: appModels.Application, source: appModels
);
};
// Detect if a resource is an Application
const isApplicationResource = (resource: ResourceTreeNode): boolean => {
return resource.kind === 'Application' && resource.group === 'argoproj.io';
};
// Detect if an application is a child application
const isChildApplication = (application: appModels.Application): boolean => {
const partOfLabel = application.metadata.labels?.['app.kubernetes.io/part-of'];
return partOfLabel && partOfLabel.trim() !== '';
};
export const deletePopup = async (
ctx: ContextApis,
resource: ResourceTreeNode,
@@ -473,17 +443,6 @@ export const deletePopup = async (
childResources: appModels.ResourceNode[],
appChanged?: BehaviorSubject<appModels.Application>
) => {
// Detect if this is an Application resource
const isApplication = isApplicationResource(resource);
// Check if we're in a parent-child context (used for both Application and non-Application resources)
const isInParentContext = isChildApplication(application);
// For Application resources, use the deleteApplication function with resource tree context
if (isApplication) {
return deleteApplication(resource.name, resource.namespace || '', ctx, application);
}
const deleteOptions = {
option: 'foreground'
};
@@ -495,31 +454,13 @@ export const deletePopup = async (
return deletePodAction(ctx, resource, application);
}
// Determine dialog title and add custom messaging
const dialogTitle = 'Delete resource';
let customMessage: React.ReactNode = null;
if (isInParentContext) {
customMessage = (
<div>
<p>
<strong>
<i className='fa fa-exclamation-triangle delete-dialog-icon info' /> Note:
</strong>{' '}
You are about to delete a resource from a parent application's resource tree.
</p>
</div>
);
}
return ctx.popup.prompt(
dialogTitle,
'Delete resource',
api => (
<div>
<p>
Are you sure you want to delete <strong>{resource.kind}</strong> <kbd>{resource.name}</kbd>?
</p>
{customMessage}
<p>
Deleting resources can be <strong>dangerous</strong>. Be sure you understand the effects of deleting this resource before continuing. Consider asking someone to
review the change first.
@@ -1775,10 +1716,6 @@ export const getProgressiveSyncStatusIcon = ({status, isButton}: {status: string
return {icon: 'fa-clock', color: COLORS.sync.out_of_sync};
case 'Error':
return {icon: 'fa-times-circle', color: COLORS.health.degraded};
case 'Synced':
return {icon: 'fa-check-circle', color: COLORS.sync.synced};
case 'OutOfSync':
return {icon: 'fa-exclamation-triangle', color: COLORS.sync.out_of_sync};
default:
return {icon: 'fa-question-circle', color: COLORS.sync.unknown};
}
@@ -1801,10 +1738,6 @@ export const getProgressiveSyncStatusColor = (status: string): string => {
return COLORS.health.healthy;
case 'Error':
return COLORS.health.degraded;
case 'Synced':
return COLORS.sync.synced;
case 'OutOfSync':
return COLORS.sync.out_of_sync;
default:
return COLORS.sync.unknown;
}

View File

@@ -1134,8 +1134,3 @@ export interface ApplicationSet {
resources?: ApplicationSetResource[];
};
}
export interface ApplicationSetList {
metadata: models.ListMeta;
items: ApplicationSet[];
}

View File

@@ -550,8 +550,4 @@ export class ApplicationsService {
.query({appsetNamespace: namespace})
.then(res => res.body as models.ApplicationSet);
}
public async listApplicationSets(): Promise<models.ApplicationSetList> {
return requests.get(`/applicationsets`).then(res => res.body as models.ApplicationSetList);
}
}

View File

@@ -6839,10 +6839,10 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
normalize-url@4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.3.0.tgz#9c49e10fc1876aeb76dba88bf1b2b5d9fa57b2ee"
integrity sha512-0NLtR71o4k6GLP+mr6Ty34c5GA6CMoEsncKJxvQd8NzPxaHRJNnb5gZE8R1XF4CPIS7QPHLJ74IFszwtNVAHVQ==
normalize-url@4.5.1:
version "4.5.1"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a"
integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==
npm-run-path@^4.0.1:
version "4.0.1"

View File

@@ -34,9 +34,9 @@ func (s *secretsRepositoryBackend) CreateRepository(ctx context.Context, reposit
},
}
updatedSecret := s.repositoryToSecret(repository, repositorySecret)
s.repositoryToSecret(repository, repositorySecret)
_, err := s.db.createSecret(ctx, updatedSecret)
_, err := s.db.createSecret(ctx, repositorySecret)
if err != nil {
if apierrors.IsAlreadyExists(err) {
hasLabel, err := s.hasRepoTypeLabel(secName)
@@ -142,9 +142,9 @@ func (s *secretsRepositoryBackend) UpdateRepository(ctx context.Context, reposit
return nil, err
}
updatedSecret := s.repositoryToSecret(repository, repositorySecret)
s.repositoryToSecret(repository, repositorySecret)
_, err = s.db.kubeclientset.CoreV1().Secrets(s.db.ns).Update(ctx, updatedSecret, metav1.UpdateOptions{})
_, err = s.db.kubeclientset.CoreV1().Secrets(s.db.ns).Update(ctx, repositorySecret, metav1.UpdateOptions{})
if err != nil {
return nil, err
}
@@ -187,9 +187,9 @@ func (s *secretsRepositoryBackend) CreateRepoCreds(ctx context.Context, repoCred
},
}
updatedSecret := s.repoCredsToSecret(repoCreds, repoCredsSecret)
s.repoCredsToSecret(repoCreds, repoCredsSecret)
_, err := s.db.createSecret(ctx, updatedSecret)
_, err := s.db.createSecret(ctx, repoCredsSecret)
if err != nil {
if apierrors.IsAlreadyExists(err) {
return nil, status.Errorf(codes.AlreadyExists, "repository credentials %q already exists", repoCreds.URL)
@@ -237,9 +237,9 @@ func (s *secretsRepositoryBackend) UpdateRepoCreds(ctx context.Context, repoCred
return nil, err
}
updatedSecret := s.repoCredsToSecret(repoCreds, repoCredsSecret)
s.repoCredsToSecret(repoCreds, repoCredsSecret)
repoCredsSecret, err = s.db.kubeclientset.CoreV1().Secrets(s.db.ns).Update(ctx, updatedSecret, metav1.UpdateOptions{})
repoCredsSecret, err = s.db.kubeclientset.CoreV1().Secrets(s.db.ns).Update(ctx, repoCredsSecret, metav1.UpdateOptions{})
if err != nil {
return nil, err
}
@@ -323,75 +323,73 @@ func (s *secretsRepositoryBackend) GetAllOCIRepoCreds(_ context.Context) ([]*app
}
func secretToRepository(secret *corev1.Secret) (*appsv1.Repository, error) {
secretCopy := secret.DeepCopy()
repository := &appsv1.Repository{
Name: string(secretCopy.Data["name"]),
Repo: string(secretCopy.Data["url"]),
Username: string(secretCopy.Data["username"]),
Password: string(secretCopy.Data["password"]),
BearerToken: string(secretCopy.Data["bearerToken"]),
SSHPrivateKey: string(secretCopy.Data["sshPrivateKey"]),
TLSClientCertData: string(secretCopy.Data["tlsClientCertData"]),
TLSClientCertKey: string(secretCopy.Data["tlsClientCertKey"]),
Type: string(secretCopy.Data["type"]),
GithubAppPrivateKey: string(secretCopy.Data["githubAppPrivateKey"]),
GitHubAppEnterpriseBaseURL: string(secretCopy.Data["githubAppEnterpriseBaseUrl"]),
Proxy: string(secretCopy.Data["proxy"]),
NoProxy: string(secretCopy.Data["noProxy"]),
Project: string(secretCopy.Data["project"]),
GCPServiceAccountKey: string(secretCopy.Data["gcpServiceAccountKey"]),
Name: string(secret.Data["name"]),
Repo: string(secret.Data["url"]),
Username: string(secret.Data["username"]),
Password: string(secret.Data["password"]),
BearerToken: string(secret.Data["bearerToken"]),
SSHPrivateKey: string(secret.Data["sshPrivateKey"]),
TLSClientCertData: string(secret.Data["tlsClientCertData"]),
TLSClientCertKey: string(secret.Data["tlsClientCertKey"]),
Type: string(secret.Data["type"]),
GithubAppPrivateKey: string(secret.Data["githubAppPrivateKey"]),
GitHubAppEnterpriseBaseURL: string(secret.Data["githubAppEnterpriseBaseUrl"]),
Proxy: string(secret.Data["proxy"]),
NoProxy: string(secret.Data["noProxy"]),
Project: string(secret.Data["project"]),
GCPServiceAccountKey: string(secret.Data["gcpServiceAccountKey"]),
}
insecureIgnoreHostKey, err := boolOrFalse(secretCopy, "insecureIgnoreHostKey")
insecureIgnoreHostKey, err := boolOrFalse(secret, "insecureIgnoreHostKey")
if err != nil {
return repository, err
}
repository.InsecureIgnoreHostKey = insecureIgnoreHostKey
insecure, err := boolOrFalse(secretCopy, "insecure")
insecure, err := boolOrFalse(secret, "insecure")
if err != nil {
return repository, err
}
repository.Insecure = insecure
enableLfs, err := boolOrFalse(secretCopy, "enableLfs")
enableLfs, err := boolOrFalse(secret, "enableLfs")
if err != nil {
return repository, err
}
repository.EnableLFS = enableLfs
enableOCI, err := boolOrFalse(secretCopy, "enableOCI")
enableOCI, err := boolOrFalse(secret, "enableOCI")
if err != nil {
return repository, err
}
repository.EnableOCI = enableOCI
insecureOCIForceHTTP, err := boolOrFalse(secretCopy, "insecureOCIForceHttp")
insecureOCIForceHTTP, err := boolOrFalse(secret, "insecureOCIForceHttp")
if err != nil {
return repository, err
}
repository.InsecureOCIForceHttp = insecureOCIForceHTTP
githubAppID, err := intOrZero(secretCopy, "githubAppID")
githubAppID, err := intOrZero(secret, "githubAppID")
if err != nil {
return repository, err
}
repository.GithubAppId = githubAppID
githubAppInstallationID, err := intOrZero(secretCopy, "githubAppInstallationID")
githubAppInstallationID, err := intOrZero(secret, "githubAppInstallationID")
if err != nil {
return repository, err
}
repository.GithubAppInstallationId = githubAppInstallationID
forceBasicAuth, err := boolOrFalse(secretCopy, "forceHttpBasicAuth")
forceBasicAuth, err := boolOrFalse(secret, "forceHttpBasicAuth")
if err != nil {
return repository, err
}
repository.ForceHttpBasicAuth = forceBasicAuth
useAzureWorkloadIdentity, err := boolOrFalse(secretCopy, "useAzureWorkloadIdentity")
useAzureWorkloadIdentity, err := boolOrFalse(secret, "useAzureWorkloadIdentity")
if err != nil {
return repository, err
}
@@ -400,92 +398,86 @@ func secretToRepository(secret *corev1.Secret) (*appsv1.Repository, error) {
return repository, nil
}
func (s *secretsRepositoryBackend) repositoryToSecret(repository *appsv1.Repository, secret *corev1.Secret) *corev1.Secret {
secretCopy := secret.DeepCopy()
if secretCopy.Data == nil {
secretCopy.Data = make(map[string][]byte)
func (s *secretsRepositoryBackend) repositoryToSecret(repository *appsv1.Repository, secret *corev1.Secret) {
if secret.Data == nil {
secret.Data = make(map[string][]byte)
}
updateSecretString(secretCopy, "name", repository.Name)
updateSecretString(secretCopy, "project", repository.Project)
updateSecretString(secretCopy, "url", repository.Repo)
updateSecretString(secretCopy, "username", repository.Username)
updateSecretString(secretCopy, "password", repository.Password)
updateSecretString(secretCopy, "bearerToken", repository.BearerToken)
updateSecretString(secretCopy, "sshPrivateKey", repository.SSHPrivateKey)
updateSecretBool(secretCopy, "enableOCI", repository.EnableOCI)
updateSecretBool(secretCopy, "insecureOCIForceHttp", repository.InsecureOCIForceHttp)
updateSecretString(secretCopy, "tlsClientCertData", repository.TLSClientCertData)
updateSecretString(secretCopy, "tlsClientCertKey", repository.TLSClientCertKey)
updateSecretString(secretCopy, "type", repository.Type)
updateSecretString(secretCopy, "githubAppPrivateKey", repository.GithubAppPrivateKey)
updateSecretInt(secretCopy, "githubAppID", repository.GithubAppId)
updateSecretInt(secretCopy, "githubAppInstallationID", repository.GithubAppInstallationId)
updateSecretString(secretCopy, "githubAppEnterpriseBaseUrl", repository.GitHubAppEnterpriseBaseURL)
updateSecretBool(secretCopy, "insecureIgnoreHostKey", repository.InsecureIgnoreHostKey)
updateSecretBool(secretCopy, "insecure", repository.Insecure)
updateSecretBool(secretCopy, "enableLfs", repository.EnableLFS)
updateSecretString(secretCopy, "proxy", repository.Proxy)
updateSecretString(secretCopy, "noProxy", repository.NoProxy)
updateSecretString(secretCopy, "gcpServiceAccountKey", repository.GCPServiceAccountKey)
updateSecretBool(secretCopy, "forceHttpBasicAuth", repository.ForceHttpBasicAuth)
updateSecretBool(secretCopy, "useAzureWorkloadIdentity", repository.UseAzureWorkloadIdentity)
addSecretMetadata(secretCopy, s.getSecretType())
return secretCopy
updateSecretString(secret, "name", repository.Name)
updateSecretString(secret, "project", repository.Project)
updateSecretString(secret, "url", repository.Repo)
updateSecretString(secret, "username", repository.Username)
updateSecretString(secret, "password", repository.Password)
updateSecretString(secret, "bearerToken", repository.BearerToken)
updateSecretString(secret, "sshPrivateKey", repository.SSHPrivateKey)
updateSecretBool(secret, "enableOCI", repository.EnableOCI)
updateSecretBool(secret, "insecureOCIForceHttp", repository.InsecureOCIForceHttp)
updateSecretString(secret, "tlsClientCertData", repository.TLSClientCertData)
updateSecretString(secret, "tlsClientCertKey", repository.TLSClientCertKey)
updateSecretString(secret, "type", repository.Type)
updateSecretString(secret, "githubAppPrivateKey", repository.GithubAppPrivateKey)
updateSecretInt(secret, "githubAppID", repository.GithubAppId)
updateSecretInt(secret, "githubAppInstallationID", repository.GithubAppInstallationId)
updateSecretString(secret, "githubAppEnterpriseBaseUrl", repository.GitHubAppEnterpriseBaseURL)
updateSecretBool(secret, "insecureIgnoreHostKey", repository.InsecureIgnoreHostKey)
updateSecretBool(secret, "insecure", repository.Insecure)
updateSecretBool(secret, "enableLfs", repository.EnableLFS)
updateSecretString(secret, "proxy", repository.Proxy)
updateSecretString(secret, "noProxy", repository.NoProxy)
updateSecretString(secret, "gcpServiceAccountKey", repository.GCPServiceAccountKey)
updateSecretBool(secret, "forceHttpBasicAuth", repository.ForceHttpBasicAuth)
updateSecretBool(secret, "useAzureWorkloadIdentity", repository.UseAzureWorkloadIdentity)
addSecretMetadata(secret, s.getSecretType())
}
func (s *secretsRepositoryBackend) secretToRepoCred(secret *corev1.Secret) (*appsv1.RepoCreds, error) {
secretCopy := secret.DeepCopy()
repository := &appsv1.RepoCreds{
URL: string(secretCopy.Data["url"]),
Username: string(secretCopy.Data["username"]),
Password: string(secretCopy.Data["password"]),
BearerToken: string(secretCopy.Data["bearerToken"]),
SSHPrivateKey: string(secretCopy.Data["sshPrivateKey"]),
TLSClientCertData: string(secretCopy.Data["tlsClientCertData"]),
TLSClientCertKey: string(secretCopy.Data["tlsClientCertKey"]),
Type: string(secretCopy.Data["type"]),
GithubAppPrivateKey: string(secretCopy.Data["githubAppPrivateKey"]),
GitHubAppEnterpriseBaseURL: string(secretCopy.Data["githubAppEnterpriseBaseUrl"]),
GCPServiceAccountKey: string(secretCopy.Data["gcpServiceAccountKey"]),
Proxy: string(secretCopy.Data["proxy"]),
NoProxy: string(secretCopy.Data["noProxy"]),
URL: string(secret.Data["url"]),
Username: string(secret.Data["username"]),
Password: string(secret.Data["password"]),
BearerToken: string(secret.Data["bearerToken"]),
SSHPrivateKey: string(secret.Data["sshPrivateKey"]),
TLSClientCertData: string(secret.Data["tlsClientCertData"]),
TLSClientCertKey: string(secret.Data["tlsClientCertKey"]),
Type: string(secret.Data["type"]),
GithubAppPrivateKey: string(secret.Data["githubAppPrivateKey"]),
GitHubAppEnterpriseBaseURL: string(secret.Data["githubAppEnterpriseBaseUrl"]),
GCPServiceAccountKey: string(secret.Data["gcpServiceAccountKey"]),
Proxy: string(secret.Data["proxy"]),
NoProxy: string(secret.Data["noProxy"]),
}
enableOCI, err := boolOrFalse(secretCopy, "enableOCI")
enableOCI, err := boolOrFalse(secret, "enableOCI")
if err != nil {
return repository, err
}
repository.EnableOCI = enableOCI
insecureOCIForceHTTP, err := boolOrFalse(secretCopy, "insecureOCIForceHttp")
insecureOCIForceHTTP, err := boolOrFalse(secret, "insecureOCIForceHttp")
if err != nil {
return repository, err
}
repository.InsecureOCIForceHttp = insecureOCIForceHTTP
githubAppID, err := intOrZero(secretCopy, "githubAppID")
githubAppID, err := intOrZero(secret, "githubAppID")
if err != nil {
return repository, err
}
repository.GithubAppId = githubAppID
githubAppInstallationID, err := intOrZero(secretCopy, "githubAppInstallationID")
githubAppInstallationID, err := intOrZero(secret, "githubAppInstallationID")
if err != nil {
return repository, err
}
repository.GithubAppInstallationId = githubAppInstallationID
forceBasicAuth, err := boolOrFalse(secretCopy, "forceHttpBasicAuth")
forceBasicAuth, err := boolOrFalse(secret, "forceHttpBasicAuth")
if err != nil {
return repository, err
}
repository.ForceHttpBasicAuth = forceBasicAuth
useAzureWorkloadIdentity, err := boolOrFalse(secretCopy, "useAzureWorkloadIdentity")
useAzureWorkloadIdentity, err := boolOrFalse(secret, "useAzureWorkloadIdentity")
if err != nil {
return repository, err
}
@@ -494,35 +486,31 @@ func (s *secretsRepositoryBackend) secretToRepoCred(secret *corev1.Secret) (*app
return repository, nil
}
func (s *secretsRepositoryBackend) repoCredsToSecret(repoCreds *appsv1.RepoCreds, secret *corev1.Secret) *corev1.Secret {
secretCopy := secret.DeepCopy()
if secretCopy.Data == nil {
secretCopy.Data = make(map[string][]byte)
func (s *secretsRepositoryBackend) repoCredsToSecret(repoCreds *appsv1.RepoCreds, secret *corev1.Secret) {
if secret.Data == nil {
secret.Data = make(map[string][]byte)
}
updateSecretString(secretCopy, "url", repoCreds.URL)
updateSecretString(secretCopy, "username", repoCreds.Username)
updateSecretString(secretCopy, "password", repoCreds.Password)
updateSecretString(secretCopy, "bearerToken", repoCreds.BearerToken)
updateSecretString(secretCopy, "sshPrivateKey", repoCreds.SSHPrivateKey)
updateSecretBool(secretCopy, "enableOCI", repoCreds.EnableOCI)
updateSecretBool(secretCopy, "insecureOCIForceHttp", repoCreds.InsecureOCIForceHttp)
updateSecretString(secretCopy, "tlsClientCertData", repoCreds.TLSClientCertData)
updateSecretString(secretCopy, "tlsClientCertKey", repoCreds.TLSClientCertKey)
updateSecretString(secretCopy, "type", repoCreds.Type)
updateSecretString(secretCopy, "githubAppPrivateKey", repoCreds.GithubAppPrivateKey)
updateSecretInt(secretCopy, "githubAppID", repoCreds.GithubAppId)
updateSecretInt(secretCopy, "githubAppInstallationID", repoCreds.GithubAppInstallationId)
updateSecretString(secretCopy, "githubAppEnterpriseBaseUrl", repoCreds.GitHubAppEnterpriseBaseURL)
updateSecretString(secretCopy, "gcpServiceAccountKey", repoCreds.GCPServiceAccountKey)
updateSecretString(secretCopy, "proxy", repoCreds.Proxy)
updateSecretString(secretCopy, "noProxy", repoCreds.NoProxy)
updateSecretBool(secretCopy, "forceHttpBasicAuth", repoCreds.ForceHttpBasicAuth)
updateSecretBool(secretCopy, "useAzureWorkloadIdentity", repoCreds.UseAzureWorkloadIdentity)
addSecretMetadata(secretCopy, s.getRepoCredSecretType())
return secretCopy
updateSecretString(secret, "url", repoCreds.URL)
updateSecretString(secret, "username", repoCreds.Username)
updateSecretString(secret, "password", repoCreds.Password)
updateSecretString(secret, "bearerToken", repoCreds.BearerToken)
updateSecretString(secret, "sshPrivateKey", repoCreds.SSHPrivateKey)
updateSecretBool(secret, "enableOCI", repoCreds.EnableOCI)
updateSecretBool(secret, "insecureOCIForceHttp", repoCreds.InsecureOCIForceHttp)
updateSecretString(secret, "tlsClientCertData", repoCreds.TLSClientCertData)
updateSecretString(secret, "tlsClientCertKey", repoCreds.TLSClientCertKey)
updateSecretString(secret, "type", repoCreds.Type)
updateSecretString(secret, "githubAppPrivateKey", repoCreds.GithubAppPrivateKey)
updateSecretInt(secret, "githubAppID", repoCreds.GithubAppId)
updateSecretInt(secret, "githubAppInstallationID", repoCreds.GithubAppInstallationId)
updateSecretString(secret, "githubAppEnterpriseBaseUrl", repoCreds.GitHubAppEnterpriseBaseURL)
updateSecretString(secret, "gcpServiceAccountKey", repoCreds.GCPServiceAccountKey)
updateSecretString(secret, "proxy", repoCreds.Proxy)
updateSecretString(secret, "noProxy", repoCreds.NoProxy)
updateSecretBool(secret, "forceHttpBasicAuth", repoCreds.ForceHttpBasicAuth)
updateSecretBool(secret, "useAzureWorkloadIdentity", repoCreds.UseAzureWorkloadIdentity)
addSecretMetadata(secret, s.getRepoCredSecretType())
}
func (s *secretsRepositoryBackend) getRepositorySecret(repoURL, project string, allowFallback bool) (*corev1.Secret, error) {

View File

@@ -1,9 +1,7 @@
package db
import (
"fmt"
"strconv"
"sync"
"testing"
"github.com/stretchr/testify/assert"
@@ -87,9 +85,9 @@ func TestSecretsRepositoryBackend_CreateRepository(t *testing.T) {
t.Parallel()
secret := &corev1.Secret{}
s := secretsRepositoryBackend{}
updatedSecret := s.repositoryToSecret(repo, secret)
delete(updatedSecret.Labels, common.LabelKeySecretType)
f := setupWithK8sObjects(updatedSecret)
s.repositoryToSecret(repo, secret)
delete(secret.Labels, common.LabelKeySecretType)
f := setupWithK8sObjects(secret)
f.clientSet.ReactionChain = nil
f.clientSet.AddReactor("create", "secrets", func(_ k8stesting.Action) (handled bool, ret runtime.Object, err error) {
gr := schema.GroupResource{
@@ -124,8 +122,8 @@ func TestSecretsRepositoryBackend_CreateRepository(t *testing.T) {
},
}
s := secretsRepositoryBackend{}
updatedSecret := s.repositoryToSecret(repo, secret)
f := setupWithK8sObjects(updatedSecret)
s.repositoryToSecret(repo, secret)
f := setupWithK8sObjects(secret)
f.clientSet.ReactionChain = nil
f.clientSet.WatchReactionChain = nil
f.clientSet.AddReactor("create", "secrets", func(_ k8stesting.Action) (handled bool, ret runtime.Object, err error) {
@@ -136,7 +134,7 @@ func TestSecretsRepositoryBackend_CreateRepository(t *testing.T) {
return true, nil, apierrors.NewAlreadyExists(gr, "already exists")
})
watcher := watch.NewFakeWithChanSize(1, true)
watcher.Add(updatedSecret)
watcher.Add(secret)
f.clientSet.AddWatchReactor("secrets", func(_ k8stesting.Action) (handled bool, ret watch.Interface, err error) {
return true, watcher, nil
})
@@ -954,9 +952,7 @@ func TestRepoCredsToSecret(t *testing.T) {
GithubAppInstallationId: 456,
GitHubAppEnterpriseBaseURL: "GitHubAppEnterpriseBaseURL",
}
s = testee.repoCredsToSecret(creds, s)
testee.repoCredsToSecret(creds, s)
assert.Equal(t, []byte(creds.URL), s.Data["url"])
assert.Equal(t, []byte(creds.Username), s.Data["username"])
assert.Equal(t, []byte(creds.Password), s.Data["password"])
@@ -998,7 +994,7 @@ func TestRepoWriteCredsToSecret(t *testing.T) {
GithubAppInstallationId: 456,
GitHubAppEnterpriseBaseURL: "GitHubAppEnterpriseBaseURL",
}
s = testee.repoCredsToSecret(creds, s)
testee.repoCredsToSecret(creds, s)
assert.Equal(t, []byte(creds.URL), s.Data["url"])
assert.Equal(t, []byte(creds.Username), s.Data["username"])
assert.Equal(t, []byte(creds.Password), s.Data["password"])
@@ -1014,169 +1010,3 @@ func TestRepoWriteCredsToSecret(t *testing.T) {
assert.Equal(t, map[string]string{common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD}, s.Annotations)
assert.Equal(t, map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepoCredsWrite}, s.Labels)
}
func TestRaceConditionInRepoCredsOperations(t *testing.T) {
// Create a single shared secret that will be accessed concurrently
sharedSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git", ""),
Namespace: testNamespace,
Labels: map[string]string{
common.LabelKeySecretType: common.LabelValueSecretTypeRepoCreds,
},
},
Data: map[string][]byte{
"url": []byte("git@github.com:argoproj/argo-cd.git"),
"username": []byte("test-user"),
"password": []byte("test-pass"),
},
}
// Create test credentials that we'll use for conversion
repoCreds := &appsv1.RepoCreds{
URL: "git@github.com:argoproj/argo-cd.git",
Username: "test-user",
Password: "test-pass",
}
backend := &secretsRepositoryBackend{}
var wg sync.WaitGroup
concurrentOps := 50
errChan := make(chan error, concurrentOps*2) // Channel to collect errors
// Launch goroutines that perform concurrent operations
for i := 0; i < concurrentOps; i++ {
wg.Add(2)
// One goroutine converts from RepoCreds to Secret
go func() {
defer wg.Done()
defer func() {
if r := recover(); r != nil {
errChan <- fmt.Errorf("panic in repoCredsToSecret: %v", r)
}
}()
_ = backend.repoCredsToSecret(repoCreds, sharedSecret)
}()
// Another goroutine converts from Secret to RepoCreds
go func() {
defer wg.Done()
defer func() {
if r := recover(); r != nil {
errChan <- fmt.Errorf("panic in secretToRepoCred: %v", r)
}
}()
creds, err := backend.secretToRepoCred(sharedSecret)
if err != nil {
errChan <- fmt.Errorf("error in secretToRepoCred: %w", err)
return
}
// Verify data integrity
if creds.URL != repoCreds.URL || creds.Username != repoCreds.Username || creds.Password != repoCreds.Password {
errChan <- fmt.Errorf("data mismatch in conversion: expected %v, got %v", repoCreds, creds)
}
}()
}
wg.Wait()
close(errChan)
// Check for any errors that occurred during concurrent operations
for err := range errChan {
t.Errorf("concurrent operation error: %v", err)
}
// Verify final state
finalCreds, err := backend.secretToRepoCred(sharedSecret)
require.NoError(t, err)
assert.Equal(t, repoCreds.URL, finalCreds.URL)
assert.Equal(t, repoCreds.Username, finalCreds.Username)
assert.Equal(t, repoCreds.Password, finalCreds.Password)
}
func TestRaceConditionInRepositoryOperations(t *testing.T) {
// Create a single shared secret that will be accessed concurrently
sharedSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: RepoURLToSecretName(repoSecretPrefix, "git@github.com:argoproj/argo-cd.git", ""),
Namespace: testNamespace,
Labels: map[string]string{
common.LabelKeySecretType: common.LabelValueSecretTypeRepository,
},
},
Data: map[string][]byte{
"url": []byte("git@github.com:argoproj/argo-cd.git"),
"name": []byte("test-repo"),
"username": []byte("test-user"),
"password": []byte("test-pass"),
},
}
// Create test repository that we'll use for conversion
repo := &appsv1.Repository{
Name: "test-repo",
Repo: "git@github.com:argoproj/argo-cd.git",
Username: "test-user",
Password: "test-pass",
}
backend := &secretsRepositoryBackend{}
var wg sync.WaitGroup
concurrentOps := 50
errChan := make(chan error, concurrentOps*2) // Channel to collect errors
// Launch goroutines that perform concurrent operations
for i := 0; i < concurrentOps; i++ {
wg.Add(2)
// One goroutine converts from Repository to Secret
go func() {
defer wg.Done()
defer func() {
if r := recover(); r != nil {
errChan <- fmt.Errorf("panic in repositoryToSecret: %v", r)
}
}()
_ = backend.repositoryToSecret(repo, sharedSecret)
}()
// Another goroutine converts from Secret to Repository
go func() {
defer wg.Done()
defer func() {
if r := recover(); r != nil {
errChan <- fmt.Errorf("panic in secretToRepository: %v", r)
}
}()
repository, err := secretToRepository(sharedSecret)
if err != nil {
errChan <- fmt.Errorf("error in secretToRepository: %w", err)
return
}
// Verify data integrity
if repository.Name != repo.Name || repository.Repo != repo.Repo ||
repository.Username != repo.Username || repository.Password != repo.Password {
errChan <- fmt.Errorf("data mismatch in conversion: expected %v, got %v", repo, repository)
}
}()
}
wg.Wait()
close(errChan)
// Check for any errors that occurred during concurrent operations
for err := range errChan {
t.Errorf("concurrent operation error: %v", err)
}
// Verify final state
finalRepo, err := secretToRepository(sharedSecret)
require.NoError(t, err)
assert.Equal(t, repo.Name, finalRepo.Name)
assert.Equal(t, repo.Repo, finalRepo.Repo)
assert.Equal(t, repo.Username, finalRepo.Username)
assert.Equal(t, repo.Password, finalRepo.Password)
}

View File

@@ -62,7 +62,7 @@ func RunWithRedactor(cmd *exec.Cmd, redactor func(text string) string) (string,
}
func RunWithExecRunOpts(cmd *exec.Cmd, opts ExecRunOpts) (string, error) {
cmdOpts := CmdOpts{Timeout: timeout, FatalTimeout: fatalTimeout, Redactor: opts.Redactor, TimeoutBehavior: opts.TimeoutBehavior, SkipErrorLogging: opts.SkipErrorLogging, CaptureStderr: opts.CaptureStderr}
cmdOpts := CmdOpts{Timeout: timeout, FatalTimeout: fatalTimeout, Redactor: opts.Redactor, TimeoutBehavior: opts.TimeoutBehavior, SkipErrorLogging: opts.SkipErrorLogging}
span := tracing.NewLoggingTracer(log.NewLogrusLogger(log.NewWithCurrentConfig())).StartSpan(fmt.Sprintf("exec %v", cmd.Args[0]))
span.SetBaggageItem("dir", cmd.Dir)
if cmdOpts.Redactor != nil {

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