Compare commits

..

2 Commits

Author SHA1 Message Date
Michael Crenshaw
1e71863944 feat(hydrator): add commit-server component
Co-authored-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
Co-authored-by: Omer Azmon <omer_azmon@intuit.com>
Co-authored-by: daengdaengLee <gunho1020@gmail.com>
Co-authored-by: Juwon Hwang (Kevin) <juwon8891@gmail.com>
Co-authored-by: thisishwan2 <feel000617@gmail.com>
Co-authored-by: mirageoasis <kimhw0820@naver.com>
Co-authored-by: Robin Lieb <robin.j.lieb@gmail.com>
Co-authored-by: miiiinju1 <gms07073@ynu.ac.kr>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

go mod tidy

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

one test file for both implementations

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

simplify

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

fix test for linux

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

fix git client mock

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

fix git client mock

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

address comments

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

unit tests

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

lint

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

fix image, fix health checks, fix merge issue

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

fix lint issues

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

remove code that doesn't work for GHE

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

changes from comments

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-12-14 12:47:26 -05:00
Michael Crenshaw
cc1f9f53f1 feat(hydrator): add sourceHydrator types
Co-authored-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
Co-authored-by: Omer Azmon <omer_azmon@intuit.com>
Co-authored-by: daengdaengLee <gunho1020@gmail.com>
Co-authored-by: Juwon Hwang (Kevin) <juwon8891@gmail.com>
Co-authored-by: thisishwan2 <feel000617@gmail.com>
Co-authored-by: mirageoasis <kimhw0820@naver.com>
Co-authored-by: Robin Lieb <robin.j.lieb@gmail.com>
Co-authored-by: miiiinju1 <gms07073@ynu.ac.kr>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

fix(codegen): use kube_codegen.sh deepcopy and client gen correctly

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

deepcopy gen

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-12-14 12:46:40 -05:00
1652 changed files with 82572 additions and 200358 deletions

5
.gitattributes vendored
View File

@@ -5,14 +5,9 @@ docs/operator-manual/resource_actions_builtin.md linguist-generated=true
docs/operator-manual/server-commands/argocd-*.md linguist-generated=true docs/operator-manual/server-commands/argocd-*.md linguist-generated=true
docs/user-guide/commands/argocd_*.md linguist-generated=true docs/user-guide/commands/argocd_*.md linguist-generated=true
manifests/core-install.yaml linguist-generated=true manifests/core-install.yaml linguist-generated=true
manifests/core-install-with-hydrator.yaml linguist-generated=true
manifests/crds/*-crd.yaml linguist-generated=true manifests/crds/*-crd.yaml linguist-generated=true
manifests/ha/install.yaml linguist-generated=true manifests/ha/install.yaml linguist-generated=true
manifests/ha/install-with-hydrator.yaml linguist-generated=true
manifests/ha/namespace-install.yaml linguist-generated=true manifests/ha/namespace-install.yaml linguist-generated=true
manifests/ha/namespace-install-with-hydrator.yaml linguist-generated=true
manifests/install.yaml linguist-generated=true manifests/install.yaml linguist-generated=true
manifests/install-with-hydrator.yaml linguist-generated=true
manifests/namespace-install.yaml linguist-generated=true manifests/namespace-install.yaml linguist-generated=true
manifests/namespace-install-with-hydrator.yaml linguist-generated=true
pkg/apis/api-rules/violation_exceptions.list linguist-generated=true pkg/apis/api-rules/violation_exceptions.list linguist-generated=true

View File

@@ -10,7 +10,7 @@ updates:
groups: groups:
otel: otel:
patterns: patterns:
- "go.opentelemetry.io/*" - "^go.opentelemetry.io/.*"
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
directory: "/" directory: "/"
@@ -22,11 +22,10 @@ updates:
schedule: schedule:
interval: "daily" interval: "daily"
# Disabled since this code is rarely used. - package-ecosystem: "npm"
# - package-ecosystem: "npm" directory: "/ui-test/"
# directory: "/ui-test/" schedule:
# schedule: interval: "daily"
# interval: "daily"
- package-ecosystem: "docker" - package-ecosystem: "docker"
directory: "/" directory: "/"
@@ -53,8 +52,7 @@ updates:
schedule: schedule:
interval: "daily" interval: "daily"
# Disabled since this code is rarely used. - package-ecosystem: "docker"
# - package-ecosystem: "docker" directory: "/ui-test/"
# directory: "/ui-test/" schedule:
# schedule: interval: "daily"
# interval: "daily"

View File

@@ -16,7 +16,7 @@
## image-reuse.yaml ## image-reuse.yaml
- The reusable workflow can be used to publish or build images with multiple container registries(Quay,GHCR, dockerhub), and then sign them with cosign when an image is published. - The resuable workflow can be used to publish or build images with multiple container registries(Quay,GHCR, dockerhub), and then sign them with cosign when an image is published.
- A GO version `must` be specified e.g. 1.21 - A GO version `must` be specified e.g. 1.21
- The image name for each registry *must* contain the tag. Note: multiple tags are allowed for each registry using a CSV type. - The image name for each registry *must* contain the tag. Note: multiple tags are allowed for each registry using a CSV type.
- Multiple platforms can be specified e.g. linux/amd64,linux/arm64 - Multiple platforms can be specified e.g. linux/amd64,linux/arm64

View File

@@ -1,89 +0,0 @@
name: Bump major version
on:
workflow_dispatch: {}
permissions: {}
jobs:
prepare-release:
permissions:
contents: write # for peter-evans/create-pull-request to create branch
pull-requests: write # for peter-evans/create-pull-request to create a PR
name: Automatically update major version
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
# Get the current major version from go.mod and save it as a variable.
- name: Get target version
id: get-target-version
run: |
set -ue
CURRENT_VERSION=$(grep 'module github.com/argoproj/argo-cd' go.mod | awk '{print $2}' | sed 's/.*\/v//')
echo "TARGET_VERSION=$((CURRENT_VERSION + 1))" >> $GITHUB_OUTPUT
- name: Copy source code to GOPATH
run: |
mkdir -p ~/go/src/github.com/argoproj
cp -a ../argo-cd ~/go/src/github.com/argoproj
- name: Run script to bump the version
run: |
hack/bump-major-version.sh
working-directory: /home/runner/go/src/github.com/argoproj/argo-cd
- name: Setup Golang
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Add ~/go/bin to PATH
run: |
echo "/home/runner/go/bin" >> $GITHUB_PATH
- name: Add /usr/local/bin to PATH
run: |
echo "/usr/local/bin" >> $GITHUB_PATH
- name: Download & vendor dependencies
run: |
# We need to vendor go modules for codegen yet
go mod download
go mod vendor -v
working-directory: /home/runner/go/src/github.com/argoproj/argo-cd
- name: Install toolchain for codegen
run: |
make install-codegen-tools-local
make install-go-tools-local
working-directory: /home/runner/go/src/github.com/argoproj/argo-cd
# We install kustomize in the dist directory
- name: Add dist to PATH
run: |
echo "/home/runner/work/argo-cd/argo-cd/dist" >> $GITHUB_PATH
- name: Run codegen
run: |
set -x
export GOPATH=$(go env GOPATH)
make codegen-local
working-directory: /home/runner/go/src/github.com/argoproj/argo-cd
- name: Copy changes back
run: |
# Copy the contents back, but skip the .git directory
rsync -a --exclude=.git /home/runner/go/src/github.com/argoproj/argo-cd/ ../argo-cd
- name: Create pull request
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with:
commit-message: "Bump major version to ${{ steps.get-target-version.outputs.TARGET_VERSION }}"
title: "Bump major version to ${{ steps.get-target-version.outputs.TARGET_VERSION }}"
body: |
Congrats! You've just bumped the major version to ${{ steps.get-target-version.outputs.TARGET_VERSION }}.
Next steps:
- [ ] Merge this PR
- [ ] Add an upgrade guide to the docs for this version
branch: bump-major-version
branch-suffix: random
signoff: true

View File

@@ -14,11 +14,11 @@ on:
env: env:
# Golang version to use across CI steps # Golang version to use across CI steps
# renovate: datasource=golang-version packageName=golang # renovate: datasource=golang-version packageName=golang
GOLANG_VERSION: '1.24.4' GOLANG_VERSION: '1.23.3'
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} cancel-in-progress: true
permissions: permissions:
contents: read contents: read
@@ -32,14 +32,14 @@ jobs:
docs: ${{ steps.filter.outputs.docs_any_changed }} docs: ${{ steps.filter.outputs.docs_any_changed }}
steps: steps:
- uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 - uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46.0.5 - uses: tj-actions/changed-files@bab30c2299617f6615ec02a68b9a40d10bd21366 # v45.0.5
id: filter id: filter
with: with:
# Any file which is not under docs/, ui/ or is not a markdown file is counted as a backend file # Any file which is not under docs/, ui/ or is not a markdown file is counted as a backend file
files_yaml: | files_yaml: |
backend: backend:
- '!ui/**' - '!ui/**'
- '!**.md' - '!**.md'
- '!**/*.md' - '!**/*.md'
- '!docs/**' - '!docs/**'
frontend: frontend:
@@ -57,7 +57,7 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Download all Go modules - name: Download all Go modules
@@ -78,11 +78,11 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Restore go build cache - name: Restore go build cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with: with:
path: ~/.cache/go-build path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }} key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -94,8 +94,8 @@ jobs:
lint-go: lint-go:
permissions: permissions:
contents: read # for actions/checkout to fetch code contents: read # for actions/checkout to fetch code
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
name: Lint Go code name: Lint Go code
if: ${{ needs.changes.outputs.backend == 'true' }} if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
@@ -105,14 +105,14 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Run golangci-lint - name: Run golangci-lint
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1
with: with:
# renovate: datasource=go packageName=github.com/golangci/golangci-lint versioning=regex:^v(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)?$ # renovate: datasource=go packageName=github.com/golangci/golangci-lint versioning=regex:^v(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)?$
version: v2.1.6 version: v1.62.2
args: --verbose args: --verbose
test-go: test-go:
@@ -133,7 +133,7 @@ jobs:
- name: Create symlink in GOPATH - name: Create symlink in GOPATH
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Install required packages - name: Install required packages
@@ -153,7 +153,7 @@ jobs:
run: | run: |
echo "/usr/local/bin" >> $GITHUB_PATH echo "/usr/local/bin" >> $GITHUB_PATH
- name: Restore go build cache - name: Restore go build cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with: with:
path: ~/.cache/go-build path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }} key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -174,7 +174,7 @@ jobs:
- name: Run all unit tests - name: Run all unit tests
run: make test-local run: make test-local
- name: Generate test results artifacts - name: Generate test results artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with: with:
name: test-results name: test-results
path: test-results path: test-results
@@ -197,7 +197,7 @@ jobs:
- name: Create symlink in GOPATH - name: Create symlink in GOPATH
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Install required packages - name: Install required packages
@@ -217,7 +217,7 @@ jobs:
run: | run: |
echo "/usr/local/bin" >> $GITHUB_PATH echo "/usr/local/bin" >> $GITHUB_PATH
- name: Restore go build cache - name: Restore go build cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with: with:
path: ~/.cache/go-build path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }} key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -238,7 +238,7 @@ jobs:
- name: Run all unit tests - name: Run all unit tests
run: make test-race-local run: make test-race-local
- name: Generate test results artifacts - name: Generate test results artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with: with:
name: race-results name: race-results
path: test-results/ path: test-results/
@@ -253,7 +253,7 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Create symlink in GOPATH - name: Create symlink in GOPATH
@@ -305,13 +305,13 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup NodeJS - name: Setup NodeJS
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with: with:
# renovate: datasource=node-version packageName=node versioning=node # renovate: datasource=node-version packageName=node versioning=node
node-version: '22.9.0' node-version: '22.9.0'
- name: Restore node dependency cache - name: Restore node dependency cache
id: cache-dependencies id: cache-dependencies
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with: with:
path: ui/node_modules path: ui/node_modules
key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }} key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }}
@@ -333,15 +333,6 @@ jobs:
run: yarn lint run: yarn lint
working-directory: ui/ working-directory: ui/
shellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- run: |
sudo apt-get install shellcheck
shellcheck -e SC2086 -e SC2046 -e SC2068 -e SC2206 -e SC2048 -e SC2059 -e SC2154 -e SC2034 -e SC2016 -e SC2128 -e SC1091 -e SC2207 $(find . -type f -name '*.sh') | tee sc.log
test ! -s sc.log
analyze: analyze:
name: Process & analyze test artifacts name: Process & analyze test artifacts
if: ${{ needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true' }} if: ${{ needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true' }}
@@ -360,7 +351,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- name: Restore node dependency cache - name: Restore node dependency cache
id: cache-dependencies id: cache-dependencies
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with: with:
path: ui/node_modules path: ui/node_modules
key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }} key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }}
@@ -368,12 +359,12 @@ jobs:
run: | run: |
rm -rf ui/node_modules/argo-ui/node_modules rm -rf ui/node_modules/argo-ui/node_modules
- name: Get e2e code coverage - name: Get e2e code coverage
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with: with:
name: e2e-code-coverage name: e2e-code-coverage
path: e2e-code-coverage path: e2e-code-coverage
- name: Get unit test code coverage - name: Get unit test code coverage
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with: with:
name: test-results name: test-results
path: test-results path: test-results
@@ -385,15 +376,15 @@ jobs:
run: | run: |
go tool covdata percent -i=test-results,e2e-code-coverage/applicationset-controller,e2e-code-coverage/repo-server,e2e-code-coverage/app-controller,e2e-code-coverage/commit-server -o test-results/full-coverage.out go tool covdata percent -i=test-results,e2e-code-coverage/applicationset-controller,e2e-code-coverage/repo-server,e2e-code-coverage/app-controller,e2e-code-coverage/commit-server -o test-results/full-coverage.out
- name: Upload code coverage information to codecov.io - name: Upload code coverage information to codecov.io
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0
with: with:
files: test-results/full-coverage.out file: test-results/full-coverage.out
fail_ci_if_error: true fail_ci_if_error: true
env: env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- name: Upload test results to Codecov - name: Upload test results to Codecov
if: github.ref == 'refs/heads/master' && github.event_name == 'push' && github.repository == 'argoproj/argo-cd' if: github.ref == 'refs/heads/master' && github.event_name == 'push' && github.repository == 'argoproj/argo-cd'
uses: codecov/test-results-action@47f89e9acb64b76debcd5ea40642d25a4adced9f # v1.1.1 uses: codecov/test-results-action@9739113ad922ea0a9abb4b2c0f8bf6a4aa8ef820 # v1.0.1
with: with:
file: test-results/junit.xml file: test-results/junit.xml
fail_ci_if_error: true fail_ci_if_error: true
@@ -402,7 +393,7 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
uses: SonarSource/sonarqube-scan-action@2500896589ef8f7247069a56136f8dc177c27ccf # v5.2.0 uses: SonarSource/sonarqube-scan-action@1b442ee39ac3fa7c2acdd410208dcb2bcfaae6c4 # v4.1.0
if: env.sonar_secret != '' if: env.sonar_secret != ''
test-e2e: test-e2e:
name: Run end-to-end tests name: Run end-to-end tests
@@ -411,45 +402,37 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
# latest: true means that this version mush upload the coverage report to codecov.io
# We designate the latest version because we only collect code coverage for that version.
k3s: k3s:
- version: v1.33.1
latest: true
- version: v1.32.1
latest: false
- version: v1.31.0 - version: v1.31.0
latest: false # We designate the latest version because we only collect code coverage for that version.
latest: true
- version: v1.30.4 - version: v1.30.4
latest: false latest: false
- version: v1.29.8
latest: false
- version: v1.28.13
latest: false
needs: needs:
- build-go - build-go
- changes - changes
env: env:
GOPATH: /home/runner/go GOPATH: /home/runner/go
ARGOCD_FAKE_IN_CLUSTER: 'true' ARGOCD_FAKE_IN_CLUSTER: "true"
ARGOCD_SSH_DATA_PATH: '/tmp/argo-e2e/app/config/ssh' ARGOCD_SSH_DATA_PATH: "/tmp/argo-e2e/app/config/ssh"
ARGOCD_TLS_DATA_PATH: '/tmp/argo-e2e/app/config/tls' ARGOCD_TLS_DATA_PATH: "/tmp/argo-e2e/app/config/tls"
ARGOCD_E2E_SSH_KNOWN_HOSTS: '../fixture/certs/ssh_known_hosts' ARGOCD_E2E_SSH_KNOWN_HOSTS: "../fixture/certs/ssh_known_hosts"
ARGOCD_E2E_K3S: 'true' ARGOCD_E2E_K3S: "true"
ARGOCD_IN_CI: 'true' ARGOCD_IN_CI: "true"
ARGOCD_E2E_APISERVER_PORT: '8088' ARGOCD_E2E_APISERVER_PORT: "8088"
ARGOCD_APPLICATION_NAMESPACES: 'argocd-e2e-external,argocd-e2e-external-2' ARGOCD_APPLICATION_NAMESPACES: "argocd-e2e-external,argocd-e2e-external-2"
ARGOCD_SERVER: '127.0.0.1:8088' ARGOCD_SERVER: "127.0.0.1:8088"
GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }} GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
steps: steps:
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
with:
large-packages: false
docker-images: false
swap-storage: false
tool-cache: false
- name: Checkout code - name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: GH actions workaround - Kill XSP4 process - name: GH actions workaround - Kill XSP4 process
@@ -468,7 +451,7 @@ jobs:
sudo chmod go-r $HOME/.kube/config sudo chmod go-r $HOME/.kube/config
kubectl version kubectl version
- name: Restore go build cache - name: Restore go build cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with: with:
path: ~/.cache/go-build path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }} key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -494,9 +477,9 @@ jobs:
git config --global user.email "john.doe@example.com" git config --global user.email "john.doe@example.com"
- name: Pull Docker image required for tests - name: Pull Docker image required for tests
run: | run: |
docker pull ghcr.io/dexidp/dex:v2.43.0 docker pull ghcr.io/dexidp/dex:v2.41.1
docker pull argoproj/argo-cd-ci-builder:v1.0.0 docker pull argoproj/argo-cd-ci-builder:v1.0.0
docker pull redis:7.2.7-alpine docker pull redis:7.0.15-alpine
- name: Create target directory for binaries in the build-process - name: Create target directory for binaries in the build-process
run: | run: |
mkdir -p dist mkdir -p dist
@@ -526,13 +509,13 @@ jobs:
goreman run stop-all || echo "goreman trouble" goreman run stop-all || echo "goreman trouble"
sleep 30 sleep 30
- name: Upload e2e coverage report - name: Upload e2e coverage report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with: with:
name: e2e-code-coverage name: e2e-code-coverage
path: /tmp/coverage path: /tmp/coverage
if: ${{ matrix.k3s.latest }} if: ${{ matrix.k3s.latest }}
- name: Upload e2e-server logs - name: Upload e2e-server logs
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with: with:
name: e2e-server-k8s${{ matrix.k3s.version }}.log name: e2e-server-k8s${{ matrix.k3s.version }}.log
path: /tmp/e2e-server.log path: /tmp/e2e-server.log
@@ -559,4 +542,4 @@ jobs:
exit 0 exit 0
else else
exit 1 exit 1
fi fi

View File

@@ -33,7 +33,7 @@ jobs:
# Use correct go version. https://github.com/github/codeql-action/issues/1842#issuecomment-1704398087 # Use correct go version. https://github.com/github/codeql-action/issues/1842#issuecomment-1704398087
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version-file: go.mod go-version-file: go.mod

View File

@@ -17,9 +17,11 @@ on:
platforms: platforms:
required: true required: true
type: string type: string
default: linux/amd64
push: push:
required: true required: true
type: boolean type: boolean
default: false
target: target:
required: false required: false
type: string type: string
@@ -67,16 +69,15 @@ jobs:
if: ${{ github.ref_type != 'tag'}} if: ${{ github.ref_type != 'tag'}}
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ inputs.go-version }} go-version: ${{ inputs.go-version }}
cache: false
- name: Install cosign - name: Install cosign
uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2 uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
- uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 - uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1
- name: Setup tags for container image as a CSV type - name: Setup tags for container image as a CSV type
run: | run: |
@@ -103,7 +104,7 @@ jobs:
echo 'EOF' >> $GITHUB_ENV echo 'EOF' >> $GITHUB_ENV
- name: Login to Quay.io - name: Login to Quay.io
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with: with:
registry: quay.io registry: quay.io
username: ${{ secrets.quay_username }} username: ${{ secrets.quay_username }}
@@ -111,7 +112,7 @@ jobs:
if: ${{ inputs.quay_image_name && inputs.push }} if: ${{ inputs.quay_image_name && inputs.push }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ secrets.ghcr_username }} username: ${{ secrets.ghcr_username }}
@@ -119,7 +120,7 @@ jobs:
if: ${{ inputs.ghcr_image_name && inputs.push }} if: ${{ inputs.ghcr_image_name && inputs.push }}
- name: Login to dockerhub Container Registry - name: Login to dockerhub Container Registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with: with:
username: ${{ secrets.docker_username }} username: ${{ secrets.docker_username }}
password: ${{ secrets.docker_password }} password: ${{ secrets.docker_password }}
@@ -142,7 +143,7 @@ jobs:
- name: Build and push container image - name: Build and push container image
id: image id: image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 #v6.18.0 uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 #v6.10.0
with: with:
context: . context: .
platforms: ${{ inputs.platforms }} platforms: ${{ inputs.platforms }}

View File

@@ -7,7 +7,7 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
types: [labeled, unlabeled, opened, synchronize, reopened] types: [ labeled, unlabeled, opened, synchronize, reopened ]
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
@@ -46,14 +46,14 @@ jobs:
needs: [set-vars] needs: [set-vars]
permissions: permissions:
contents: read contents: read
packages: write # for pushing packages to GHCR, which is used by cd.apps.argoproj.io to avoid polluting Quay with tags packages: write # for pushing packages to GHCR, which is used by cd.apps.argoproj.io to avoid polluting Quay with tags
id-token: write # for creating OIDC tokens for signing. id-token: write # for creating OIDC tokens for signing.
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name != 'push' }} if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name != 'push' }}
uses: ./.github/workflows/image-reuse.yaml uses: ./.github/workflows/image-reuse.yaml
with: with:
# Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations) # 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 # renovate: datasource=golang-version packageName=golang
go-version: 1.24.4 go-version: 1.23.3
platforms: ${{ needs.set-vars.outputs.platforms }} platforms: ${{ needs.set-vars.outputs.platforms }}
push: false push: false
@@ -61,7 +61,7 @@ jobs:
needs: [set-vars] needs: [set-vars]
permissions: permissions:
contents: read contents: read
packages: write # for pushing packages to GHCR, which is used by cd.apps.argoproj.io to avoid polluting Quay with tags packages: write # for pushing packages to GHCR, which is used by cd.apps.argoproj.io to avoid polluting Quay with tags
id-token: write # for creating OIDC tokens for signing. id-token: write # for creating OIDC tokens for signing.
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }} if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
uses: ./.github/workflows/image-reuse.yaml uses: ./.github/workflows/image-reuse.yaml
@@ -70,7 +70,7 @@ jobs:
ghcr_image_name: ghcr.io/argoproj/argo-cd/argocd:${{ needs.set-vars.outputs.image-tag }} 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) # 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 # renovate: datasource=golang-version packageName=golang
go-version: 1.24.4 go-version: 1.23.3
platforms: ${{ needs.set-vars.outputs.platforms }} platforms: ${{ needs.set-vars.outputs.platforms }}
push: true push: true
secrets: secrets:
@@ -88,7 +88,7 @@ jobs:
packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues) packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }} if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator # Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0 uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
with: with:
image: ghcr.io/argoproj/argo-cd/argocd image: ghcr.io/argoproj/argo-cd/argocd
digest: ${{ needs.build-and-publish.outputs.image-digest }} digest: ${{ needs.build-and-publish.outputs.image-digest }}
@@ -101,8 +101,8 @@ jobs:
- build-and-publish - build-and-publish
- set-vars - set-vars
permissions: permissions:
contents: write # for git to push upgrade commit if not already deployed contents: write # for git to push upgrade commit if not already deployed
packages: write # for pushing packages to GHCR, which is used by cd.apps.argoproj.io to avoid polluting Quay with tags packages: write # for pushing packages to GHCR, which is used by cd.apps.argoproj.io to avoid polluting Quay with tags
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }} if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
@@ -116,3 +116,4 @@ jobs:
git config --global user.name 'CI' git config --global user.name 'CI'
git diff --exit-code && echo 'Already deployed' || (git commit -am 'Upgrade argocd to ${{ needs.set-vars.outputs.image-tag }}' && git push) git diff --exit-code && echo 'Already deployed' || (git commit -am 'Upgrade argocd to ${{ needs.set-vars.outputs.image-tag }}' && git push)
working-directory: argoproj-deployments/argocd working-directory: argoproj-deployments/argocd

View File

@@ -64,7 +64,7 @@ jobs:
git stash pop git stash pop
- name: Create pull request - name: Create pull request
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with: with:
commit-message: "Bump version to ${{ inputs.TARGET_VERSION }}" commit-message: "Bump version to ${{ inputs.TARGET_VERSION }}"
title: "Bump version to ${{ inputs.TARGET_VERSION }} on ${{ inputs.TARGET_BRANCH }} branch" title: "Bump version to ${{ inputs.TARGET_VERSION }} on ${{ inputs.TARGET_BRANCH }} branch"

View File

@@ -12,8 +12,8 @@ permissions: {}
# workflow being trigger a number of times. This limits it # workflow being trigger a number of times. This limits it
# to one run per PR. # to one run per PR.
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
validate: validate:

View File

@@ -11,7 +11,7 @@ permissions: {}
env: env:
# renovate: datasource=golang-version packageName=golang # renovate: datasource=golang-version packageName=golang
GOLANG_VERSION: '1.24.4' # Note: go-version must also be set in job argocd-image.with.go-version GOLANG_VERSION: '1.23.3' # Note: go-version must also be set in job argocd-image.with.go-version
jobs: jobs:
argocd-image: argocd-image:
@@ -25,7 +25,7 @@ jobs:
quay_image_name: quay.io/argoproj/argocd:${{ github.ref_name }} 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) # 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 # renovate: datasource=golang-version packageName=golang
go-version: 1.24.4 go-version: 1.23.3
platforms: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le platforms: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
push: true push: true
secrets: secrets:
@@ -33,20 +33,20 @@ jobs:
quay_password: ${{ secrets.RELEASE_QUAY_TOKEN }} quay_password: ${{ secrets.RELEASE_QUAY_TOKEN }}
argocd-image-provenance: argocd-image-provenance:
needs: [argocd-image] needs: [argocd-image]
permissions: permissions:
actions: read # for detecting the Github Actions environment. actions: read # for detecting the Github Actions environment.
id-token: write # for creating OIDC tokens for signing. id-token: write # for creating OIDC tokens for signing.
packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues) packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator # Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
if: github.repository == 'argoproj/argo-cd' if: github.repository == 'argoproj/argo-cd'
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0 uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
with: with:
image: quay.io/argoproj/argocd image: quay.io/argoproj/argocd
digest: ${{ needs.argocd-image.outputs.image-digest }} digest: ${{ needs.argocd-image.outputs.image-digest }}
secrets: secrets:
registry-username: ${{ secrets.RELEASE_QUAY_USERNAME }} registry-username: ${{ secrets.RELEASE_QUAY_USERNAME }}
registry-password: ${{ secrets.RELEASE_QUAY_TOKEN }} registry-password: ${{ secrets.RELEASE_QUAY_TOKEN }}
goreleaser: goreleaser:
needs: needs:
@@ -70,16 +70,14 @@ jobs:
run: git fetch --force --tags run: git fetch --force --tags
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
cache: false
- name: Set GORELEASER_PREVIOUS_TAG # Workaround, GoReleaser uses 'git-describe' to determine a previous tag. Our tags are created in release branches. - name: Set GORELEASER_PREVIOUS_TAG # Workaround, GoReleaser uses 'git-describe' to determine a previous tag. Our tags are created in release branches.
run: | run: |
set -xue set -xue
GORELEASER_PREVIOUS_TAG=$(go run hack/get-previous-release/get-previous-version-for-release-notes.go ${{ github.ref_name }}) || exit 1 echo "GORELEASER_PREVIOUS_TAG=$(go run hack/get-previous-release/get-previous-version-for-release-notes.go ${{ github.ref_name }})" >> $GITHUB_ENV
echo "GORELEASER_PREVIOUS_TAG=$GORELEASER_PREVIOUS_TAG" >> $GITHUB_ENV
- name: Set environment variables for ldflags - name: Set environment variables for ldflags
id: set_ldflag id: set_ldflag
@@ -96,7 +94,7 @@ jobs:
tool-cache: false tool-cache: false
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf # v6.1.0
id: run-goreleaser id: run-goreleaser
with: with:
version: latest version: latest
@@ -109,7 +107,7 @@ jobs:
- name: Generate subject for provenance - name: Generate subject for provenance
id: hash id: hash
env: env:
ARTIFACTS: '${{ steps.run-goreleaser.outputs.artifacts }}' ARTIFACTS: "${{ steps.run-goreleaser.outputs.artifacts }}"
run: | run: |
set -euo pipefail set -euo pipefail
@@ -128,10 +126,10 @@ jobs:
contents: write # Needed for release uploads contents: write # Needed for release uploads
if: github.repository == 'argoproj/argo-cd' if: github.repository == 'argoproj/argo-cd'
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator # Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0 uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
with: with:
base64-subjects: '${{ needs.goreleaser.outputs.hashes }}' base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
provenance-name: 'argocd-cli.intoto.jsonl' provenance-name: "argocd-cli.intoto.jsonl"
upload-assets: true upload-assets: true
generate-sbom: generate-sbom:
@@ -153,10 +151,9 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
cache: false
- name: Generate SBOM (spdx) - name: Generate SBOM (spdx)
id: spdx-builder id: spdx-builder
@@ -167,7 +164,7 @@ jobs:
SIGS_BOM_VERSION: v0.2.1 SIGS_BOM_VERSION: v0.2.1
# comma delimited list of project relative folders to inspect for package # comma delimited list of project relative folders to inspect for package
# managers (gomod, yarn, npm). # managers (gomod, yarn, npm).
PROJECT_FOLDERS: '.,./ui' PROJECT_FOLDERS: ".,./ui"
# full qualified name of the docker image to be inspected # full qualified name of the docker image to be inspected
DOCKER_IMAGE: quay.io/argoproj/argocd:${{ github.ref_name }} DOCKER_IMAGE: quay.io/argoproj/argocd:${{ github.ref_name }}
run: | run: |
@@ -198,7 +195,7 @@ jobs:
echo "hashes=$(sha256sum /tmp/sbom.tar.gz | base64 -w0)" >> "$GITHUB_OUTPUT" echo "hashes=$(sha256sum /tmp/sbom.tar.gz | base64 -w0)" >> "$GITHUB_OUTPUT"
- name: Upload SBOM - name: Upload SBOM
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2 uses: softprops/action-gh-release@7b4da11513bf3f43f9999e90eabced41ab8bb048 # v2.2.0
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
@@ -213,10 +210,10 @@ jobs:
contents: write # Needed for release uploads contents: write # Needed for release uploads
if: github.repository == 'argoproj/argo-cd' if: github.repository == 'argoproj/argo-cd'
# Must be referenced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator # Must be referenced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0 uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
with: with:
base64-subjects: '${{ needs.generate-sbom.outputs.hashes }}' base64-subjects: "${{ needs.generate-sbom.outputs.hashes }}"
provenance-name: 'argocd-sbom.intoto.jsonl' provenance-name: "argocd-sbom.intoto.jsonl"
upload-assets: true upload-assets: true
post-release: post-release:
@@ -296,10 +293,10 @@ jobs:
if: ${{ env.UPDATE_VERSION == 'true' }} if: ${{ env.UPDATE_VERSION == 'true' }}
- name: Create PR to update VERSION on master branch - name: Create PR to update VERSION on master branch
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with: with:
commit-message: Bump version in master commit-message: Bump version in master
title: 'chore: Bump version in master' title: "chore: Bump version in master"
body: All images built from master should indicate which version we are on track for. body: All images built from master should indicate which version we are on track for.
signoff: true signoff: true
branch: update-version branch: update-version

View File

@@ -35,7 +35,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: "Run analysis" - name: "Run analysis"
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
with: with:
results_file: results.sarif results_file: results.sarif
results_format: sarif results_format: sarif
@@ -54,7 +54,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab. # format to the repository Actions tab.
- name: "Upload artifact" - name: "Upload artifact"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with: with:
name: SARIF file name: SARIF file
path: results.sarif path: results.sarif

4
.gitignore vendored
View File

@@ -8,7 +8,6 @@ ui/dist/app/*
!ui/dist/app/gitkeep !ui/dist/app/gitkeep
site/ site/
*.iml *.iml
.tilt-bin/
# delve debug binaries # delve debug binaries
cmd/**/debug cmd/**/debug
debug.test debug.test
@@ -28,6 +27,3 @@ cmd/argocd/argocd
cmd/argocd-application-controller/argocd-application-controller cmd/argocd-application-controller/argocd-application-controller
cmd/argocd-repo-server/argocd-repo-server cmd/argocd-repo-server/argocd-repo-server
cmd/argocd-server/argocd-server cmd/argocd-server/argocd-server
# ignore generated `.argocd-helm-dep-up` marker file; this should not be committed to git
reposerver/repository/testdata/**/.argocd-helm-dep-up

21
.gitpod.Dockerfile vendored Normal file
View File

@@ -0,0 +1,21 @@
FROM gitpod/workspace-full@sha256:230285e0b949e6d728d384b2029a4111db7b9c87c182f22f32a0be9e36b225df
USER root
RUN curl -o /usr/local/bin/kubectl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" && \
chmod +x /usr/local/bin/kubectl
RUN curl -L https://github.com/kubernetes-sigs/kubebuilder/releases/download/v2.3.1/kubebuilder_2.3.1_$(go env GOOS)_$(go env GOARCH).tar.gz | \
tar -xz -C /tmp/ && mv /tmp/kubebuilder_2.3.1_$(go env GOOS)_$(go env GOARCH) /usr/local/kubebuilder
ENV GOCACHE=/go-build-cache
RUN apt-get install redis-server -y
RUN go install github.com/mattn/goreman@latest
RUN chown -R gitpod:gitpod /go-build-cache
USER gitpod
ENV ARGOCD_REDIS_LOCAL=true
ENV KUBECONFIG=/tmp/kubeconfig

6
.gitpod.yml Normal file
View File

@@ -0,0 +1,6 @@
image:
file: .gitpod.Dockerfile
tasks:
- init: make mod-download-local dep-ui-local && GO111MODULE=off go install github.com/mattn/goreman@latest
command: make start-test-k8s

View File

@@ -1,253 +1,59 @@
formatters:
enable:
- gofumpt
- goimports
settings:
goimports:
local-prefixes:
- github.com/argoproj/argo-cd/v3
issues: issues:
exclude:
- SA5011
max-issues-per-linter: 0 max-issues-per-linter: 0
max-same-issues: 0 max-same-issues: 0
exclude-rules:
- path: '(.+)_test\.go'
linters:
- unparam
linters: linters:
enable: enable:
- errcheck
- errorlint - errorlint
- exptostd
- gocritic - gocritic
- gomodguard - gofumpt
- goimports
- gosimple
- govet - govet
- importas - ineffassign
- misspell - misspell
- perfsprint - perfsprint
- revive
- staticcheck - staticcheck
- testifylint - testifylint
- thelper - thelper
- tparallel
- unparam - unparam
- unused
- usestdlibvars - usestdlibvars
- usetesting - whitespace
- whitespace linters-settings:
gocritic:
exclusions: disabled-checks:
rules: - appendAssign
- linters: - assignOp # Keep it disabled for readability
- unparam - badCond
path: (.+)_test\.go - commentFormatting
- exitAfterDefer
presets: - ifElseChain
- comments - mapKey
- common-false-positives - singleCaseSwitch
- legacy - typeSwitchVar
- std-error-handling goimports:
local-prefixes: github.com/argoproj/argo-cd/v2
warn-unused: true perfsprint:
# Optimizes even if it requires an int or uint type cast.
settings: int-conversion: true
gocritic: # Optimizes into `err.Error()` even if it is only equivalent for non-nil errors.
enable-all: true err-error: false
# Most of these should probably be enabled one-by-one. # Optimizes `fmt.Errorf`.
disabled-checks: errorf: false
- appendAssign # Optimizes `fmt.Sprintf` with only one argument.
- appendCombine # Leave disabled, multi-line assigns can be more readable. sprintf1: true
- assignOp # Leave disabled, assign operations can be more confusing than helpful. # Optimizes into strings concatenation.
- commentedOutCode strconcat: false
- deferInLoop testifylint:
- exitAfterDefer enable-all: true
- exposedSyncMutex disable:
- hugeParam - go-require
- importShadow run:
- paramTypeCombine # Leave disabled, there are too many failures to be worth fixing. timeout: 50m
- rangeValCopy
- tooManyResultsChecker
- unnamedResult
- whyNoLint
gomodguard:
blocked:
modules:
- github.com/golang-jwt/jwt/v4:
recommendations:
- github.com/golang-jwt/jwt/v5
- github.com/imdario/mergo:
recommendations:
- dario.cat/mergo
reason: '`github.com/imdario/mergo` has been renamed.'
- github.com/pkg/errors:
recommendations:
- errors
govet:
disable:
- fieldalignment
- shadow
enable-all: true
importas:
alias:
- pkg: github.com/golang-jwt/jwt/v5
alias: jwtgo
- pkg: k8s.io/api/apps/v1
alias: appsv1
- pkg: k8s.io/api/core/v1
alias: corev1
- pkg: k8s.io/api/rbac/v1
alias: rbacv1
- pkg: k8s.io/apimachinery/pkg/api/errors
alias: apierrors
- pkg: k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1
alias: apiextensionsv1
- pkg: k8s.io/apimachinery/pkg/apis/meta/v1
alias: metav1
- pkg: k8s.io/client-go/informers/core/v1
alias: informersv1
- pkg: errors
alias: stderrors
- pkg: github.com/argoproj/argo-cd/v3/util/io
alias: utilio
nolintlint:
require-specific: true
perfsprint:
# Optimizes even if it requires an int or uint type cast.
int-conversion: true
# Optimizes into `err.Error()` even if it is only equivalent for non-nil errors.
err-error: true
# Optimizes `fmt.Errorf`.
errorf: true
# Optimizes `fmt.Sprintf` with only one argument.
sprintf1: true
# Optimizes into strings concatenation.
strconcat: true
revive:
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md
rules:
- name: bool-literal-in-expr
- name: blank-imports
disabled: true
- name: context-as-argument
arguments:
- allowTypesBefore: '*testing.T,testing.TB'
- name: context-keys-type
disabled: true
- name: dot-imports
disabled: true
- name: duplicated-imports
- name: early-return
arguments:
- preserveScope
- name: empty-block
disabled: true
- name: error-naming
disabled: true
- name: error-return
- name: error-strings
disabled: true
- name: errorf
- name: identical-branches
- name: if-return
- name: increment-decrement
- name: indent-error-flow
arguments:
- preserveScope
- name: modifies-parameter
- name: optimize-operands-order
- name: range
- name: receiver-naming
- name: redefines-builtin-id
disabled: true
- name: redundant-import-alias
- name: superfluous-else
arguments:
- preserveScope
- name: time-equal
- name: time-naming
disabled: true
- name: unexported-return
disabled: true
- name: unnecessary-stmt
- name: unreachable-code
- name: unused-parameter
- name: use-any
- name: useless-break
- name: var-declaration
- name: var-naming
arguments:
- - ID
- - VM
- - skipPackageNameChecks: true
upperCaseConst: true
staticcheck:
checks:
- all
- -SA5011
- -ST1003
- -ST1016
testifylint:
enable-all: true
disable:
- go-require
unused:
field-writes-are-uses: false
exported-fields-are-used: false
usetesting:
os-mkdir-temp: false
output:
show-stats: false
version: "2"

View File

@@ -16,16 +16,16 @@ builds:
flags: flags:
- -v - -v
ldflags: ldflags:
- -X github.com/argoproj/argo-cd/v3/common.version={{ .Version }} - -X github.com/argoproj/argo-cd/v2/common.version={{ .Version }}
- -X github.com/argoproj/argo-cd/v3/common.buildDate={{ .Date }} - -X github.com/argoproj/argo-cd/v2/common.buildDate={{ .Date }}
- -X github.com/argoproj/argo-cd/v3/common.gitCommit={{ .FullCommit }} - -X github.com/argoproj/argo-cd/v2/common.gitCommit={{ .FullCommit }}
- -X github.com/argoproj/argo-cd/v3/common.gitTreeState={{ .Env.GIT_TREE_STATE }} - -X github.com/argoproj/argo-cd/v2/common.gitTreeState={{ .Env.GIT_TREE_STATE }}
- -X github.com/argoproj/argo-cd/v3/common.kubectlVersion={{ .Env.KUBECTL_VERSION }} - -X github.com/argoproj/argo-cd/v2/common.kubectlVersion={{ .Env.KUBECTL_VERSION }}
- '{{ if or (eq .Runtime.Goos "linux") (eq .Runtime.Goos "windows") }}-extldflags="-static"{{ end }}' - -extldflags="-static"
goos: goos:
- linux - linux
- windows
- darwin - darwin
- windows
goarch: goarch:
- amd64 - amd64
- arm64 - arm64
@@ -42,23 +42,14 @@ builds:
goarch: ppc64le goarch: ppc64le
- goos: windows - goos: windows
goarch: arm64 goarch: arm64
overrides:
- goos: darwin
goarch: amd64
env:
- CGO_ENABLED=1
- goos: darwin
goarch: arm64
env:
- CGO_ENABLED=1
archives: archives:
- id: argocd-archive - id: argocd-archive
ids: builds:
- argocd-cli - argocd-cli
name_template: |- name_template: |-
{{ .ProjectName }}-{{ .Os }}-{{ .Arch }} {{ .ProjectName }}-{{ .Os }}-{{ .Arch }}
formats: [ binary ] format: binary
checksum: checksum:
name_template: 'cli_checksums.txt' name_template: 'cli_checksums.txt'
@@ -88,28 +79,25 @@ release:
All Argo CD container images are signed by cosign. A Provenance is generated for container images and CLI binaries which meet the SLSA Level 3 specifications. See the [documentation](https://argo-cd.readthedocs.io/en/stable/operator-manual/signed-release-assets) on how to verify. All Argo CD container images are signed by cosign. A Provenance is generated for container images and CLI binaries which meet the SLSA Level 3 specifications. See the [documentation](https://argo-cd.readthedocs.io/en/stable/operator-manual/signed-release-assets) on how to verify.
## Release Notes Blog Post
For a detailed breakdown of the key changes and improvements in this release, check out the [official blog post](https://blog.argoproj.io/argo-cd-v3-0-release-candidate-a0b933f4e58f)
## Upgrading ## Upgrading
If upgrading from a different minor version, be sure to read the [upgrading](https://argo-cd.readthedocs.io/en/stable/operator-manual/upgrading/overview/) documentation. If upgrading from a different minor version, be sure to read the [upgrading](https://argo-cd.readthedocs.io/en/stable/operator-manual/upgrading/overview/) documentation.
footer: | footer: |
**Full Changelog**: https://github.com/argoproj/argo-cd/compare/{{ .PreviousTag }}...{{ .Tag }} **Full Changelog**: https://github.com/argoproj/argo-cd/compare/{{ .PreviousTag }}...{{ .Tag }}
<a href="https://argoproj.github.io/cd/"><img src="https://raw.githubusercontent.com/argoproj/argo-site/master/content/pages/cd/gitops-cd.png" width="25%" ></a> <a href="https://argoproj.github.io/cd/"><img src="https://raw.githubusercontent.com/argoproj/argo-site/master/content/pages/cd/gitops-cd.png" width="25%" ></a>
snapshot: #### To be removed for PR snapshot: #### To be removed for PR
version_template: '2.6.0' name_template: "2.6.0"
changelog: changelog:
use: github use:
github
sort: asc sort: asc
abbrev: 0 abbrev: 0
groups: # Regex use RE2 syntax as defined here: https://github.com/google/re2/wiki/Syntax. groups: # Regex use RE2 syntax as defined here: https://github.com/google/re2/wiki/Syntax.
- title: 'Breaking Changes'
regexp: '^.*?(\([[:word:]]+\))??!:.+$'
order: 0
- title: 'Features' - title: 'Features'
regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$' regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
order: 100 order: 100
@@ -129,4 +117,7 @@ changelog:
- '^test:' - '^test:'
- '^.*?Bump(\([[:word:]]+\))?.+$' - '^.*?Bump(\([[:word:]]+\))?.+$'
- '^.*?\[Bot\](\([[:word:]]+\))?.+$' - '^.*?\[Bot\](\([[:word:]]+\))?.+$'
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json # yaml-language-server: $schema=https://goreleaser.com/static/schema.json

View File

@@ -1,82 +1,76 @@
dir: '{{.InterfaceDir}}/mocks' # global config
structname: '{{.InterfaceName}}' filename: "{{.InterfaceName}}.go"
filename: '{{.InterfaceName}}.go' dir: "{{.InterfaceDir}}/mocks"
pkgname: mocks outpkg: "mocks"
mockname: "{{.InterfaceName}}"
template-data: with-expecter: false
unroll-variadic: true # individual interface config
packages: packages:
github.com/argoproj/argo-cd/v3/applicationset/generators: github.com/argoproj/argo-cd/v2/applicationset/generators:
interfaces: interfaces:
Generator: {} Generator:
github.com/argoproj/argo-cd/v3/applicationset/services: github.com/argoproj/argo-cd/v2/applicationset/services:
interfaces: interfaces:
Repos: {} Repos:
github.com/argoproj/argo-cd/v3/applicationset/services/scm_provider: github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider:
config: config:
dir: applicationset/services/scm_provider/aws_codecommit/mocks dir: "applicationset/services/scm_provider/aws_codecommit/mocks"
interfaces: interfaces:
AWSCodeCommitClient: {} AWSCodeCommitClient:
AWSTaggingClient: {} AWSTaggingClient:
github.com/argoproj/argo-cd/v3/applicationset/utils: github.com/microsoft/azure-devops-go-api/azuredevops/git:
interfaces:
Renderer: {}
github.com/argoproj/argo-cd/v3/commitserver/apiclient:
interfaces:
Clientset: {}
CommitServiceClient: {}
github.com/argoproj/argo-cd/v3/commitserver/commit:
interfaces:
RepoClientFactory: {}
github.com/argoproj/argo-cd/v3/controller/cache:
interfaces:
LiveStateCache: {}
github.com/argoproj/argo-cd/v3/pkg/apiclient/cluster:
interfaces:
ClusterServiceServer: {}
github.com/argoproj/argo-cd/v3/pkg/apiclient/session:
interfaces:
SessionServiceClient: {}
SessionServiceServer: {}
github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned/typed/application/v1alpha1:
interfaces:
AppProjectInterface: {}
github.com/argoproj/argo-cd/v3/reposerver/apiclient:
interfaces:
RepoServerService_GenerateManifestWithFilesClient: {}
RepoServerServiceClient: {}
github.com/argoproj/argo-cd/v3/server/application:
interfaces:
Broadcaster: {}
github.com/argoproj/argo-cd/v3/server/extension:
interfaces:
ApplicationGetter: {}
ExtensionMetricsRegistry: {}
ProjectGetter: {}
RbacEnforcer: {}
SettingsGetter: {}
UserGetter: {}
github.com/argoproj/argo-cd/v3/util/db:
interfaces:
ArgoDB: {}
github.com/argoproj/argo-cd/v3/util/git:
interfaces:
Client: {}
github.com/argoproj/argo-cd/v3/util/helm:
interfaces:
Client: {}
github.com/argoproj/argo-cd/v3/util/io:
interfaces:
TempPaths: {}
github.com/argoproj/argo-cd/v3/util/notification/argocd:
interfaces:
Service: {}
github.com/argoproj/argo-cd/v3/util/workloadidentity:
interfaces:
TokenProvider: {}
github.com/microsoft/azure-devops-go-api/azuredevops/v7/git:
config: config:
dir: applicationset/services/scm_provider/azure_devops/git/mocks dir: "applicationset/services/scm_provider/azure_devops/git/mocks"
interfaces: interfaces:
Client: {} Client:
github.com/argoproj/argo-cd/v2/applicationset/utils:
interfaces:
Renderer:
github.com/argoproj/argo-cd/v2/commitserver/commit:
interfaces:
RepoClientFactory:
github.com/argoproj/argo-cd/v2/commitserver/apiclient:
interfaces:
CommitServiceClient:
Clientset:
github.com/argoproj/argo-cd/v2/controller/cache:
interfaces:
LiveStateCache:
github.com/argoproj/argo-cd/v2/reposerver/apiclient:
interfaces:
RepoServerServiceClient:
RepoServerService_GenerateManifestWithFilesClient:
github.com/argoproj/argo-cd/v2/server/application:
interfaces:
Broadcaster:
github.com/argoproj/argo-cd/v2/server/extension:
interfaces:
ApplicationGetter:
ExtensionMetricsRegistry:
ProjectGetter:
RbacEnforcer:
SettingsGetter:
UserGetter:
github.com/argoproj/argo-cd/v2/util/db:
interfaces:
ArgoDB:
github.com/argoproj/argo-cd/v2/util/git:
interfaces:
Client:
github.com/argoproj/argo-cd/v2/util/helm:
interfaces:
Client:
github.com/argoproj/argo-cd/v2/util/io:
interfaces:
TempPaths:
github.com/argoproj/argo-cd/v2/util/notification/argocd:
interfaces:
Service:
# These mocks are not currently used, but they are part of the public API of this package.
github.com/argoproj/argo-cd/v2/pkg/apiclient/session:
interfaces:
SessionServiceServer:
SessionServiceClient:
github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster:
interfaces:
ClusterServiceServer:

View File

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

View File

@@ -1,12 +1,10 @@
ARG BASE_IMAGE=docker.io/library/ubuntu:24.04@sha256:80dd3c3b9c6cecb9f1667e9290b3bc61b78c2678c02cbdae5f0fea92cc6734ab ARG BASE_IMAGE=docker.io/library/ubuntu:24.04@sha256:3f85b7caad41a95462cf5b787d8a04604c8262cdcdf9a472b8c52ef83375fe15
#################################################################################################### ####################################################################################################
# Builder image # Builder image
# Initial stage which pulls prepares build dependencies and CLI tooling we need for our final image # Initial stage which pulls prepares build dependencies and CLI tooling we need for our final image
# Also used as the image in CI jobs so needs all dependencies # Also used as the image in CI jobs so needs all dependencies
#################################################################################################### ####################################################################################################
FROM docker.io/library/golang:1.24.4@sha256:db5d0afbfb4ab648af2393b92e87eaae9ad5e01132803d80caef91b5752d289c AS builder FROM docker.io/library/golang:1.23.3@sha256:d56c3e08fe5b27729ee3834854ae8f7015af48fd651cd25d1e3bcf3c19830174 AS builder
WORKDIR /tmp
RUN echo 'deb http://archive.debian.org/debian buster-backports main' >> /etc/apt/sources.list RUN echo 'deb http://archive.debian.org/debian buster-backports main' >> /etc/apt/sources.list
@@ -25,6 +23,8 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
apt-get clean && \ apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
WORKDIR /tmp
COPY hack/install.sh hack/tool-versions.sh ./ COPY hack/install.sh hack/tool-versions.sh ./
COPY hack/installers installers COPY hack/installers installers
@@ -40,8 +40,8 @@ LABEL org.opencontainers.image.source="https://github.com/argoproj/argo-cd"
USER root USER root
ENV ARGOCD_USER_ID=999 \ ENV ARGOCD_USER_ID=999
DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
RUN groupadd -g $ARGOCD_USER_ID argocd && \ RUN groupadd -g $ARGOCD_USER_ID argocd && \
useradd -r -u $ARGOCD_USER_ID -g argocd argocd && \ useradd -r -u $ARGOCD_USER_ID -g argocd argocd && \
@@ -55,13 +55,11 @@ RUN groupadd -g $ARGOCD_USER_ID argocd && \
apt-get clean && \ apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY hack/gpg-wrapper.sh \ COPY hack/gpg-wrapper.sh /usr/local/bin/gpg-wrapper.sh
hack/git-verify-wrapper.sh \ COPY hack/git-verify-wrapper.sh /usr/local/bin/git-verify-wrapper.sh
entrypoint.sh \
/usr/local/bin/
COPY --from=builder /usr/local/bin/helm /usr/local/bin/helm COPY --from=builder /usr/local/bin/helm /usr/local/bin/helm
COPY --from=builder /usr/local/bin/kustomize /usr/local/bin/kustomize COPY --from=builder /usr/local/bin/kustomize /usr/local/bin/kustomize
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
# keep uid_entrypoint.sh for backward compatibility # keep uid_entrypoint.sh for backward compatibility
RUN ln -s /usr/local/bin/entrypoint.sh /usr/local/bin/uid_entrypoint.sh RUN ln -s /usr/local/bin/entrypoint.sh /usr/local/bin/uid_entrypoint.sh
@@ -103,7 +101,7 @@ RUN HOST_ARCH=$TARGETARCH NODE_ENV='production' NODE_ONLINE_ENV='online' NODE_OP
#################################################################################################### ####################################################################################################
# Argo CD Build stage which performs the actual build of Argo CD binaries # Argo CD Build stage which performs the actual build of Argo CD binaries
#################################################################################################### ####################################################################################################
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.24.4@sha256:db5d0afbfb4ab648af2393b92e87eaae9ad5e01132803d80caef91b5752d289c AS argocd-build FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.23.3@sha256:d56c3e08fe5b27729ee3834854ae8f7015af48fd651cd25d1e3bcf3c19830174 AS argocd-build
WORKDIR /go/src/github.com/argoproj/argo-cd WORKDIR /go/src/github.com/argoproj/argo-cd
@@ -113,13 +111,13 @@ RUN go mod download
# Perform the build # Perform the build
COPY . . COPY . .
COPY --from=argocd-ui /src/dist/app /go/src/github.com/argoproj/argo-cd/ui/dist/app COPY --from=argocd-ui /src/dist/app /go/src/github.com/argoproj/argo-cd/ui/dist/app
ARG TARGETOS \ ARG TARGETOS
TARGETARCH ARG TARGETARCH
# These build args are optional; if not specified the defaults will be taken from the Makefile # These build args are optional; if not specified the defaults will be taken from the Makefile
ARG GIT_TAG \ ARG GIT_TAG
BUILD_DATE \ ARG BUILD_DATE
GIT_TREE_STATE \ ARG GIT_TREE_STATE
GIT_COMMIT ARG GIT_COMMIT
RUN GIT_COMMIT=$GIT_COMMIT \ RUN GIT_COMMIT=$GIT_COMMIT \
GIT_TREE_STATE=$GIT_TREE_STATE \ GIT_TREE_STATE=$GIT_TREE_STATE \
GIT_TAG=$GIT_TAG \ GIT_TAG=$GIT_TAG \
@@ -132,7 +130,6 @@ RUN GIT_COMMIT=$GIT_COMMIT \
# Final image # Final image
#################################################################################################### ####################################################################################################
FROM argocd-base FROM argocd-base
ENTRYPOINT ["/usr/bin/tini", "--"]
COPY --from=argocd-build /go/src/github.com/argoproj/argo-cd/dist/argocd* /usr/local/bin/ COPY --from=argocd-build /go/src/github.com/argoproj/argo-cd/dist/argocd* /usr/local/bin/
USER root USER root
@@ -147,3 +144,4 @@ RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-server && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-commit-server ln -s /usr/local/bin/argocd /usr/local/bin/argocd-commit-server
USER $ARGOCD_USER_ID USER $ARGOCD_USER_ID
ENTRYPOINT ["/usr/bin/tini", "--"]

View File

@@ -1,62 +0,0 @@
FROM docker.io/library/golang:1.24.1@sha256:c5adecdb7b3f8c5ca3c88648a861882849cc8b02fed68ece31e25de88ad13418
ENV DEBIAN_FRONTEND=noninteractive
RUN echo 'deb http://archive.debian.org/debian buster-backports main' >> /etc/apt/sources.list
RUN apt-get update && apt-get install --no-install-recommends -y \
curl \
openssh-server \
nginx \
unzip \
fcgiwrap \
git \
git-lfs \
make \
wget \
gcc \
sudo \
zip \
tini \
gpg \
tzdata \
connect-proxy
RUN go install github.com/go-delve/delve/cmd/dlv@latest
COPY hack/install.sh hack/tool-versions.sh ./
COPY hack/installers installers
RUN ./install.sh helm && \
INSTALL_PATH=/usr/local/bin ./install.sh kustomize
COPY hack/gpg-wrapper.sh \
hack/git-verify-wrapper.sh \
entrypoint.sh \
/usr/local/bin/
# support for mounting configuration from a configmap
WORKDIR /app/config/ssh
RUN touch ssh_known_hosts && \
ln -s /app/config/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts
WORKDIR /app/config
RUN mkdir -p tls && \
mkdir -p gpg/source && \
mkdir -p gpg/keys
COPY .tilt-bin/argocd_linux /usr/local/bin/argocd
RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-server && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-repo-server && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-application-controller && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-dex && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-notifications && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-applicationset-controller && \
ln -s /usr/local/bin/argocd /usr/local/bin/argocd-commit-server
# directory for Tilt restart file
RUN mkdir -p /tilt
# overridden by Tiltfile
ENTRYPOINT ["/usr/bin/tini", "-s", "--", "dlv", "exec", "--continue", "--accept-multiclient", "--headless", "--listen=:2345", "--api-version=2"]

View File

@@ -1,9 +0,0 @@
FROM node:24
WORKDIR /app/ui
COPY ui /app/ui
RUN yarn install
ENTRYPOINT ["yarn", "start"]

View File

@@ -1,18 +1,9 @@
PACKAGE=github.com/argoproj/argo-cd/v3/common PACKAGE=github.com/argoproj/argo-cd/v2/common
CURRENT_DIR=$(shell pwd) CURRENT_DIR=$(shell pwd)
DIST_DIR=${CURRENT_DIR}/dist DIST_DIR=${CURRENT_DIR}/dist
CLI_NAME=argocd CLI_NAME=argocd
BIN_NAME=argocd BIN_NAME=argocd
CGO_FLAG=0
UNAME_S:=$(shell uname)
IS_DARWIN:=$(if $(filter Darwin, $(UNAME_S)),true,false)
# When using OSX/Darwin, you might need to enable CGO for local builds
DEFAULT_CGO_FLAG:=0
ifeq ($(IS_DARWIN),true)
DEFAULT_CGO_FLAG:=1
endif
CGO_FLAG?=${DEFAULT_CGO_FLAG}
GEN_RESOURCES_CLI_NAME=argocd-resources-gen GEN_RESOURCES_CLI_NAME=argocd-resources-gen
@@ -156,11 +147,7 @@ PATH:=$(PATH):$(PWD)/hack
DOCKER_PUSH?=false DOCKER_PUSH?=false
IMAGE_NAMESPACE?= IMAGE_NAMESPACE?=
# perform static compilation # perform static compilation
DEFAULT_STATIC_BUILD:=true STATIC_BUILD?=true
ifeq ($(IS_DARWIN),true)
DEFAULT_STATIC_BUILD:=false
endif
STATIC_BUILD?=${DEFAULT_STATIC_BUILD}
# build development images # build development images
DEV_IMAGE?=false DEV_IMAGE?=false
ARGOCD_GPG_ENABLED?=true ARGOCD_GPG_ENABLED?=true
@@ -368,6 +355,11 @@ mod-vendor: test-tools-image
mod-vendor-local: mod-download-local mod-vendor-local: mod-download-local
go mod vendor go mod vendor
# Deprecated - replace by install-tools-local
.PHONY: install-lint-tools
install-lint-tools:
./hack/install.sh lint-tools
# Run linter on the code # Run linter on the code
.PHONY: lint .PHONY: lint
lint: test-tools-image lint: test-tools-image
@@ -443,7 +435,7 @@ test-e2e:
test-e2e-local: cli-local test-e2e-local: cli-local
# NO_PROXY ensures all tests don't go out through a proxy if one is configured on the test system # NO_PROXY ensures all tests don't go out through a proxy if one is configured on the test system
export GO111MODULE=off export GO111MODULE=off
DIST_DIR=${DIST_DIR} RERUN_FAILS=5 PACKAGES="./test/e2e" ARGOCD_E2E_RECORD=${ARGOCD_E2E_RECORD} ARGOCD_CONFIG_DIR=$(HOME)/.config/argocd-e2e ARGOCD_GPG_ENABLED=true NO_PROXY=* ./hack/test.sh -timeout $(ARGOCD_E2E_TEST_TIMEOUT) -v -args -test.gocoverdir="$(PWD)/test-results" DIST_DIR=${DIST_DIR} RERUN_FAILS=5 PACKAGES="./test/e2e" ARGOCD_E2E_RECORD=${ARGOCD_E2E_RECORD} ARGOCD_GPG_ENABLED=true NO_PROXY=* ./hack/test.sh -timeout $(ARGOCD_E2E_TEST_TIMEOUT) -v -args -test.gocoverdir="$(PWD)/test-results"
# Spawns a shell in the test server container for debugging purposes # Spawns a shell in the test server container for debugging purposes
debug-test-server: test-tools-image debug-test-server: test-tools-image
@@ -498,8 +490,6 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
ARGOCD_APPLICATIONSET_CONTROLLER_TOKENREF_STRICT_MODE=true \ ARGOCD_APPLICATIONSET_CONTROLLER_TOKENREF_STRICT_MODE=true \
ARGOCD_APPLICATIONSET_CONTROLLER_ALLOWED_SCM_PROVIDERS=http://127.0.0.1:8341,http://127.0.0.1:8342,http://127.0.0.1:8343,http://127.0.0.1:8344 \ ARGOCD_APPLICATIONSET_CONTROLLER_ALLOWED_SCM_PROVIDERS=http://127.0.0.1:8341,http://127.0.0.1:8342,http://127.0.0.1:8343,http://127.0.0.1:8344 \
ARGOCD_E2E_TEST=true \ ARGOCD_E2E_TEST=true \
ARGOCD_HYDRATOR_ENABLED=true \
ARGOCD_CLUSTER_CACHE_EVENTS_PROCESSING_INTERVAL=1ms \
goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START} goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START}
ls -lrt /tmp/coverage ls -lrt /tmp/coverage
@@ -598,7 +588,6 @@ install-test-tools-local:
./hack/install.sh kustomize ./hack/install.sh kustomize
./hack/install.sh helm ./hack/install.sh helm
./hack/install.sh gotestsum ./hack/install.sh gotestsum
./hack/install.sh oras
# Installs all tools required for running codegen (Linux packages) # Installs all tools required for running codegen (Linux packages)
.PHONY: install-codegen-tools-local .PHONY: install-codegen-tools-local
@@ -609,7 +598,6 @@ install-codegen-tools-local:
.PHONY: install-go-tools-local .PHONY: install-go-tools-local
install-go-tools-local: install-go-tools-local:
./hack/install.sh codegen-go-tools ./hack/install.sh codegen-go-tools
./hack/install.sh lint-tools
.PHONY: dep-ui .PHONY: dep-ui
dep-ui: test-tools-image dep-ui: test-tools-image
@@ -690,6 +678,7 @@ help:
@echo 'debug:' @echo 'debug:'
@echo ' list -- list all make targets' @echo ' list -- list all make targets'
@echo ' install-tools-local -- install all the tools below' @echo ' install-tools-local -- install all the tools below'
@echo ' install-lint-tools(-local)'
@echo @echo
@echo 'codegen:' @echo 'codegen:'
@echo ' codegen(-local) -- if using -local, run the following targets first' @echo ' codegen(-local) -- if using -local, run the following targets first'

View File

@@ -1,6 +1,6 @@
controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/app-controller} HOSTNAME=testappcontroller-1 FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --commit-server localhost:${ARGOCD_E2E_COMMITSERVER_PORT:-8086} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''} --server-side-diff-enabled=${ARGOCD_APPLICATION_CONTROLLER_SERVER_SIDE_DIFF:-'false'} --hydrator-enabled=${ARGOCD_HYDRATOR_ENABLED:='false'}" controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/app-controller} HOSTNAME=testappcontroller-1 FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-application-controller $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''} --server-side-diff-enabled=${ARGOCD_APPLICATION_CONTROLLER_SERVER_SIDE_DIFF:-'false'}"
api-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/api-server} FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-server $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''} --hydrator-enabled=${ARGOCD_HYDRATOR_ENABLED:='false'}" api-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/api-server} FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-server $COMMAND --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} --otlp-address=${ARGOCD_OTLP_ADDRESS} --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''}"
dex: sh -c "ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/v3/cmd gendexcfg -o `pwd`/dist/dex.yaml && (test -f dist/dex.yaml || { echo 'Failed to generate dex configuration'; exit 1; }) && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:$(grep "image: ghcr.io/dexidp/dex" manifests/base/dex/argocd-dex-server-deployment.yaml | cut -d':' -f3) dex serve /dex.yaml" dex: sh -c "ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/v2/cmd gendexcfg -o `pwd`/dist/dex.yaml && (test -f dist/dex.yaml || { echo 'Failed to generate dex configuration'; exit 1; }) && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:$(grep "image: ghcr.io/dexidp/dex" manifests/base/dex/argocd-dex-server-deployment.yaml | cut -d':' -f3) dex serve /dex.yaml"
redis: hack/start-redis-with-password.sh redis: hack/start-redis-with-password.sh
repo-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/repo-server} FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-repo-server ARGOCD_GPG_ENABLED=${ARGOCD_GPG_ENABLED:-false} $COMMAND --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --otlp-address=${ARGOCD_OTLP_ADDRESS}" repo-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/repo-server} FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-repo-server ARGOCD_GPG_ENABLED=${ARGOCD_GPG_ENABLED:-false} $COMMAND --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --otlp-address=${ARGOCD_OTLP_ADDRESS}"
cmp-server: [ "$ARGOCD_E2E_TEST" = 'true' ] && exit 0 || [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_BINARY_NAME=argocd-cmp-server ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} $COMMAND --config-dir-path ./test/cmp --loglevel debug --otlp-address=${ARGOCD_OTLP_ADDRESS}" cmp-server: [ "$ARGOCD_E2E_TEST" = 'true' ] && exit 0 || [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_BINARY_NAME=argocd-cmp-server ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-./test/cmp} $COMMAND --config-dir-path ./test/cmp --loglevel debug --otlp-address=${ARGOCD_OTLP_ADDRESS}"
@@ -8,7 +8,6 @@ commit-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go
ui: sh -c 'cd ui && ${ARGOCD_E2E_YARN_CMD:-yarn} start' ui: sh -c 'cd ui && ${ARGOCD_E2E_YARN_CMD:-yarn} start'
git-server: test/fixture/testrepos/start-git.sh git-server: test/fixture/testrepos/start-git.sh
helm-registry: test/fixture/testrepos/start-helm-registry.sh helm-registry: test/fixture/testrepos/start-helm-registry.sh
oci-registry: test/fixture/testrepos/start-authenticated-helm-registry.sh
dev-mounter: [[ "$ARGOCD_E2E_TEST" != "true" ]] && go run hack/dev-mounter/main.go --configmap argocd-ssh-known-hosts-cm=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} --configmap argocd-tls-certs-cm=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} --configmap argocd-gpg-keys-cm=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} dev-mounter: [[ "$ARGOCD_E2E_TEST" != "true" ]] && go run hack/dev-mounter/main.go --configmap argocd-ssh-known-hosts-cm=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} --configmap argocd-tls-certs-cm=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} --configmap argocd-gpg-keys-cm=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source}
applicationset-controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/applicationset-controller} FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-applicationset-controller $COMMAND --loglevel debug --metrics-addr localhost:12345 --probe-addr localhost:12346 --argocd-repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}" applicationset-controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/applicationset-controller} FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-applicationset-controller $COMMAND --loglevel debug --metrics-addr localhost:12345 --probe-addr localhost:12346 --argocd-repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
notification: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/notification} FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_BINARY_NAME=argocd-notifications $COMMAND --loglevel debug --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''} --self-service-notification-enabled=${ARGOCD_NOTIFICATION_CONTROLLER_SELF_SERVICE_NOTIFICATION_ENABLED:-'false'}" notification: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/notification} FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_BINARY_NAME=argocd-notifications $COMMAND --loglevel debug --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''} --self-service-notification-enabled=${ARGOCD_NOTIFICATION_CONTROLLER_SELF_SERVICE_NOTIFICATION_ENABLED:-'false'}"

View File

@@ -3,9 +3,9 @@ header:
expiration-date: '2024-10-31T00:00:00.000Z' # One year from initial release. expiration-date: '2024-10-31T00:00:00.000Z' # One year from initial release.
last-updated: '2023-10-27' last-updated: '2023-10-27'
last-reviewed: '2023-10-27' last-reviewed: '2023-10-27'
commit-hash: 226a670fe6b3c6769ff6d18e6839298a58e4577d commit-hash: 74a367d10e7110209610ba3ec225539ebe5f7522
project-url: https://github.com/argoproj/argo-cd project-url: https://github.com/argoproj/argo-cd
project-release: v3.1.0 project-release: v2.14.0
changelog: https://github.com/argoproj/argo-cd/releases changelog: https://github.com/argoproj/argo-cd/releases
license: https://github.com/argoproj/argo-cd/blob/master/LICENSE license: https://github.com/argoproj/argo-cd/blob/master/LICENSE
project-lifecycle: project-lifecycle:

282
Tiltfile
View File

@@ -1,282 +0,0 @@
load('ext://restart_process', 'docker_build_with_restart')
load('ext://uibutton', 'cmd_button', 'location')
# add ui button in web ui to run make codegen-local (top nav)
cmd_button(
'make codegen-local',
argv=['sh', '-c', 'make codegen-local'],
location=location.NAV,
icon_name='terminal',
text='make codegen-local',
)
# add ui button in web ui to run make codegen-local (top nav)
cmd_button(
'make cli-local',
argv=['sh', '-c', 'make cli-local'],
location=location.NAV,
icon_name='terminal',
text='make cli-local',
)
# detect cluster architecture for build
cluster_version = decode_yaml(local('kubectl version -o yaml'))
platform = cluster_version['serverVersion']['platform']
arch = platform.split('/')[1]
# build the argocd binary on code changes
code_deps = [
'applicationset',
'cmd',
'cmpserver',
'commitserver',
'common',
'controller',
'notification-controller',
'pkg',
'reposerver',
'server',
'util',
'go.mod',
'go.sum',
]
local_resource(
'build',
'CGO_ENABLED=0 GOOS=linux GOARCH=' + arch + ' go build -gcflags="all=-N -l" -mod=readonly -o .tilt-bin/argocd_linux cmd/main.go',
deps = code_deps,
allow_parallel=True,
)
# deploy the argocd manifests
k8s_yaml(kustomize('manifests/dev-tilt'))
# build dev image
docker_build_with_restart(
'argocd',
context='.',
dockerfile='Dockerfile.tilt',
entrypoint=[
"/usr/bin/tini",
"-s",
"--",
"dlv",
"exec",
"--continue",
"--accept-multiclient",
"--headless",
"--listen=:2345",
"--api-version=2"
],
platform=platform,
live_update=[
sync('.tilt-bin/argocd_linux_amd64', '/usr/local/bin/argocd'),
],
only=[
'.tilt-bin',
'hack',
'entrypoint.sh',
],
restart_file='/tilt/.restart-proc'
)
# build image for argocd-cli jobs
docker_build(
'argocd-job',
context='.',
dockerfile='Dockerfile.tilt',
platform=platform,
only=[
'.tilt-bin',
'hack',
'entrypoint.sh',
]
)
# track argocd-server resources and port forward
k8s_resource(
workload='argocd-server',
objects=[
'argocd-server:serviceaccount',
'argocd-server:role',
'argocd-server:rolebinding',
'argocd-cm:configmap',
'argocd-cmd-params-cm:configmap',
'argocd-gpg-keys-cm:configmap',
'argocd-rbac-cm:configmap',
'argocd-ssh-known-hosts-cm:configmap',
'argocd-tls-certs-cm:configmap',
'argocd-secret:secret',
'argocd-server-network-policy:networkpolicy',
'argocd-server:clusterrolebinding',
'argocd-server:clusterrole',
],
port_forwards=[
'8080:8080',
'9345:2345',
'8083:8083'
],
)
# track crds
k8s_resource(
new_name='cluster-resources',
objects=[
'applications.argoproj.io:customresourcedefinition',
'applicationsets.argoproj.io:customresourcedefinition',
'appprojects.argoproj.io:customresourcedefinition',
'argocd:namespace'
]
)
# track argocd-repo-server resources and port forward
k8s_resource(
workload='argocd-repo-server',
objects=[
'argocd-repo-server:serviceaccount',
'argocd-repo-server-network-policy:networkpolicy',
],
port_forwards=[
'8081:8081',
'9346:2345',
'8084:8084'
],
)
# track argocd-redis resources and port forward
k8s_resource(
workload='argocd-redis',
objects=[
'argocd-redis:serviceaccount',
'argocd-redis:role',
'argocd-redis:rolebinding',
'argocd-redis-network-policy:networkpolicy',
],
port_forwards=[
'6379:6379',
],
)
# track argocd-applicationset-controller resources
k8s_resource(
workload='argocd-applicationset-controller',
objects=[
'argocd-applicationset-controller:serviceaccount',
'argocd-applicationset-controller-network-policy:networkpolicy',
'argocd-applicationset-controller:role',
'argocd-applicationset-controller:rolebinding',
'argocd-applicationset-controller:clusterrolebinding',
'argocd-applicationset-controller:clusterrole',
],
port_forwards=[
'9347:2345',
'8085:8080',
'7000:7000'
],
)
# track argocd-application-controller resources
k8s_resource(
workload='argocd-application-controller',
objects=[
'argocd-application-controller:serviceaccount',
'argocd-application-controller-network-policy:networkpolicy',
'argocd-application-controller:role',
'argocd-application-controller:rolebinding',
'argocd-application-controller:clusterrolebinding',
'argocd-application-controller:clusterrole',
],
port_forwards=[
'9348:2345',
'8086:8082',
],
)
# track argocd-notifications-controller resources
k8s_resource(
workload='argocd-notifications-controller',
objects=[
'argocd-notifications-controller:serviceaccount',
'argocd-notifications-controller-network-policy:networkpolicy',
'argocd-notifications-controller:role',
'argocd-notifications-controller:rolebinding',
'argocd-notifications-cm:configmap',
'argocd-notifications-secret:secret',
],
port_forwards=[
'9349:2345',
'8087:9001',
],
)
# track argocd-dex-server resources
k8s_resource(
workload='argocd-dex-server',
objects=[
'argocd-dex-server:serviceaccount',
'argocd-dex-server-network-policy:networkpolicy',
'argocd-dex-server:role',
'argocd-dex-server:rolebinding',
],
)
# track argocd-commit-server resources
k8s_resource(
workload='argocd-commit-server',
objects=[
'argocd-commit-server:serviceaccount',
'argocd-commit-server-network-policy:networkpolicy',
],
port_forwards=[
'9350:2345',
'8088:8087',
'8089:8086',
],
)
# docker for ui
docker_build(
'argocd-ui',
context='.',
dockerfile='Dockerfile.ui.tilt',
entrypoint=['sh', '-c', 'cd /app/ui && yarn start'],
only=['ui'],
live_update=[
sync('ui', '/app/ui'),
run('sh -c "cd /app/ui && yarn install"', trigger=['/app/ui/package.json', '/app/ui/yarn.lock']),
],
)
# track argocd-ui resources and port forward
k8s_resource(
workload='argocd-ui',
port_forwards=[
'4000:4000',
],
)
# linting
local_resource(
'lint',
'make lint-local',
deps = code_deps,
allow_parallel=True,
)
local_resource(
'lint-ui',
'make lint-ui-local',
deps = [
'ui',
],
allow_parallel=True,
)
local_resource(
'vendor',
'go mod vendor',
deps = [
'go.mod',
'go.sum',
],
)

View File

@@ -30,8 +30,7 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Ant Group](https://www.antgroup.com/) 1. [Ant Group](https://www.antgroup.com/)
1. [AppDirect](https://www.appdirect.com) 1. [AppDirect](https://www.appdirect.com)
1. [Arctiq Inc.](https://www.arctiq.ca) 1. [Arctiq Inc.](https://www.arctiq.ca)
1. [Artemis Health by Nomi Health](https://www.artemishealth.com/) 2. [Arturia](https://www.arturia.com)
1. [Arturia](https://www.arturia.com)
1. [ARZ Allgemeines Rechenzentrum GmbH](https://www.arz.at/) 1. [ARZ Allgemeines Rechenzentrum GmbH](https://www.arz.at/)
1. [Augury](https://www.augury.com/) 1. [Augury](https://www.augury.com/)
1. [Autodesk](https://www.autodesk.com) 1. [Autodesk](https://www.autodesk.com)
@@ -46,7 +45,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Bedag Informatik AG](https://www.bedag.ch/) 1. [Bedag Informatik AG](https://www.bedag.ch/)
1. [Beleza Na Web](https://www.belezanaweb.com.br/) 1. [Beleza Na Web](https://www.belezanaweb.com.br/)
1. [Believable Bots](https://believablebots.io) 1. [Believable Bots](https://believablebots.io)
1. [Bayer AG](https://bayer.com)
1. [BigPanda](https://bigpanda.io) 1. [BigPanda](https://bigpanda.io)
1. [BioBox Analytics](https://biobox.io) 1. [BioBox Analytics](https://biobox.io)
1. [BMW Group](https://www.bmwgroup.com/) 1. [BMW Group](https://www.bmwgroup.com/)
@@ -69,7 +67,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Chainnodes](https://chainnodes.org) 1. [Chainnodes](https://chainnodes.org)
1. [Chargetrip](https://chargetrip.com) 1. [Chargetrip](https://chargetrip.com)
1. [Chime](https://www.chime.com) 1. [Chime](https://www.chime.com)
1. [Chronicle Labs](https://chroniclelabs.org)
1. [Cisco ET&I](https://eti.cisco.com/) 1. [Cisco ET&I](https://eti.cisco.com/)
1. [Cloud Posse](https://www.cloudposse.com/) 1. [Cloud Posse](https://www.cloudposse.com/)
1. [Cloud Scale](https://cloudscaleinc.com/) 1. [Cloud Scale](https://cloudscaleinc.com/)
@@ -95,15 +92,11 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Daydream](https://daydream.ing) 1. [Daydream](https://daydream.ing)
1. [Deloitte](https://www.deloitte.com/) 1. [Deloitte](https://www.deloitte.com/)
1. [Deutsche Telekom AG](https://telekom.com) 1. [Deutsche Telekom AG](https://telekom.com)
1. [Deutsche Bank AG](https://www.deutsche-bank.de/)
1. [Devopsi - Poland Software/DevOps Consulting](https://devopsi.pl/) 1. [Devopsi - Poland Software/DevOps Consulting](https://devopsi.pl/)
1. [Devtron Labs](https://github.com/devtron-labs/devtron) 1. [Devtron Labs](https://github.com/devtron-labs/devtron)
1. [DigitalEd](https://www.digitaled.com)
1. [DigitalOcean](https://www.digitalocean.com) 1. [DigitalOcean](https://www.digitalocean.com)
1. [Divar](https://divar.ir)
1. [Divistant](https://divistant.com) 1. [Divistant](https://divistant.com)
1. [Dott](https://ridedott.com) 1. [Dott](https://ridedott.com)
1. [Doubble](https://www.doubble.app)
1. [Doximity](https://www.doximity.com/) 1. [Doximity](https://www.doximity.com/)
1. [EDF Renewables](https://www.edf-re.com/) 1. [EDF Renewables](https://www.edf-re.com/)
1. [edX](https://edx.org) 1. [edX](https://edx.org)
@@ -115,7 +108,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Energisme](https://energisme.com/) 1. [Energisme](https://energisme.com/)
1. [enigmo](https://enigmo.co.jp/) 1. [enigmo](https://enigmo.co.jp/)
1. [Envoy](https://envoy.com/) 1. [Envoy](https://envoy.com/)
1. [eSave](https://esave.es/)
1. [Factorial](https://factorialhr.com/) 1. [Factorial](https://factorialhr.com/)
1. [Farfetch](https://www.farfetch.com) 1. [Farfetch](https://www.farfetch.com)
1. [Faro](https://www.faro.com/) 1. [Faro](https://www.faro.com/)
@@ -154,7 +146,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Hazelcast](https://hazelcast.com/) 1. [Hazelcast](https://hazelcast.com/)
1. [Healy](https://www.healyworld.net) 1. [Healy](https://www.healyworld.net)
1. [Helio](https://helio.exchange) 1. [Helio](https://helio.exchange)
1. [hetao101](https://www.hetao101.com/)
1. [Hetki](https://hetki.ai) 1. [Hetki](https://hetki.ai)
1. [hipages](https://hipages.com.au/) 1. [hipages](https://hipages.com.au/)
1. [Hiya](https://hiya.com) 1. [Hiya](https://hiya.com)
@@ -163,7 +154,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [IABAI](https://www.iab.ai) 1. [IABAI](https://www.iab.ai)
1. [IBM](https://www.ibm.com/) 1. [IBM](https://www.ibm.com/)
1. [Ibotta](https://home.ibotta.com) 1. [Ibotta](https://home.ibotta.com)
1. [Icelandair](https://www.icelandair.com)
1. [IFS](https://www.ifs.com) 1. [IFS](https://www.ifs.com)
1. [IITS-Consulting](https://iits-consulting.de) 1. [IITS-Consulting](https://iits-consulting.de)
1. [IllumiDesk](https://www.illumidesk.com) 1. [IllumiDesk](https://www.illumidesk.com)
@@ -201,10 +191,10 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Lian Chu Securities](https://lczq.com) 1. [Lian Chu Securities](https://lczq.com)
1. [Liatrio](https://www.liatrio.com) 1. [Liatrio](https://www.liatrio.com)
1. [Lightricks](https://www.lightricks.com/) 1. [Lightricks](https://www.lightricks.com/)
1. [LINE](https://linecorp.com/en/)
1. [Loom](https://www.loom.com/) 1. [Loom](https://www.loom.com/)
1. [Lucid Motors](https://www.lucidmotors.com/) 1. [Lucid Motors](https://www.lucidmotors.com/)
1. [Lytt](https://www.lytt.co/) 1. [Lytt](https://www.lytt.co/)
1. [LY Corporation](https://www.lycorp.co.jp/en/)
1. [Magic Leap](https://www.magicleap.com/) 1. [Magic Leap](https://www.magicleap.com/)
1. [Majid Al Futtaim](https://www.majidalfuttaim.com/) 1. [Majid Al Futtaim](https://www.majidalfuttaim.com/)
1. [Major League Baseball](https://mlb.com) 1. [Major League Baseball](https://mlb.com)
@@ -267,7 +257,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Patreon](https://www.patreon.com/) 1. [Patreon](https://www.patreon.com/)
1. [PayIt](https://payitgov.com/) 1. [PayIt](https://payitgov.com/)
1. [PayPay](https://paypay.ne.jp/) 1. [PayPay](https://paypay.ne.jp/)
1. [Paystack](https://paystack.com/)
1. [Peloton Interactive](https://www.onepeloton.com/) 1. [Peloton Interactive](https://www.onepeloton.com/)
1. [Percona](https://percona.com/) 1. [Percona](https://percona.com/)
1. [PGS](https://www.pgs.com) 1. [PGS](https://www.pgs.com)
@@ -279,7 +268,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [PITS Globale Datenrettungsdienste](https://www.pitsdatenrettung.de/) 1. [PITS Globale Datenrettungsdienste](https://www.pitsdatenrettung.de/)
1. [Platform9 Systems](https://platform9.com/) 1. [Platform9 Systems](https://platform9.com/)
1. [Polarpoint.io](https://polarpoint.io) 1. [Polarpoint.io](https://polarpoint.io)
1. [Pollinate](https://www.pollinate.global)
1. [PostFinance](https://github.com/postfinance) 1. [PostFinance](https://github.com/postfinance)
1. [Preferred Networks](https://preferred.jp/en/) 1. [Preferred Networks](https://preferred.jp/en/)
1. [Previder BV](https://previder.nl) 1. [Previder BV](https://previder.nl)
@@ -314,7 +302,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Saloodo! GmbH](https://www.saloodo.com) 1. [Saloodo! GmbH](https://www.saloodo.com)
1. [Sap Labs](http://sap.com) 1. [Sap Labs](http://sap.com)
1. [Sauce Labs](https://saucelabs.com/) 1. [Sauce Labs](https://saucelabs.com/)
1. [Schneider Electric](https://www.se.com)
1. [Schwarz IT](https://jobs.schwarz/it-mission) 1. [Schwarz IT](https://jobs.schwarz/it-mission)
1. [SCRM Lidl International Hub](https://scrm.lidl) 1. [SCRM Lidl International Hub](https://scrm.lidl)
1. [SEEK](https://seek.com.au) 1. [SEEK](https://seek.com.au)
@@ -324,7 +311,7 @@ Currently, the following organizations are **officially** using Argo CD:
1. [SI Analytics](https://si-analytics.ai) 1. [SI Analytics](https://si-analytics.ai)
1. [Sidewalk Entertainment](https://sidewalkplay.com/) 1. [Sidewalk Entertainment](https://sidewalkplay.com/)
1. [Skit](https://skit.ai/) 1. [Skit](https://skit.ai/)
1. [Skribble](https://skribble.com) 1. [Skribble](https://skribble.com)
1. [Skyscanner](https://www.skyscanner.net/) 1. [Skyscanner](https://www.skyscanner.net/)
1. [Smart Pension](https://www.smartpension.co.uk/) 1. [Smart Pension](https://www.smartpension.co.uk/)
1. [Smilee.io](https://smilee.io) 1. [Smilee.io](https://smilee.io)
@@ -345,13 +332,10 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Sumo Logic](https://sumologic.com/) 1. [Sumo Logic](https://sumologic.com/)
1. [Sutpc](http://www.sutpc.com/) 1. [Sutpc](http://www.sutpc.com/)
1. [Swiss Post](https://github.com/swisspost) 1. [Swiss Post](https://github.com/swisspost)
1. [Swissblock Technologies](https://swissblock.net/)
1. [Swisscom](https://www.swisscom.ch) 1. [Swisscom](https://www.swisscom.ch)
1. [Swissquote](https://github.com/swissquote) 1. [Swissquote](https://github.com/swissquote)
1. [Syncier](https://syncier.com/) 1. [Syncier](https://syncier.com/)
1. [Synergy](https://synergy.net.au)
1. [Syself](https://syself.com) 1. [Syself](https://syself.com)
1. [T-ROC Global](https://trocglobal.com/)
1. [TableCheck](https://tablecheck.com/) 1. [TableCheck](https://tablecheck.com/)
1. [Tailor Brands](https://www.tailorbrands.com) 1. [Tailor Brands](https://www.tailorbrands.com)
1. [Tamkeen Technologies](https://tamkeentech.sa/) 1. [Tamkeen Technologies](https://tamkeentech.sa/)
@@ -389,11 +373,9 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Vinted](https://vinted.com/) 1. [Vinted](https://vinted.com/)
1. [Virtuo](https://www.govirtuo.com/) 1. [Virtuo](https://www.govirtuo.com/)
1. [VISITS Technologies](https://visits.world/en) 1. [VISITS Technologies](https://visits.world/en)
1. [Viya](https://viya.me)
1. [Volvo Cars](https://www.volvocars.com/) 1. [Volvo Cars](https://www.volvocars.com/)
1. [Voyager Digital](https://www.investvoyager.com/) 1. [Voyager Digital](https://www.investvoyager.com/)
1. [VSHN - The DevOps Company](https://vshn.ch/) 1. [VSHN - The DevOps Company](https://vshn.ch/)
1. [Wakacje.pl](https://www.wakacje.pl/)
1. [Walkbase](https://www.walkbase.com/) 1. [Walkbase](https://www.walkbase.com/)
1. [Webstores](https://www.webstores.nl) 1. [Webstores](https://www.webstores.nl)
1. [Wehkamp](https://www.wehkamp.nl/) 1. [Wehkamp](https://www.wehkamp.nl/)
@@ -405,12 +387,10 @@ Currently, the following organizations are **officially** using Argo CD:
1. [WooliesX](https://wooliesx.com.au/) 1. [WooliesX](https://wooliesx.com.au/)
1. [Woolworths Group](https://www.woolworthsgroup.com.au/) 1. [Woolworths Group](https://www.woolworthsgroup.com.au/)
1. [WSpot](https://www.wspot.com.br/) 1. [WSpot](https://www.wspot.com.br/)
1. [X3M ads](https://x3mads.com)
1. [Yieldlab](https://www.yieldlab.de/) 1. [Yieldlab](https://www.yieldlab.de/)
1. [Youverify](https://youverify.co/) 1. [Youverify](https://youverify.co/)
1. [Yubo](https://www.yubo.live/) 1. [Yubo](https://www.yubo.live/)
1. [Yuno](https://y.uno/)
1. [ZDF](https://www.zdf.de/) 1. [ZDF](https://www.zdf.de/)
1. [Zimpler](https://www.zimpler.com/) 1. [Zimpler](https://www.zimpler.com/)
1. [ZipRecruiter](https://www.ziprecruiter.com/) 1. [ZipRecuiter](https://www.ziprecruiter.com/)
1. [ZOZO](https://corp.zozo.com/) 1. [ZOZO](https://corp.zozo.com/)

View File

@@ -1 +1 @@
3.1.0 2.14.0

View File

@@ -28,7 +28,7 @@ import (
"github.com/google/go-cmp/cmp/cmpopts" "github.com/google/go-cmp/cmp/cmpopts"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
@@ -45,20 +45,19 @@ import (
"sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/predicate"
"github.com/argoproj/argo-cd/v3/applicationset/controllers/template" "github.com/argoproj/argo-cd/v2/applicationset/controllers/template"
"github.com/argoproj/argo-cd/v3/applicationset/generators" "github.com/argoproj/argo-cd/v2/applicationset/generators"
"github.com/argoproj/argo-cd/v3/applicationset/metrics" "github.com/argoproj/argo-cd/v2/applicationset/metrics"
"github.com/argoproj/argo-cd/v3/applicationset/status" "github.com/argoproj/argo-cd/v2/applicationset/status"
"github.com/argoproj/argo-cd/v3/applicationset/utils" "github.com/argoproj/argo-cd/v2/applicationset/utils"
"github.com/argoproj/argo-cd/v3/common" "github.com/argoproj/argo-cd/v2/common"
applog "github.com/argoproj/argo-cd/v3/util/app/log" "github.com/argoproj/argo-cd/v2/util/db"
"github.com/argoproj/argo-cd/v3/util/db"
argov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
argoutil "github.com/argoproj/argo-cd/v3/util/argo" argoutil "github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/argo-cd/v3/util/argo/normalizers" "github.com/argoproj/argo-cd/v2/util/argo/normalizers"
"github.com/argoproj/argo-cd/v3/pkg/apis/application" "github.com/argoproj/argo-cd/v2/pkg/apis/application"
) )
const ( const (
@@ -128,8 +127,8 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
}() }()
// Do not attempt to further reconcile the ApplicationSet if it is being deleted. // Do not attempt to further reconcile the ApplicationSet if it is being deleted.
if applicationSetInfo.DeletionTimestamp != nil { if applicationSetInfo.ObjectMeta.DeletionTimestamp != nil {
appsetName := applicationSetInfo.Name appsetName := applicationSetInfo.ObjectMeta.Name
logCtx.Debugf("DeletionTimestamp is set on %s", appsetName) logCtx.Debugf("DeletionTimestamp is set on %s", appsetName)
deleteAllowed := utils.DefaultPolicy(applicationSetInfo.Spec.SyncPolicy, r.Policy, r.EnablePolicyOverride).AllowDelete() deleteAllowed := utils.DefaultPolicy(applicationSetInfo.Spec.SyncPolicy, r.Policy, r.EnablePolicyOverride).AllowDelete()
if !deleteAllowed { if !deleteAllowed {
@@ -156,7 +155,6 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
// desiredApplications is the main list of all expected Applications from all generators in this appset. // desiredApplications is the main list of all expected Applications from all generators in this appset.
desiredApplications, applicationSetReason, err := template.GenerateApplications(logCtx, applicationSetInfo, r.Generators, r.Renderer, r.Client) desiredApplications, applicationSetReason, err := template.GenerateApplications(logCtx, applicationSetInfo, r.Generators, r.Renderer, r.Client)
if err != nil { if err != nil {
logCtx.Errorf("unable to generate applications: %v", err)
_ = r.setApplicationSetStatusCondition(ctx, _ = r.setApplicationSetStatusCondition(ctx,
&applicationSetInfo, &applicationSetInfo,
argov1alpha1.ApplicationSetCondition{ argov1alpha1.ApplicationSetCondition{
@@ -166,8 +164,7 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
Status: argov1alpha1.ApplicationSetConditionStatusTrue, Status: argov1alpha1.ApplicationSetConditionStatusTrue,
}, parametersGenerated, }, parametersGenerated,
) )
// In order for the controller SDK to respect RequeueAfter, the error must be nil return ctrl.Result{RequeueAfter: ReconcileRequeueOnValidationError}, err
return ctrl.Result{RequeueAfter: ReconcileRequeueOnValidationError}, nil
} }
parametersGenerated = true parametersGenerated = true
@@ -211,16 +208,16 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
appSyncMap := map[string]bool{} appSyncMap := map[string]bool{}
if r.EnableProgressiveSyncs { if r.EnableProgressiveSyncs {
if !isRollingSyncStrategy(&applicationSetInfo) && len(applicationSetInfo.Status.ApplicationStatus) > 0 { if applicationSetInfo.Spec.Strategy == nil && len(applicationSetInfo.Status.ApplicationStatus) > 0 {
// If an appset was previously syncing with a `RollingSync` strategy but it has switched to the default strategy, clean up the progressive sync application statuses // If appset used progressive sync but stopped, clean up the progressive sync application statuses
logCtx.Infof("Removing %v unnecessary AppStatus entries from ApplicationSet %v", len(applicationSetInfo.Status.ApplicationStatus), applicationSetInfo.Name) logCtx.Infof("Removing %v unnecessary AppStatus entries from ApplicationSet %v", len(applicationSetInfo.Status.ApplicationStatus), applicationSetInfo.Name)
err := r.setAppSetApplicationStatus(ctx, logCtx, &applicationSetInfo, []argov1alpha1.ApplicationSetApplicationStatus{}) err := r.setAppSetApplicationStatus(ctx, logCtx, &applicationSetInfo, []argov1alpha1.ApplicationSetApplicationStatus{})
if err != nil { if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to clear previous AppSet application statuses for %v: %w", applicationSetInfo.Name, err) return ctrl.Result{}, fmt.Errorf("failed to clear previous AppSet application statuses for %v: %w", applicationSetInfo.Name, err)
} }
} else if isRollingSyncStrategy(&applicationSetInfo) { } else if applicationSetInfo.Spec.Strategy != nil {
// The appset uses progressive sync with `RollingSync` strategy // appset uses progressive sync
for _, app := range currentApplications { for _, app := range currentApplications {
appMap[app.Name] = app appMap[app.Name] = app
} }
@@ -315,7 +312,7 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
if applicationSetInfo.RefreshRequired() { if applicationSetInfo.RefreshRequired() {
delete(applicationSetInfo.Annotations, common.AnnotationApplicationSetRefresh) delete(applicationSetInfo.Annotations, common.AnnotationApplicationSetRefresh)
err := r.Update(ctx, &applicationSetInfo) err := r.Client.Update(ctx, &applicationSetInfo)
if err != nil { if err != nil {
logCtx.Warnf("error occurred while updating ApplicationSet: %v", err) logCtx.Warnf("error occurred while updating ApplicationSet: %v", err)
_ = r.setApplicationSetStatusCondition(ctx, _ = r.setApplicationSetStatusCondition(ctx,
@@ -468,7 +465,7 @@ func (r *ApplicationSetReconciler) setApplicationSetStatusCondition(ctx context.
updatedAppset.DeepCopyInto(applicationSet) updatedAppset.DeepCopyInto(applicationSet)
return nil return nil
}) })
if err != nil && !apierrors.IsNotFound(err) { if err != nil && !apierr.IsNotFound(err) {
return fmt.Errorf("unable to set application set condition: %w", err) return fmt.Errorf("unable to set application set condition: %w", err)
} }
} }
@@ -482,23 +479,24 @@ func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Con
errorsByIndex := map[int]error{} errorsByIndex := map[int]error{}
namesSet := map[string]bool{} namesSet := map[string]bool{}
for i, app := range desiredApplications { for i, app := range desiredApplications {
if namesSet[app.Name] { if !namesSet[app.Name] {
namesSet[app.Name] = true
} else {
errorsByIndex[i] = fmt.Errorf("ApplicationSet %s contains applications with duplicate name: %s", applicationSetInfo.Name, app.Name) errorsByIndex[i] = fmt.Errorf("ApplicationSet %s contains applications with duplicate name: %s", applicationSetInfo.Name, app.Name)
continue continue
} }
namesSet[app.Name] = true
appProject := &argov1alpha1.AppProject{} appProject := &argov1alpha1.AppProject{}
err := r.Get(ctx, types.NamespacedName{Name: app.Spec.Project, Namespace: r.ArgoCDNamespace}, appProject) err := r.Client.Get(ctx, types.NamespacedName{Name: app.Spec.Project, Namespace: r.ArgoCDNamespace}, appProject)
if err != nil { if err != nil {
if apierrors.IsNotFound(err) { if apierr.IsNotFound(err) {
errorsByIndex[i] = fmt.Errorf("application references project %s which does not exist", app.Spec.Project) errorsByIndex[i] = fmt.Errorf("application references project %s which does not exist", app.Spec.Project)
continue continue
} }
return nil, err return nil, err
} }
if _, err = argoutil.GetDestinationCluster(ctx, app.Spec.Destination, r.ArgoDB); err != nil { if err := utils.ValidateDestination(ctx, &app.Spec.Destination, r.KubeClientset, r.ArgoCDNamespace); err != nil {
errorsByIndex[i] = fmt.Errorf("application destination spec is invalid: %s", err.Error()) errorsByIndex[i] = fmt.Errorf("application destination spec is invalid: %s", err.Error())
continue continue
} }
@@ -527,9 +525,11 @@ func (r *ApplicationSetReconciler) getMinRequeueAfter(applicationSetInfo *argov1
} }
func ignoreNotAllowedNamespaces(namespaces []string) predicate.Predicate { func ignoreNotAllowedNamespaces(namespaces []string) predicate.Predicate {
return predicate.NewPredicateFuncs(func(object client.Object) bool { return predicate.Funcs{
return utils.IsNamespaceAllowed(namespaces, object.GetNamespace()) CreateFunc: func(e event.CreateEvent) bool {
}) return utils.IsNamespaceAllowed(namespaces, e.Object.GetNamespace())
},
}
} }
func appControllerIndexer(rawObj client.Object) []string { func appControllerIndexer(rawObj client.Object) []string {
@@ -553,13 +553,12 @@ func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager, enableProg
return fmt.Errorf("error setting up with manager: %w", err) return fmt.Errorf("error setting up with manager: %w", err)
} }
appOwnsHandler := getApplicationOwnsHandler(enableProgressiveSyncs) ownsHandler := getOwnsHandlerPredicates(enableProgressiveSyncs)
appSetOwnsHandler := getApplicationSetOwnsHandler(enableProgressiveSyncs)
return ctrl.NewControllerManagedBy(mgr).WithOptions(controller.Options{ return ctrl.NewControllerManagedBy(mgr).WithOptions(controller.Options{
MaxConcurrentReconciles: maxConcurrentReconciliations, MaxConcurrentReconciles: maxConcurrentReconciliations,
}).For(&argov1alpha1.ApplicationSet{}, builder.WithPredicates(appSetOwnsHandler)). }).For(&argov1alpha1.ApplicationSet{}).
Owns(&argov1alpha1.Application{}, builder.WithPredicates(appOwnsHandler)). Owns(&argov1alpha1.Application{}, builder.WithPredicates(ownsHandler)).
WithEventFilter(ignoreNotAllowedNamespaces(r.ApplicationSetNamespaces)). WithEventFilter(ignoreNotAllowedNamespaces(r.ApplicationSetNamespaces)).
Watches( Watches(
&corev1.Secret{}, &corev1.Secret{},
@@ -567,6 +566,7 @@ func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager, enableProg
Client: mgr.GetClient(), Client: mgr.GetClient(),
Log: log.WithField("type", "createSecretEventHandler"), Log: log.WithField("type", "createSecretEventHandler"),
}). }).
// TODO: also watch Applications and respond on changes if we own them.
Complete(r) Complete(r)
} }
@@ -578,7 +578,7 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
var firstError error var firstError error
// Creates or updates the application in appList // Creates or updates the application in appList
for _, generatedApp := range desiredApplications { for _, generatedApp := range desiredApplications {
appLog := logCtx.WithFields(applog.GetAppLogFields(&generatedApp)) appLog := logCtx.WithFields(log.Fields{"app": generatedApp.QualifiedName()})
// Normalize to avoid fighting with the application controller. // Normalize to avoid fighting with the application controller.
generatedApp.Spec = *argoutil.NormalizeApplicationSpec(&generatedApp.Spec) generatedApp.Spec = *argoutil.NormalizeApplicationSpec(&generatedApp.Spec)
@@ -625,7 +625,7 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
preservedAnnotations = append(preservedAnnotations, defaultPreservedAnnotations...) preservedAnnotations = append(preservedAnnotations, defaultPreservedAnnotations...)
for _, key := range preservedAnnotations { for _, key := range preservedAnnotations {
if state, exists := found.Annotations[key]; exists { if state, exists := found.ObjectMeta.Annotations[key]; exists {
if generatedApp.Annotations == nil { if generatedApp.Annotations == nil {
generatedApp.Annotations = map[string]string{} generatedApp.Annotations = map[string]string{}
} }
@@ -634,7 +634,7 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
} }
for _, key := range preservedLabels { for _, key := range preservedLabels {
if state, exists := found.Labels[key]; exists { if state, exists := found.ObjectMeta.Labels[key]; exists {
if generatedApp.Labels == nil { if generatedApp.Labels == nil {
generatedApp.Labels = map[string]string{} generatedApp.Labels = map[string]string{}
} }
@@ -644,7 +644,7 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
// Preserve post-delete finalizers: // Preserve post-delete finalizers:
// https://github.com/argoproj/argo-cd/issues/17181 // https://github.com/argoproj/argo-cd/issues/17181
for _, finalizer := range found.Finalizers { for _, finalizer := range found.ObjectMeta.Finalizers {
if strings.HasPrefix(finalizer, argov1alpha1.PostDeleteFinalizerName) { if strings.HasPrefix(finalizer, argov1alpha1.PostDeleteFinalizerName) {
if generatedApp.Finalizers == nil { if generatedApp.Finalizers == nil {
generatedApp.Finalizers = []string{} generatedApp.Finalizers = []string{}
@@ -653,10 +653,10 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
} }
} }
found.Annotations = generatedApp.Annotations found.ObjectMeta.Annotations = generatedApp.Annotations
found.Finalizers = generatedApp.Finalizers found.ObjectMeta.Finalizers = generatedApp.Finalizers
found.Labels = generatedApp.Labels found.ObjectMeta.Labels = generatedApp.Labels
return controllerutil.SetControllerReference(&applicationSet, found, r.Scheme) return controllerutil.SetControllerReference(&applicationSet, found, r.Scheme)
}) })
@@ -710,7 +710,7 @@ func (r *ApplicationSetReconciler) createInCluster(ctx context.Context, logCtx *
func (r *ApplicationSetReconciler) getCurrentApplications(ctx context.Context, applicationSet argov1alpha1.ApplicationSet) ([]argov1alpha1.Application, error) { func (r *ApplicationSetReconciler) getCurrentApplications(ctx context.Context, applicationSet argov1alpha1.ApplicationSet) ([]argov1alpha1.Application, error) {
var current argov1alpha1.ApplicationList var current argov1alpha1.ApplicationList
err := r.List(ctx, &current, client.MatchingFields{".metadata.controller": applicationSet.Name}, client.InNamespace(applicationSet.Namespace)) err := r.Client.List(ctx, &current, client.MatchingFields{".metadata.controller": applicationSet.Name}, client.InNamespace(applicationSet.Namespace))
if err != nil { if err != nil {
return nil, fmt.Errorf("error retrieving applications: %w", err) return nil, fmt.Errorf("error retrieving applications: %w", err)
} }
@@ -721,6 +721,9 @@ func (r *ApplicationSetReconciler) getCurrentApplications(ctx context.Context, a
// deleteInCluster will delete Applications that are currently on the cluster, but not in appList. // deleteInCluster will delete Applications that are currently on the cluster, but not in appList.
// The function must be called after all generators had been called and generated applications // The function must be called after all generators had been called and generated applications
func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, logCtx *log.Entry, applicationSet argov1alpha1.ApplicationSet, desiredApplications []argov1alpha1.Application) error { func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, logCtx *log.Entry, applicationSet argov1alpha1.ApplicationSet, desiredApplications []argov1alpha1.Application) error {
// settingsMgr := settings.NewSettingsManager(context.TODO(), r.KubeClientset, applicationSet.Namespace)
// argoDB := db.NewDB(applicationSet.Namespace, settingsMgr, r.KubeClientset)
// clusterList, err := argoDB.ListClusters(ctx)
clusterList, err := utils.ListClusters(ctx, r.KubeClientset, r.ArgoCDNamespace) clusterList, err := utils.ListClusters(ctx, r.KubeClientset, r.ArgoCDNamespace)
if err != nil { if err != nil {
return fmt.Errorf("error listing clusters: %w", err) return fmt.Errorf("error listing clusters: %w", err)
@@ -741,7 +744,7 @@ func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, logCtx *
// Delete apps that are not in m[string]bool // Delete apps that are not in m[string]bool
var firstError error var firstError error
for _, app := range current { for _, app := range current {
logCtx = logCtx.WithFields(applog.GetAppLogFields(&app)) logCtx = logCtx.WithField("app", app.QualifiedName())
_, exists := m[app.Name] _, exists := m[app.Name]
if !exists { if !exists {
@@ -755,7 +758,7 @@ func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, logCtx *
continue continue
} }
err = r.Delete(ctx, &app) err = r.Client.Delete(ctx, &app)
if err != nil { if err != nil {
logCtx.WithError(err).Error("failed to delete Application") logCtx.WithError(err).Error("failed to delete Application")
if firstError != nil { if firstError != nil {
@@ -771,7 +774,7 @@ func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, logCtx *
} }
// removeFinalizerOnInvalidDestination removes the Argo CD resources finalizer if the application contains an invalid target (eg missing cluster) // removeFinalizerOnInvalidDestination removes the Argo CD resources finalizer if the application contains an invalid target (eg missing cluster)
func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx context.Context, applicationSet argov1alpha1.ApplicationSet, app *argov1alpha1.Application, clusterList []utils.ClusterSpecifier, appLog *log.Entry) error { func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx context.Context, applicationSet argov1alpha1.ApplicationSet, app *argov1alpha1.Application, clusterList *argov1alpha1.ClusterList, appLog *log.Entry) error {
// Only check if the finalizers need to be removed IF there are finalizers to remove // Only check if the finalizers need to be removed IF there are finalizers to remove
if len(app.Finalizers) == 0 { if len(app.Finalizers) == 0 {
return nil return nil
@@ -780,18 +783,21 @@ func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx conte
var validDestination bool var validDestination bool
// Detect if the destination is invalid (name doesn't correspond to a matching cluster) // Detect if the destination is invalid (name doesn't correspond to a matching cluster)
if destCluster, err := argoutil.GetDestinationCluster(ctx, app.Spec.Destination, r.ArgoDB); err != nil { if err := utils.ValidateDestination(ctx, &app.Spec.Destination, r.KubeClientset, r.ArgoCDNamespace); err != nil {
appLog.Warnf("The destination cluster for %s couldn't be found: %v", app.Name, err) appLog.Warnf("The destination cluster for %s couldn't be found: %v", app.Name, err)
validDestination = false validDestination = false
} else { } else {
// Detect if the destination's server field does not match an existing cluster // Detect if the destination's server field does not match an existing cluster
matchingCluster := false matchingCluster := false
for _, cluster := range clusterList { for _, cluster := range clusterList.Items {
if destCluster.Server != cluster.Server { // Server fields must match. Note that ValidateDestination ensures that the server field is set, if applicable.
if app.Spec.Destination.Server != cluster.Server {
continue continue
} }
if destCluster.Name != cluster.Name { // The name must match, if it is not empty
if app.Spec.Destination.Name != "" && cluster.Name != app.Spec.Destination.Name {
continue continue
} }
@@ -824,7 +830,7 @@ func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx conte
if log.IsLevelEnabled(log.DebugLevel) { if log.IsLevelEnabled(log.DebugLevel) {
utils.LogPatch(appLog, patch, updated) utils.LogPatch(appLog, patch, updated)
} }
if err := r.Patch(ctx, updated, patch); err != nil { if err := r.Client.Patch(ctx, updated, patch); err != nil {
return fmt.Errorf("error updating finalizers: %w", err) return fmt.Errorf("error updating finalizers: %w", err)
} }
// Application must have updated list of finalizers // Application must have updated list of finalizers
@@ -846,7 +852,7 @@ func (r *ApplicationSetReconciler) removeOwnerReferencesOnDeleteAppSet(ctx conte
for _, app := range applications { for _, app := range applications {
app.SetOwnerReferences([]metav1.OwnerReference{}) app.SetOwnerReferences([]metav1.OwnerReference{})
err := r.Update(ctx, &app) err := r.Client.Update(ctx, &app)
if err != nil { if err != nil {
return fmt.Errorf("error updating application: %w", err) return fmt.Errorf("error updating application: %w", err)
} }
@@ -976,16 +982,17 @@ func (r *ApplicationSetReconciler) buildAppSyncMap(applicationSet argov1alpha1.A
} }
appStatus := applicationSet.Status.ApplicationStatus[idx] appStatus := applicationSet.Status.ApplicationStatus[idx]
app, ok := appMap[appName]
if !ok { if app, ok := appMap[appName]; ok {
syncEnabled = appSyncEnabledForNextStep(&applicationSet, app, appStatus)
if !syncEnabled {
break
}
} else {
// application name not found in the list of applications managed by this ApplicationSet, maybe because it's being deleted // application name not found in the list of applications managed by this ApplicationSet, maybe because it's being deleted
syncEnabled = false syncEnabled = false
break break
} }
syncEnabled = appSyncEnabledForNextStep(&applicationSet, app, appStatus)
if !syncEnabled {
break
}
} }
} }
@@ -1001,14 +1008,8 @@ func appSyncEnabledForNextStep(appset *argov1alpha1.ApplicationSet, app argov1al
return true 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
}
func progressiveSyncsRollingSyncStrategyEnabled(appset *argov1alpha1.ApplicationSet) bool { func progressiveSyncsRollingSyncStrategyEnabled(appset *argov1alpha1.ApplicationSet) bool {
// ProgressiveSync is enabled if the strategy is set to `RollingSync` + steps slice is not empty return appset.Spec.Strategy != nil && appset.Spec.Strategy.RollingSync != nil && appset.Spec.Strategy.Type == "RollingSync" && len(appset.Spec.Strategy.RollingSync.Steps) > 0
return isRollingSyncStrategy(appset) && len(appset.Spec.Strategy.RollingSync.Steps) > 0
} }
func isApplicationHealthy(app argov1alpha1.Application) bool { func isApplicationHealthy(app argov1alpha1.Application) bool {
@@ -1061,20 +1062,19 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
Message: "No Application status found, defaulting status to Waiting.", Message: "No Application status found, defaulting status to Waiting.",
Status: "Waiting", Status: "Waiting",
Step: strconv.Itoa(getAppStep(app.Name, appStepMap)), Step: strconv.Itoa(getAppStep(app.Name, appStepMap)),
TargetRevisions: app.Status.GetRevisions(),
} }
} else { } else {
// we have an existing AppStatus // we have an existing AppStatus
currentAppStatus = applicationSet.Status.ApplicationStatus[idx] currentAppStatus = applicationSet.Status.ApplicationStatus[idx]
if !reflect.DeepEqual(currentAppStatus.TargetRevisions, app.Status.GetRevisions()) {
currentAppStatus.Message = "Application has pending changes, setting status to Waiting." // upgrade any existing AppStatus that might have been set by an older argo-cd version
// note: currentAppStatus.TargetRevisions may be set to empty list earlier during migrations,
// to prevent other usage of r.Client.Status().Update to fail before reaching here.
if len(currentAppStatus.TargetRevisions) == 0 {
currentAppStatus.TargetRevisions = app.Status.GetRevisions()
} }
} }
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))
}
appOutdated := false appOutdated := false
if progressiveSyncsRollingSyncStrategyEnabled(applicationSet) { if progressiveSyncsRollingSyncStrategyEnabled(applicationSet) {
@@ -1087,15 +1087,25 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
currentAppStatus.Status = "Waiting" currentAppStatus.Status = "Waiting"
currentAppStatus.Message = "Application has pending changes, setting status to Waiting." currentAppStatus.Message = "Application has pending changes, setting status to Waiting."
currentAppStatus.Step = strconv.Itoa(getAppStep(currentAppStatus.Application, appStepMap)) currentAppStatus.Step = strconv.Itoa(getAppStep(currentAppStatus.Application, appStepMap))
currentAppStatus.TargetRevisions = app.Status.GetRevisions()
} }
if currentAppStatus.Status == "Pending" { if currentAppStatus.Status == "Pending" {
if !appOutdated && operationPhaseString == "Succeeded" { if operationPhaseString == "Succeeded" {
logCtx.Infof("Application %v has completed a sync successfully, updating its ApplicationSet status to Progressing", app.Name) revisions := []string{}
currentAppStatus.LastTransitionTime = &now if len(app.Status.OperationState.SyncResult.Revisions) > 0 {
currentAppStatus.Status = "Progressing" revisions = app.Status.OperationState.SyncResult.Revisions
currentAppStatus.Message = "Application resource completed a sync successfully, updating status from Pending to Progressing." } else if app.Status.OperationState.SyncResult.Revision != "" {
currentAppStatus.Step = strconv.Itoa(getAppStep(currentAppStatus.Application, appStepMap)) revisions = append(revisions, app.Status.OperationState.SyncResult.Revision)
}
if reflect.DeepEqual(currentAppStatus.TargetRevisions, revisions) {
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" { } else if operationPhaseString == "Running" || healthStatusString == "Progressing" {
logCtx.Infof("Application %v has entered Progressing status, updating its ApplicationSet status to Progressing", app.Name) logCtx.Infof("Application %v has entered Progressing status, updating its ApplicationSet status to Progressing", app.Name)
currentAppStatus.LastTransitionTime = &now currentAppStatus.LastTransitionTime = &now
@@ -1152,10 +1162,10 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
// populate updateCountMap with counts of existing Pending and Progressing Applications // populate updateCountMap with counts of existing Pending and Progressing Applications
for _, appStatus := range applicationSet.Status.ApplicationStatus { for _, appStatus := range applicationSet.Status.ApplicationStatus {
totalCountMap[appStepMap[appStatus.Application]]++ totalCountMap[appStepMap[appStatus.Application]] += 1
if appStatus.Status == "Pending" || appStatus.Status == "Progressing" { if appStatus.Status == "Pending" || appStatus.Status == "Progressing" {
updateCountMap[appStepMap[appStatus.Application]]++ updateCountMap[appStepMap[appStatus.Application]] += 1
} }
} }
@@ -1191,7 +1201,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
appStatus.Message = "Application moved to Pending status, watching for the Application resource to start Progressing." appStatus.Message = "Application moved to Pending status, watching for the Application resource to start Progressing."
appStatus.Step = strconv.Itoa(getAppStep(appStatus.Application, appStepMap)) appStatus.Step = strconv.Itoa(getAppStep(appStatus.Application, appStepMap))
updateCountMap[appStepMap[appStatus.Application]]++ updateCountMap[appStepMap[appStatus.Application]] += 1
} }
appStatuses = append(appStatuses, appStatus) appStatuses = append(appStatuses, appStatus)
@@ -1292,7 +1302,7 @@ func (r *ApplicationSetReconciler) migrateStatus(ctx context.Context, appset *ar
updatedAppset.DeepCopyInto(appset) updatedAppset.DeepCopyInto(appset)
return nil return nil
}) })
if err != nil && !apierrors.IsNotFound(err) { if err != nil && !apierr.IsNotFound(err) {
return fmt.Errorf("unable to set application set condition: %w", err) return fmt.Errorf("unable to set application set condition: %w", err)
} }
} }
@@ -1405,7 +1415,7 @@ func (r *ApplicationSetReconciler) syncValidApplications(logCtx *log.Entry, appl
pruneEnabled := false 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 // 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() { if validApps[i].Spec.SyncPolicy != nil && validApps[i].Spec.SyncPolicy.Automated != nil {
pruneEnabled = validApps[i].Spec.SyncPolicy.Automated.Prune pruneEnabled = validApps[i].Spec.SyncPolicy.Automated.Prune
validApps[i].Spec.SyncPolicy.Automated = nil validApps[i].Spec.SyncPolicy.Automated = nil
} }
@@ -1441,10 +1451,6 @@ func syncApplication(application argov1alpha1.Application, prune bool) argov1alp
}, },
}, },
Sync: &argov1alpha1.SyncOperation{}, Sync: &argov1alpha1.SyncOperation{},
// Set a retry limit of 5, aligning with the default in Argo CD's appcontroller auto-sync behavior.
// This provides consistency for retry behavior across controllers.
// See: https://github.com/argoproj/argo-cd/blob/af9ebac0bb35dc16eb034c1cefaf7c92d1029927/controller/appcontroller.go#L2126
Retry: argov1alpha1.RetryStrategy{Limit: 5},
} }
if application.Spec.SyncPolicy != nil { if application.Spec.SyncPolicy != nil {
@@ -1461,29 +1467,29 @@ func syncApplication(application argov1alpha1.Application, prune bool) argov1alp
return application return application
} }
func getApplicationOwnsHandler(enableProgressiveSyncs bool) predicate.Funcs { func getOwnsHandlerPredicates(enableProgressiveSyncs bool) predicate.Funcs {
return predicate.Funcs{ return predicate.Funcs{
CreateFunc: func(e event.CreateEvent) bool { CreateFunc: func(e event.CreateEvent) bool {
// if we are the owner and there is a create event, we most likely created it and do not need to // if we are the owner and there is a create event, we most likely created it and do not need to
// re-reconcile // re-reconcile
if log.IsLevelEnabled(log.DebugLevel) { if log.IsLevelEnabled(log.DebugLevel) {
logFields := log.Fields{"app": ""} var appName string
app, isApp := e.Object.(*argov1alpha1.Application) app, isApp := e.Object.(*argov1alpha1.Application)
if isApp { if isApp {
logFields = applog.GetAppLogFields(app) appName = app.QualifiedName()
} }
log.WithFields(logFields).Debugln("received create event from owning an application") log.WithField("app", appName).Debugln("received create event from owning an application")
} }
return false return false
}, },
DeleteFunc: func(e event.DeleteEvent) bool { DeleteFunc: func(e event.DeleteEvent) bool {
if log.IsLevelEnabled(log.DebugLevel) { if log.IsLevelEnabled(log.DebugLevel) {
logFields := log.Fields{"app": ""} var appName string
app, isApp := e.Object.(*argov1alpha1.Application) app, isApp := e.Object.(*argov1alpha1.Application)
if isApp { if isApp {
logFields = applog.GetAppLogFields(app) appName = app.QualifiedName()
} }
log.WithFields(logFields).Debugln("received delete event from owning an application") log.WithField("app", appName).Debugln("received delete event from owning an application")
} }
return true return true
}, },
@@ -1492,37 +1498,37 @@ func getApplicationOwnsHandler(enableProgressiveSyncs bool) predicate.Funcs {
if !isApp { if !isApp {
return false return false
} }
logCtx := log.WithFields(applog.GetAppLogFields(appOld)) logCtx := log.WithField("app", appOld.QualifiedName())
logCtx.Debugln("received update event from owning an application") logCtx.Debugln("received update event from owning an application")
appNew, isApp := e.ObjectNew.(*argov1alpha1.Application) appNew, isApp := e.ObjectNew.(*argov1alpha1.Application)
if !isApp { if !isApp {
return false return false
} }
requeue := shouldRequeueForApplication(appOld, appNew, enableProgressiveSyncs) requeue := shouldRequeueApplicationSet(appOld, appNew, enableProgressiveSyncs)
logCtx.WithField("requeue", requeue).Debugf("requeue caused by application %s", appNew.Name) logCtx.WithField("requeue", requeue).Debugf("requeue: %t caused by application %s", requeue, appNew.Name)
return requeue return requeue
}, },
GenericFunc: func(e event.GenericEvent) bool { GenericFunc: func(e event.GenericEvent) bool {
if log.IsLevelEnabled(log.DebugLevel) { if log.IsLevelEnabled(log.DebugLevel) {
logFields := log.Fields{} var appName string
app, isApp := e.Object.(*argov1alpha1.Application) app, isApp := e.Object.(*argov1alpha1.Application)
if isApp { if isApp {
logFields = applog.GetAppLogFields(app) appName = app.QualifiedName()
} }
log.WithFields(logFields).Debugln("received generic event from owning an application") log.WithField("app", appName).Debugln("received generic event from owning an application")
} }
return true return true
}, },
} }
} }
// shouldRequeueForApplication determines when we want to requeue an ApplicationSet for reconciling based on an owned // shouldRequeueApplicationSet determines when we want to requeue an ApplicationSet for reconciling based on an owned
// application change // application change
// The applicationset controller owns a subset of the Application CR. // The applicationset controller owns a subset of the Application CR.
// We do not need to re-reconcile if parts of the application change outside the applicationset's control. // We do not need to re-reconcile if parts of the application change outside the applicationset's control.
// An example being, Application.ApplicationStatus.ReconciledAt which gets updated by the application controller. // An example being, Application.ApplicationStatus.ReconciledAt which gets updated by the application controller.
// Additionally, Application.ObjectMeta.ResourceVersion and Application.ObjectMeta.Generation which are set by K8s. // Additionally, Application.ObjectMeta.ResourceVersion and Application.ObjectMeta.Generation which are set by K8s.
func shouldRequeueForApplication(appOld *argov1alpha1.Application, appNew *argov1alpha1.Application, enableProgressiveSyncs bool) bool { func shouldRequeueApplicationSet(appOld *argov1alpha1.Application, appNew *argov1alpha1.Application, enableProgressiveSyncs bool) bool {
if appOld == nil || appNew == nil { if appOld == nil || appNew == nil {
return false return false
} }
@@ -1532,9 +1538,9 @@ func shouldRequeueForApplication(appOld *argov1alpha1.Application, appNew *argov
// https://pkg.go.dev/reflect#DeepEqual // https://pkg.go.dev/reflect#DeepEqual
// ApplicationDestination has an unexported field so we can just use the == for comparison // ApplicationDestination has an unexported field so we can just use the == for comparison
if !cmp.Equal(appOld.Spec, appNew.Spec, cmpopts.EquateEmpty(), cmpopts.EquateComparable(argov1alpha1.ApplicationDestination{})) || if !cmp.Equal(appOld.Spec, appNew.Spec, cmpopts.EquateEmpty(), cmpopts.EquateComparable(argov1alpha1.ApplicationDestination{})) ||
!cmp.Equal(appOld.GetAnnotations(), appNew.GetAnnotations(), cmpopts.EquateEmpty()) || !cmp.Equal(appOld.ObjectMeta.GetAnnotations(), appNew.ObjectMeta.GetAnnotations(), cmpopts.EquateEmpty()) ||
!cmp.Equal(appOld.GetLabels(), appNew.GetLabels(), cmpopts.EquateEmpty()) || !cmp.Equal(appOld.ObjectMeta.GetLabels(), appNew.ObjectMeta.GetLabels(), cmpopts.EquateEmpty()) ||
!cmp.Equal(appOld.GetFinalizers(), appNew.GetFinalizers(), cmpopts.EquateEmpty()) { !cmp.Equal(appOld.ObjectMeta.GetFinalizers(), appNew.ObjectMeta.GetFinalizers(), cmpopts.EquateEmpty()) {
return true return true
} }
@@ -1555,89 +1561,4 @@ func shouldRequeueForApplication(appOld *argov1alpha1.Application, appNew *argov
return false return false
} }
func getApplicationSetOwnsHandler(enableProgressiveSyncs bool) predicate.Funcs {
return predicate.Funcs{
CreateFunc: func(e event.CreateEvent) bool {
appSet, isApp := e.Object.(*argov1alpha1.ApplicationSet)
if !isApp {
return false
}
log.WithField("applicationset", appSet.QualifiedName()).Debugln("received create event")
// Always queue a new applicationset
return true
},
DeleteFunc: func(e event.DeleteEvent) bool {
appSet, isApp := e.Object.(*argov1alpha1.ApplicationSet)
if !isApp {
return false
}
log.WithField("applicationset", appSet.QualifiedName()).Debugln("received delete event")
// Always queue for the deletion of an applicationset
return true
},
UpdateFunc: func(e event.UpdateEvent) bool {
appSetOld, isAppSet := e.ObjectOld.(*argov1alpha1.ApplicationSet)
if !isAppSet {
return false
}
appSetNew, isAppSet := e.ObjectNew.(*argov1alpha1.ApplicationSet)
if !isAppSet {
return false
}
requeue := shouldRequeueForApplicationSet(appSetOld, appSetNew, enableProgressiveSyncs)
log.WithField("applicationset", appSetNew.QualifiedName()).
WithField("requeue", requeue).Debugln("received update event")
return requeue
},
GenericFunc: func(e event.GenericEvent) bool {
appSet, isApp := e.Object.(*argov1alpha1.ApplicationSet)
if !isApp {
return false
}
log.WithField("applicationset", appSet.QualifiedName()).Debugln("received generic event")
// Always queue for the generic of an applicationset
return true
},
}
}
// shouldRequeueForApplicationSet determines when we need to requeue an applicationset
func shouldRequeueForApplicationSet(appSetOld, appSetNew *argov1alpha1.ApplicationSet, enableProgressiveSyncs bool) bool {
if appSetOld == nil || appSetNew == nil {
return false
}
// Requeue if any ApplicationStatus.Status changed for Progressive sync strategy
if enableProgressiveSyncs {
if !cmp.Equal(appSetOld.Status.ApplicationStatus, appSetNew.Status.ApplicationStatus, cmpopts.EquateEmpty()) {
return true
}
}
// only compare the applicationset spec, annotations, labels and finalizers, specifically avoiding
// the status field. status is owned by the applicationset controller,
// and we do not need to requeue when it does bookkeeping
// NB: the ApplicationDestination comes from the ApplicationSpec being embedded
// in the ApplicationSetTemplate from the generators
if !cmp.Equal(appSetOld.Spec, appSetNew.Spec, cmpopts.EquateEmpty(), cmpopts.EquateComparable(argov1alpha1.ApplicationDestination{})) ||
!cmp.Equal(appSetOld.GetLabels(), appSetNew.GetLabels(), cmpopts.EquateEmpty()) ||
!cmp.Equal(appSetOld.GetFinalizers(), appSetNew.GetFinalizers(), cmpopts.EquateEmpty()) {
return true
}
// Requeue only when the refresh annotation is newly added to the ApplicationSet.
// Changes to other annotations made simultaneously might be missed, but such cases are rare.
if !cmp.Equal(appSetOld.GetAnnotations(), appSetNew.GetAnnotations(), cmpopts.EquateEmpty()) {
_, oldHasRefreshAnnotation := appSetOld.Annotations[common.AnnotationApplicationSetRefresh]
_, newHasRefreshAnnotation := appSetNew.Annotations[common.AnnotationApplicationSetRefresh]
if oldHasRefreshAnnotation && !newHasRefreshAnnotation {
return false
}
return true
}
return false
}
var _ handler.EventHandler = &clusterSecretEventHandler{} var _ handler.EventHandler = &clusterSecretEventHandler{}

File diff suppressed because it is too large Load Diff

View File

@@ -14,8 +14,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/event"
"github.com/argoproj/argo-cd/v3/common" "github.com/argoproj/argo-cd/v2/common"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
// clusterSecretEventHandler is used when watching Secrets to check if they are ArgoCD Cluster Secrets, and if so // clusterSecretEventHandler is used when watching Secrets to check if they are ArgoCD Cluster Secrets, and if so

View File

@@ -1,34 +1,26 @@
package controllers package controllers
import ( import (
"context"
"testing" "testing"
argocommon "github.com/argoproj/argo-cd/v3/common" argocommon "github.com/argoproj/argo-cd/v2/common"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime" ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/reconcile"
argov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
type mockAddRateLimitingInterface struct {
addedItems []reconcile.Request
}
// Add checks the type, and adds it to the internal list of received additions
func (obj *mockAddRateLimitingInterface) Add(item reconcile.Request) {
obj.addedItems = append(obj.addedItems, item)
}
func TestClusterEventHandler(t *testing.T) { func TestClusterEventHandler(t *testing.T) {
scheme := runtime.NewScheme() scheme := runtime.NewScheme()
err := argov1alpha1.AddToScheme(scheme) err := argov1alpha1.AddToScheme(scheme)
@@ -47,7 +39,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "no application sets should mean no requests", name: "no application sets should mean no requests",
items: []argov1alpha1.ApplicationSet{}, items: []argov1alpha1.ApplicationSet{},
secret: corev1.Secret{ secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
@@ -61,7 +53,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "a cluster generator should produce a request", name: "a cluster generator should produce a request",
items: []argov1alpha1.ApplicationSet{ items: []argov1alpha1.ApplicationSet{
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "my-app-set", Name: "my-app-set",
Namespace: "argocd", Namespace: "argocd",
}, },
@@ -75,7 +67,7 @@ func TestClusterEventHandler(t *testing.T) {
}, },
}, },
secret: corev1.Secret{ secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
@@ -91,7 +83,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "multiple cluster generators should produce multiple requests", name: "multiple cluster generators should produce multiple requests",
items: []argov1alpha1.ApplicationSet{ items: []argov1alpha1.ApplicationSet{
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "my-app-set", Name: "my-app-set",
Namespace: "argocd", Namespace: "argocd",
}, },
@@ -104,7 +96,7 @@ func TestClusterEventHandler(t *testing.T) {
}, },
}, },
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "my-app-set2", Name: "my-app-set2",
Namespace: "argocd", Namespace: "argocd",
}, },
@@ -118,7 +110,7 @@ func TestClusterEventHandler(t *testing.T) {
}, },
}, },
secret: corev1.Secret{ secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
@@ -135,7 +127,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "non-cluster generator should not match", name: "non-cluster generator should not match",
items: []argov1alpha1.ApplicationSet{ items: []argov1alpha1.ApplicationSet{
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "my-app-set", Name: "my-app-set",
Namespace: "another-namespace", Namespace: "another-namespace",
}, },
@@ -148,7 +140,7 @@ func TestClusterEventHandler(t *testing.T) {
}, },
}, },
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "app-set-non-cluster", Name: "app-set-non-cluster",
Namespace: "argocd", Namespace: "argocd",
}, },
@@ -162,7 +154,7 @@ func TestClusterEventHandler(t *testing.T) {
}, },
}, },
secret: corev1.Secret{ secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
@@ -178,7 +170,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "non-argo cd secret should not match", name: "non-argo cd secret should not match",
items: []argov1alpha1.ApplicationSet{ items: []argov1alpha1.ApplicationSet{
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "my-app-set", Name: "my-app-set",
Namespace: "another-namespace", Namespace: "another-namespace",
}, },
@@ -192,7 +184,7 @@ func TestClusterEventHandler(t *testing.T) {
}, },
}, },
secret: corev1.Secret{ secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "argocd", Namespace: "argocd",
Name: "my-non-argocd-secret", Name: "my-non-argocd-secret",
}, },
@@ -203,7 +195,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "a matrix generator with a cluster generator should produce a request", name: "a matrix generator with a cluster generator should produce a request",
items: []argov1alpha1.ApplicationSet{ items: []argov1alpha1.ApplicationSet{
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "my-app-set", Name: "my-app-set",
Namespace: "argocd", Namespace: "argocd",
}, },
@@ -223,7 +215,7 @@ func TestClusterEventHandler(t *testing.T) {
}, },
}, },
secret: corev1.Secret{ secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
@@ -239,7 +231,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "a matrix generator with non cluster generator should not match", name: "a matrix generator with non cluster generator should not match",
items: []argov1alpha1.ApplicationSet{ items: []argov1alpha1.ApplicationSet{
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "my-app-set", Name: "my-app-set",
Namespace: "argocd", Namespace: "argocd",
}, },
@@ -259,7 +251,7 @@ func TestClusterEventHandler(t *testing.T) {
}, },
}, },
secret: corev1.Secret{ secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
@@ -273,7 +265,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "a matrix generator with a nested matrix generator containing a cluster generator should produce a request", name: "a matrix generator with a nested matrix generator containing a cluster generator should produce a request",
items: []argov1alpha1.ApplicationSet{ items: []argov1alpha1.ApplicationSet{
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "my-app-set", Name: "my-app-set",
Namespace: "argocd", Namespace: "argocd",
}, },
@@ -309,7 +301,7 @@ func TestClusterEventHandler(t *testing.T) {
}, },
}, },
secret: corev1.Secret{ secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
@@ -325,7 +317,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "a matrix generator with a nested matrix generator containing non cluster generator should not match", name: "a matrix generator with a nested matrix generator containing non cluster generator should not match",
items: []argov1alpha1.ApplicationSet{ items: []argov1alpha1.ApplicationSet{
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "my-app-set", Name: "my-app-set",
Namespace: "argocd", Namespace: "argocd",
}, },
@@ -360,7 +352,7 @@ func TestClusterEventHandler(t *testing.T) {
}, },
}, },
secret: corev1.Secret{ secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
@@ -374,7 +366,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "a merge generator with a cluster generator should produce a request", name: "a merge generator with a cluster generator should produce a request",
items: []argov1alpha1.ApplicationSet{ items: []argov1alpha1.ApplicationSet{
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "my-app-set", Name: "my-app-set",
Namespace: "argocd", Namespace: "argocd",
}, },
@@ -394,7 +386,7 @@ func TestClusterEventHandler(t *testing.T) {
}, },
}, },
secret: corev1.Secret{ secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
@@ -410,7 +402,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "a matrix generator with non cluster generator should not match", name: "a matrix generator with non cluster generator should not match",
items: []argov1alpha1.ApplicationSet{ items: []argov1alpha1.ApplicationSet{
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "my-app-set", Name: "my-app-set",
Namespace: "argocd", Namespace: "argocd",
}, },
@@ -430,7 +422,7 @@ func TestClusterEventHandler(t *testing.T) {
}, },
}, },
secret: corev1.Secret{ secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
@@ -444,7 +436,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "a merge generator with a nested merge generator containing a cluster generator should produce a request", name: "a merge generator with a nested merge generator containing a cluster generator should produce a request",
items: []argov1alpha1.ApplicationSet{ items: []argov1alpha1.ApplicationSet{
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "my-app-set", Name: "my-app-set",
Namespace: "argocd", Namespace: "argocd",
}, },
@@ -480,7 +472,7 @@ func TestClusterEventHandler(t *testing.T) {
}, },
}, },
secret: corev1.Secret{ secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
@@ -496,7 +488,7 @@ func TestClusterEventHandler(t *testing.T) {
name: "a merge generator with a nested merge generator containing non cluster generator should not match", name: "a merge generator with a nested merge generator containing non cluster generator should not match",
items: []argov1alpha1.ApplicationSet{ items: []argov1alpha1.ApplicationSet{
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "my-app-set", Name: "my-app-set",
Namespace: "argocd", Namespace: "argocd",
}, },
@@ -531,7 +523,7 @@ func TestClusterEventHandler(t *testing.T) {
}, },
}, },
secret: corev1.Secret{ secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "argocd", Namespace: "argocd",
Name: "my-secret", Name: "my-secret",
Labels: map[string]string{ Labels: map[string]string{
@@ -558,13 +550,22 @@ func TestClusterEventHandler(t *testing.T) {
mockAddRateLimitingInterface := mockAddRateLimitingInterface{} mockAddRateLimitingInterface := mockAddRateLimitingInterface{}
handler.queueRelatedAppGenerators(t.Context(), &mockAddRateLimitingInterface, &test.secret) handler.queueRelatedAppGenerators(context.Background(), &mockAddRateLimitingInterface, &test.secret)
assert.ElementsMatch(t, mockAddRateLimitingInterface.addedItems, test.expectedRequests) assert.ElementsMatch(t, mockAddRateLimitingInterface.addedItems, test.expectedRequests)
}) })
} }
} }
// Add checks the type, and adds it to the internal list of received additions
func (obj *mockAddRateLimitingInterface) Add(item reconcile.Request) {
obj.addedItems = append(obj.addedItems, item)
}
type mockAddRateLimitingInterface struct {
addedItems []reconcile.Request
}
func TestNestedGeneratorHasClusterGenerator_NestedClusterGenerator(t *testing.T) { func TestNestedGeneratorHasClusterGenerator_NestedClusterGenerator(t *testing.T) {
nested := argov1alpha1.ApplicationSetNestedGenerator{ nested := argov1alpha1.ApplicationSetNestedGenerator{
Clusters: &argov1alpha1.ClusterGenerator{}, Clusters: &argov1alpha1.ClusterGenerator{},

View File

@@ -1,6 +1,7 @@
package controllers package controllers
import ( import (
"context"
"testing" "testing"
"time" "time"
@@ -15,15 +16,15 @@ import (
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
"sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/argoproj/argo-cd/v3/applicationset/generators" "github.com/argoproj/argo-cd/v2/applicationset/generators"
appsetmetrics "github.com/argoproj/argo-cd/v3/applicationset/metrics" appsetmetrics "github.com/argoproj/argo-cd/v2/applicationset/metrics"
"github.com/argoproj/argo-cd/v3/applicationset/services/mocks" "github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
argov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
func TestRequeueAfter(t *testing.T) { func TestRequeueAfter(t *testing.T) {
mockServer := &mocks.Repos{} mockServer := &mocks.Repos{}
ctx := t.Context() ctx := context.Background()
scheme := runtime.NewScheme() scheme := runtime.NewScheme()
err := argov1alpha1.AddToScheme(scheme) err := argov1alpha1.AddToScheme(scheme)
require.NoError(t, err) require.NoError(t, err)
@@ -35,20 +36,20 @@ func TestRequeueAfter(t *testing.T) {
appClientset := kubefake.NewSimpleClientset() appClientset := kubefake.NewSimpleClientset()
k8sClient := fake.NewClientBuilder().Build() k8sClient := fake.NewClientBuilder().Build()
duckType := &unstructured.Unstructured{ duckType := &unstructured.Unstructured{
Object: map[string]any{ Object: map[string]interface{}{
"apiVersion": "v2quack", "apiVersion": "v2quack",
"kind": "Duck", "kind": "Duck",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"name": "mightyduck", "name": "mightyduck",
"namespace": "namespace", "namespace": "namespace",
"labels": map[string]any{"duck": "all-species"}, "labels": map[string]interface{}{"duck": "all-species"},
}, },
"status": map[string]any{ "status": map[string]interface{}{
"decisions": []any{ "decisions": []interface{}{
map[string]any{ map[string]interface{}{
"clusterName": "staging-01", "clusterName": "staging-01",
}, },
map[string]any{ map[string]interface{}{
"clusterName": "production-01", "clusterName": "production-01",
}, },
}, },
@@ -56,10 +57,10 @@ func TestRequeueAfter(t *testing.T) {
}, },
} }
fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, duckType) fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, duckType)
scmConfig := generators.NewSCMConfig("", []string{""}, true, true, nil, true) scmConfig := generators.NewSCMConfig("", []string{""}, true, nil, true)
terminalGenerators := map[string]generators.Generator{ terminalGenerators := map[string]generators.Generator{
"List": generators.NewListGenerator(), "List": generators.NewListGenerator(),
"Clusters": generators.NewClusterGenerator(ctx, k8sClient, appClientset, "argocd"), "Clusters": generators.NewClusterGenerator(k8sClient, ctx, appClientset, "argocd"),
"Git": generators.NewGitGenerator(mockServer, "namespace"), "Git": generators.NewGitGenerator(mockServer, "namespace"),
"SCMProvider": generators.NewSCMProviderGenerator(fake.NewClientBuilder().WithObjects(&corev1.Secret{}).Build(), scmConfig), "SCMProvider": generators.NewSCMProviderGenerator(fake.NewClientBuilder().WithObjects(&corev1.Secret{}).Build(), scmConfig),
"ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, fakeDynClient, appClientset, "argocd"), "ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, fakeDynClient, appClientset, "argocd"),
@@ -89,7 +90,7 @@ func TestRequeueAfter(t *testing.T) {
} }
client := fake.NewClientBuilder().WithScheme(scheme).Build() client := fake.NewClientBuilder().WithScheme(scheme).Build()
metrics := appsetmetrics.NewFakeAppsetMetrics() metrics := appsetmetrics.NewFakeAppsetMetrics(client)
r := ApplicationSetReconciler{ r := ApplicationSetReconciler{
Client: client, Client: client,
Scheme: scheme, Scheme: scheme,
@@ -129,7 +130,7 @@ func TestRequeueAfter(t *testing.T) {
}}, }},
}, },
}, },
}, ""}, want: generators.DefaultRequeueAfter, wantErr: assert.NoError}, }, ""}, want: generators.DefaultRequeueAfterSeconds, wantErr: assert.NoError},
{name: "ClusterMatrixNested", args: args{&argov1alpha1.ApplicationSet{ {name: "ClusterMatrixNested", args: args{&argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{ Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{ Generators: []argov1alpha1.ApplicationSetGenerator{
@@ -144,7 +145,7 @@ func TestRequeueAfter(t *testing.T) {
}}, }},
}, },
}, },
}, ""}, want: generators.DefaultRequeueAfter, wantErr: assert.NoError}, }, ""}, want: generators.DefaultRequeueAfterSeconds, wantErr: assert.NoError},
{name: "ListGenerator", args: args{appset: &argov1alpha1.ApplicationSet{ {name: "ListGenerator", args: args{appset: &argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{ Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{{List: &argov1alpha1.ListGenerator{}}}, Generators: []argov1alpha1.ApplicationSetGenerator{{List: &argov1alpha1.ListGenerator{}}},
@@ -154,7 +155,7 @@ func TestRequeueAfter(t *testing.T) {
Spec: argov1alpha1.ApplicationSetSpec{ Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{{ClusterDecisionResource: &argov1alpha1.DuckTypeGenerator{}}}, Generators: []argov1alpha1.ApplicationSetGenerator{{ClusterDecisionResource: &argov1alpha1.DuckTypeGenerator{}}},
}, },
}}, want: generators.DefaultRequeueAfter, wantErr: assert.NoError}, }}, want: generators.DefaultRequeueAfterSeconds, wantErr: assert.NoError},
{name: "OverrideRequeueDuck", args: args{ {name: "OverrideRequeueDuck", args: args{
appset: &argov1alpha1.ApplicationSet{ appset: &argov1alpha1.ApplicationSet{
Spec: argov1alpha1.ApplicationSetSpec{ Spec: argov1alpha1.ApplicationSetSpec{

View File

@@ -6,8 +6,8 @@ import (
"k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/strategicpatch"
"github.com/argoproj/argo-cd/v3/applicationset/utils" "github.com/argoproj/argo-cd/v2/applicationset/utils"
appv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
func applyTemplatePatch(app *appv1.Application, templatePatch string) (*appv1.Application, error) { func applyTemplatePatch(app *appv1.Application, templatePatch string) (*appv1.Application, error) {

View File

@@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
appv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
func Test_ApplyTemplatePatch(t *testing.T) { func Test_ApplyTemplatePatch(t *testing.T) {
@@ -27,7 +27,7 @@ func Test_ApplyTemplatePatch(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-guestbook", Name: "my-cluster-guestbook",
Namespace: "namespace", Namespace: "namespace",
Finalizers: []string{appv1.ResourcesFinalizerName}, Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
}, },
Spec: appv1.ApplicationSpec{ Spec: appv1.ApplicationSpec{
Project: "default", Project: "default",
@@ -72,7 +72,7 @@ func Test_ApplyTemplatePatch(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-guestbook", Name: "my-cluster-guestbook",
Namespace: "namespace", Namespace: "namespace",
Finalizers: []string{appv1.ResourcesFinalizerName}, Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Annotations: map[string]string{ Annotations: map[string]string{
"annotation-some-key": "annotation-some-value", "annotation-some-key": "annotation-some-value",
}, },
@@ -112,7 +112,7 @@ func Test_ApplyTemplatePatch(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-guestbook", Name: "my-cluster-guestbook",
Namespace: "namespace", Namespace: "namespace",
Finalizers: []string{appv1.ResourcesFinalizerName}, Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
}, },
Spec: appv1.ApplicationSpec{ Spec: appv1.ApplicationSpec{
Project: "default", Project: "default",
@@ -148,7 +148,7 @@ spec:
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-guestbook", Name: "my-cluster-guestbook",
Namespace: "namespace", Namespace: "namespace",
Finalizers: []string{appv1.ResourcesFinalizerName}, Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Annotations: map[string]string{ Annotations: map[string]string{
"annotation-some-key": "annotation-some-value", "annotation-some-key": "annotation-some-value",
}, },

View File

@@ -7,10 +7,10 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v3/applicationset/generators" "github.com/argoproj/argo-cd/v2/applicationset/generators"
"github.com/argoproj/argo-cd/v3/applicationset/utils" "github.com/argoproj/argo-cd/v2/applicationset/utils"
argov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
func GenerateApplications(logCtx *log.Entry, applicationSetInfo argov1alpha1.ApplicationSet, g map[string]generators.Generator, renderer utils.Renderer, client client.Client) ([]argov1alpha1.Application, argov1alpha1.ApplicationSetReasonType, error) { func GenerateApplications(logCtx *log.Entry, applicationSetInfo argov1alpha1.ApplicationSet, g map[string]generators.Generator, renderer utils.Renderer, client client.Client) ([]argov1alpha1.Application, argov1alpha1.ApplicationSetReasonType, error) {
@@ -20,7 +20,7 @@ func GenerateApplications(logCtx *log.Entry, applicationSetInfo argov1alpha1.App
var applicationSetReason argov1alpha1.ApplicationSetReasonType var applicationSetReason argov1alpha1.ApplicationSetReasonType
for _, requestedGenerator := range applicationSetInfo.Spec.Generators { for _, requestedGenerator := range applicationSetInfo.Spec.Generators {
t, err := generators.Transform(requestedGenerator, g, applicationSetInfo.Spec.Template, &applicationSetInfo, map[string]any{}, client) t, err := generators.Transform(requestedGenerator, g, applicationSetInfo.Spec.Template, &applicationSetInfo, map[string]interface{}{}, client)
if err != nil { if err != nil {
logCtx.WithError(err).WithField("generator", requestedGenerator). logCtx.WithError(err).WithField("generator", requestedGenerator).
Error("error generating application from params") Error("error generating application from params")
@@ -79,7 +79,7 @@ func GenerateApplications(logCtx *log.Entry, applicationSetInfo argov1alpha1.App
return res, applicationSetReason, firstError return res, applicationSetReason, firstError
} }
func renderTemplatePatch(r utils.Renderer, app *argov1alpha1.Application, applicationSetInfo argov1alpha1.ApplicationSet, params map[string]any) (*argov1alpha1.Application, error) { func renderTemplatePatch(r utils.Renderer, app *argov1alpha1.Application, applicationSetInfo argov1alpha1.ApplicationSet, params map[string]interface{}) (*argov1alpha1.Application, error) {
replacedTemplate, err := r.Replace(*applicationSetInfo.Spec.TemplatePatch, params, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions) replacedTemplate, err := r.Replace(*applicationSetInfo.Spec.TemplatePatch, params, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions)
if err != nil { if err != nil {
return nil, fmt.Errorf("error replacing values in templatePatch: %w", err) return nil, fmt.Errorf("error replacing values in templatePatch: %w", err)

View File

@@ -1,7 +1,7 @@
package template package template
import ( import (
"errors" "fmt"
"maps" "maps"
"testing" "testing"
@@ -13,12 +13,12 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"github.com/argoproj/argo-cd/v3/applicationset/generators" "github.com/argoproj/argo-cd/v2/applicationset/generators"
genmock "github.com/argoproj/argo-cd/v3/applicationset/generators/mocks" genmock "github.com/argoproj/argo-cd/v2/applicationset/generators/mocks"
"github.com/argoproj/argo-cd/v3/applicationset/utils" "github.com/argoproj/argo-cd/v2/applicationset/utils"
rendmock "github.com/argoproj/argo-cd/v3/applicationset/utils/mocks" rendmock "github.com/argoproj/argo-cd/v2/applicationset/utils/mocks"
"github.com/argoproj/argo-cd/v3/pkg/apis/application" "github.com/argoproj/argo-cd/v2/pkg/apis/application"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
func TestGenerateApplications(t *testing.T) { func TestGenerateApplications(t *testing.T) {
@@ -31,7 +31,7 @@ func TestGenerateApplications(t *testing.T) {
for _, c := range []struct { for _, c := range []struct {
name string name string
params []map[string]any params []map[string]interface{}
template v1alpha1.ApplicationSetTemplate template v1alpha1.ApplicationSetTemplate
generateParamsError error generateParamsError error
rendererError error rendererError error
@@ -40,7 +40,7 @@ func TestGenerateApplications(t *testing.T) {
}{ }{
{ {
name: "Generate two applications", name: "Generate two applications",
params: []map[string]any{{"name": "app1"}, {"name": "app2"}}, params: []map[string]interface{}{{"name": "app1"}, {"name": "app2"}},
template: v1alpha1.ApplicationSetTemplate{ template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "name", Name: "name",
@@ -53,13 +53,13 @@ func TestGenerateApplications(t *testing.T) {
}, },
{ {
name: "Handles error from the generator", name: "Handles error from the generator",
generateParamsError: errors.New("error"), generateParamsError: fmt.Errorf("error"),
expectErr: true, expectErr: true,
expectedReason: v1alpha1.ApplicationSetReasonApplicationParamsGenerationError, expectedReason: v1alpha1.ApplicationSetReasonApplicationParamsGenerationError,
}, },
{ {
name: "Handles error from the render", name: "Handles error from the render",
params: []map[string]any{{"name": "app1"}, {"name": "app2"}}, params: []map[string]interface{}{{"name": "app1"}, {"name": "app2"}},
template: v1alpha1.ApplicationSetTemplate{ template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "name", Name: "name",
@@ -68,7 +68,7 @@ func TestGenerateApplications(t *testing.T) {
}, },
Spec: v1alpha1.ApplicationSpec{}, Spec: v1alpha1.ApplicationSpec{},
}, },
rendererError: errors.New("error"), rendererError: fmt.Errorf("error"),
expectErr: true, expectErr: true,
expectedReason: v1alpha1.ApplicationSetReasonRenderTemplateParamsError, expectedReason: v1alpha1.ApplicationSetReasonRenderTemplateParamsError,
}, },
@@ -153,7 +153,7 @@ func TestGenerateApplications(t *testing.T) {
func TestMergeTemplateApplications(t *testing.T) { func TestMergeTemplateApplications(t *testing.T) {
for _, c := range []struct { for _, c := range []struct {
name string name string
params []map[string]any params []map[string]interface{}
template v1alpha1.ApplicationSetTemplate template v1alpha1.ApplicationSetTemplate
overrideTemplate v1alpha1.ApplicationSetTemplate overrideTemplate v1alpha1.ApplicationSetTemplate
expectedMerged v1alpha1.ApplicationSetTemplate expectedMerged v1alpha1.ApplicationSetTemplate
@@ -161,7 +161,7 @@ func TestMergeTemplateApplications(t *testing.T) {
}{ }{
{ {
name: "Generate app", name: "Generate app",
params: []map[string]any{{"name": "app1"}}, params: []map[string]interface{}{{"name": "app1"}},
template: v1alpha1.ApplicationSetTemplate{ template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "name", Name: "name",
@@ -245,13 +245,13 @@ func TestMergeTemplateApplications(t *testing.T) {
func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) { func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) {
for _, cases := range []struct { for _, cases := range []struct {
name string name string
params []map[string]any params []map[string]interface{}
template v1alpha1.ApplicationSetTemplate template v1alpha1.ApplicationSetTemplate
expectedApp []v1alpha1.Application expectedApp []v1alpha1.Application
}{ }{
{ {
name: "Generate an application from a go template application set manifest using a pull request generator", name: "Generate an application from a go template application set manifest using a pull request generator",
params: []map[string]any{ params: []map[string]interface{}{
{ {
"number": "1", "number": "1",
"title": "title1", "title": "title1",
@@ -341,10 +341,10 @@ func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) {
renderer, renderer,
nil, nil,
) )
assert.Equal(t, cases.expectedApp[0].Name, gotApp[0].Name) assert.EqualValues(t, cases.expectedApp[0].ObjectMeta.Name, gotApp[0].ObjectMeta.Name)
assert.Equal(t, cases.expectedApp[0].Spec.Source.TargetRevision, gotApp[0].Spec.Source.TargetRevision) assert.EqualValues(t, cases.expectedApp[0].Spec.Source.TargetRevision, gotApp[0].Spec.Source.TargetRevision)
assert.Equal(t, cases.expectedApp[0].Spec.Destination.Namespace, gotApp[0].Spec.Destination.Namespace) assert.EqualValues(t, cases.expectedApp[0].Spec.Destination.Namespace, gotApp[0].Spec.Destination.Namespace)
assert.True(t, maps.Equal(cases.expectedApp[0].Labels, gotApp[0].Labels)) assert.True(t, maps.Equal(cases.expectedApp[0].ObjectMeta.Labels, gotApp[0].ObjectMeta.Labels))
}) })
} }
} }

View File

@@ -14,7 +14,7 @@ spec:
app: guestbook-ui app: guestbook-ui
spec: spec:
containers: containers:
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2 - image: gcr.io/heptio-images/ks-guestbook-demo:0.2
name: guestbook-ui name: guestbook-ui
ports: ports:
- containerPort: 80 - containerPort: 80

View File

@@ -14,7 +14,7 @@ spec:
app: guestbook-ui app: guestbook-ui
spec: spec:
containers: containers:
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2 - image: gcr.io/heptio-images/ks-guestbook-demo:0.2
name: guestbook-ui name: guestbook-ui
ports: ports:
- containerPort: 80 - containerPort: 80

View File

@@ -5,7 +5,7 @@
replicaCount: 1 replicaCount: 1
image: image:
repository: quay.io/argoprojlabs/argocd-e2e-container repository: gcr.io/heptio-images/ks-guestbook-demo
tag: 0.1 tag: 0.1
pullPolicy: IfNotPresent pullPolicy: IfNotPresent

View File

@@ -14,7 +14,7 @@ spec:
app: guestbook-ui app: guestbook-ui
spec: spec:
containers: containers:
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2 - image: gcr.io/heptio-images/ks-guestbook-demo:0.2
name: guestbook-ui name: guestbook-ui
ports: ports:
- containerPort: 80 - containerPort: 80

View File

@@ -1,26 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: guestbook
spec:
generators:
- git:
repoURL: https://github.com/argoproj/argo-cd.git
revision: HEAD
files:
- path: "applicationset/examples/git-generator-files-discovery/cluster-config/**/config.json"
- path: "applicationset/examples/git-generator-files-discovery/cluster-config/*/dev/config.json"
exclude: true
template:
metadata:
name: '{{cluster.name}}-guestbook'
spec:
project: default
source:
repoURL: https://github.com/argoproj/argo-cd.git
targetRevision: HEAD
path: "applicationset/examples/git-generator-files-discovery/apps/guestbook"
destination:
server: https://kubernetes.default.svc
#server: '{{cluster.address}}'
namespace: guestbook

View File

@@ -1,27 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: guestbook
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- git:
repoURL: https://github.com/argoproj/argo-cd.git
revision: HEAD
files:
- path: "applicationset/examples/git-generator-files-discovery/cluster-config/**/config.json"
- path: "applicationset/examples/git-generator-files-discovery/cluster-config/*/dev/config.json"
exclude: true
template:
metadata:
name: '{{.cluster.name}}-guestbook'
spec:
project: default
source:
repoURL: https://github.com/argoproj/argo-cd.git
targetRevision: HEAD
path: "applicationset/examples/git-generator-files-discovery/apps/guestbook"
destination:
server: https://kubernetes.default.svc
namespace: guestbook

View File

@@ -14,7 +14,7 @@ spec:
app: guestbook-ui app: guestbook-ui
spec: spec:
containers: containers:
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2 - image: gcr.io/heptio-images/ks-guestbook-demo:0.2
name: guestbook-ui name: guestbook-ui
ports: ports:
- containerPort: 80 - containerPort: 80

View File

@@ -14,7 +14,7 @@ spec:
app: guestbook-ui app: guestbook-ui
spec: spec:
containers: containers:
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2 - image: gcr.io/heptio-images/ks-guestbook-demo:0.2
name: guestbook-ui name: guestbook-ui
ports: ports:
- containerPort: 80 - containerPort: 80

View File

@@ -14,7 +14,7 @@ spec:
app: guestbook-ui app: guestbook-ui
spec: spec:
containers: containers:
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2 - image: gcr.io/heptio-images/ks-guestbook-demo:0.2
name: guestbook-ui name: guestbook-ui
ports: ports:
- containerPort: 80 - containerPort: 80

View File

@@ -14,7 +14,7 @@ spec:
app: guestbook-ui app: guestbook-ui
spec: spec:
containers: containers:
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2 - image: gcr.io/heptio-images/ks-guestbook-demo:0.2
name: guestbook-ui name: guestbook-ui
ports: ports:
- containerPort: 80 - containerPort: 80

View File

@@ -7,14 +7,16 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v2/util/settings"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v3/applicationset/utils" "github.com/argoproj/argo-cd/v2/applicationset/utils"
"github.com/argoproj/argo-cd/v3/common" "github.com/argoproj/argo-cd/v2/common"
argoappsetv1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argoappsetv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
var _ Generator = (*ClusterGenerator)(nil) var _ Generator = (*ClusterGenerator)(nil)
@@ -25,17 +27,21 @@ type ClusterGenerator struct {
ctx context.Context ctx context.Context
clientset kubernetes.Interface clientset kubernetes.Interface
// namespace is the Argo CD namespace // namespace is the Argo CD namespace
namespace string namespace string
settingsManager *settings.SettingsManager
} }
var render = &utils.Render{} var render = &utils.Render{}
func NewClusterGenerator(ctx context.Context, c client.Client, clientset kubernetes.Interface, namespace string) Generator { func NewClusterGenerator(c client.Client, ctx context.Context, clientset kubernetes.Interface, namespace string) Generator {
settingsManager := settings.NewSettingsManager(ctx, clientset, namespace)
g := &ClusterGenerator{ g := &ClusterGenerator{
Client: c, Client: c,
ctx: ctx, ctx: ctx,
clientset: clientset, clientset: clientset,
namespace: namespace, namespace: namespace,
settingsManager: settingsManager,
} }
return g return g
} }
@@ -50,21 +56,21 @@ func (g *ClusterGenerator) GetTemplate(appSetGenerator *argoappsetv1alpha1.Appli
return &appSetGenerator.Clusters.Template return &appSetGenerator.Clusters.Template
} }
func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator, appSet *argoappsetv1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) { func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator, appSet *argoappsetv1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
logCtx := log.WithField("applicationset", appSet.GetName()).WithField("namespace", appSet.GetNamespace()) logCtx := log.WithField("applicationset", appSet.GetName()).WithField("namespace", appSet.GetNamespace())
if appSetGenerator == nil { if appSetGenerator == nil {
return nil, ErrEmptyAppSetGenerator return nil, EmptyAppSetGeneratorError
} }
if appSetGenerator.Clusters == nil { if appSetGenerator.Clusters == nil {
return nil, ErrEmptyAppSetGenerator return nil, EmptyAppSetGeneratorError
} }
// Do not include the local cluster in the cluster parameters IF there is a non-empty selector // Do not include the local cluster in the cluster parameters IF there is a non-empty selector
// - Since local clusters do not have secrets, they do not have labels to match against // - Since local clusters do not have secrets, they do not have labels to match against
ignoreLocalClusters := len(appSetGenerator.Clusters.Selector.MatchExpressions) > 0 || len(appSetGenerator.Clusters.Selector.MatchLabels) > 0 ignoreLocalClusters := len(appSetGenerator.Clusters.Selector.MatchExpressions) > 0 || len(appSetGenerator.Clusters.Selector.MatchLabels) > 0
// ListCluster will include the local cluster in the list of clusters // ListCluster from Argo CD's util/db package will include the local cluster in the list of clusters
clustersFromArgoCD, err := utils.ListClusters(g.ctx, g.clientset, g.namespace) clustersFromArgoCD, err := utils.ListClusters(g.ctx, g.clientset, g.namespace)
if err != nil { if err != nil {
return nil, fmt.Errorf("error listing clusters: %w", err) return nil, fmt.Errorf("error listing clusters: %w", err)
@@ -79,22 +85,22 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
return nil, fmt.Errorf("error getting cluster secrets: %w", err) return nil, fmt.Errorf("error getting cluster secrets: %w", err)
} }
res := []map[string]any{} res := []map[string]interface{}{}
secretsFound := []corev1.Secret{} secretsFound := []corev1.Secret{}
isFlatMode := appSetGenerator.Clusters.FlatList isFlatMode := appSetGenerator.Clusters.FlatList
logCtx.Debugf("Using flat mode = %t for cluster generator", isFlatMode) logCtx.Debugf("Using flat mode = %t for cluster generator", isFlatMode)
clustersParams := make([]map[string]any, 0) clustersParams := make([]map[string]interface{}, 0)
for _, cluster := range clustersFromArgoCD { for _, cluster := range clustersFromArgoCD.Items {
// If there is a secret for this cluster, then it's a non-local cluster, so it will be // If there is a secret for this cluster, then it's a non-local cluster, so it will be
// handled by the next step. // handled by the next step.
if secretForCluster, exists := clusterSecrets[cluster.Name]; exists { if secretForCluster, exists := clusterSecrets[cluster.Name]; exists {
secretsFound = append(secretsFound, secretForCluster) secretsFound = append(secretsFound, secretForCluster)
} else if !ignoreLocalClusters { } else if !ignoreLocalClusters {
// If there is no secret for the cluster, it's the local cluster, so handle it here. // If there is no secret for the cluster, it's the local cluster, so handle it here.
params := map[string]any{} params := map[string]interface{}{}
params["name"] = cluster.Name params["name"] = cluster.Name
params["nameNormalized"] = cluster.Name params["nameNormalized"] = cluster.Name
params["server"] = cluster.Server params["server"] = cluster.Server
@@ -117,7 +123,7 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
// For each matching cluster secret (non-local clusters only) // For each matching cluster secret (non-local clusters only)
for _, cluster := range secretsFound { for _, cluster := range secretsFound {
params := map[string]any{} params := map[string]interface{}{}
params["name"] = string(cluster.Data["name"]) params["name"] = string(cluster.Data["name"])
params["nameNormalized"] = utils.SanitizeName(string(cluster.Data["name"])) params["nameNormalized"] = utils.SanitizeName(string(cluster.Data["name"]))
@@ -131,23 +137,23 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
} }
if appSet.Spec.GoTemplate { if appSet.Spec.GoTemplate {
meta := map[string]any{} meta := map[string]interface{}{}
if len(cluster.Annotations) > 0 { if len(cluster.ObjectMeta.Annotations) > 0 {
meta["annotations"] = cluster.Annotations meta["annotations"] = cluster.ObjectMeta.Annotations
} }
if len(cluster.Labels) > 0 { if len(cluster.ObjectMeta.Labels) > 0 {
meta["labels"] = cluster.Labels meta["labels"] = cluster.ObjectMeta.Labels
} }
params["metadata"] = meta params["metadata"] = meta
} else { } else {
for key, value := range cluster.Annotations { for key, value := range cluster.ObjectMeta.Annotations {
params["metadata.annotations."+key] = value params[fmt.Sprintf("metadata.annotations.%s", key)] = value
} }
for key, value := range cluster.Labels { for key, value := range cluster.ObjectMeta.Labels {
params["metadata.labels."+key] = value params[fmt.Sprintf("metadata.labels.%s", key)] = value
} }
} }
@@ -166,7 +172,7 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
} }
if isFlatMode { if isFlatMode {
res = append(res, map[string]any{ res = append(res, map[string]interface{}{
"clusters": clustersParams, "clusters": clustersParams,
}) })
} }
@@ -182,7 +188,7 @@ func (g *ClusterGenerator) getSecretsByClusterName(log *log.Entry, appSetGenerat
return nil, fmt.Errorf("error converting label selector: %w", err) return nil, fmt.Errorf("error converting label selector: %w", err)
} }
if err := g.List(context.Background(), clusterSecretList, client.MatchingLabelsSelector{Selector: secretSelector}); err != nil { if err := g.Client.List(context.Background(), clusterSecretList, client.MatchingLabelsSelector{Selector: secretSelector}); err != nil {
return nil, err return nil, err
} }
log.Debugf("clusters matching labels: %d", len(clusterSecretList.Items)) log.Debugf("clusters matching labels: %d", len(clusterSecretList.Items))

View File

@@ -2,7 +2,7 @@ package generators
import ( import (
"context" "context"
"errors" "fmt"
"testing" "testing"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@@ -13,8 +13,8 @@ import (
kubefake "k8s.io/client-go/kubernetes/fake" kubefake "k8s.io/client-go/kubernetes/fake"
"github.com/argoproj/argo-cd/v3/applicationset/utils" "github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -27,7 +27,7 @@ type possiblyErroringFakeCtrlRuntimeClient struct {
func (p *possiblyErroringFakeCtrlRuntimeClient) List(ctx context.Context, secretList client.ObjectList, opts ...client.ListOption) error { func (p *possiblyErroringFakeCtrlRuntimeClient) List(ctx context.Context, secretList client.ObjectList, opts ...client.ListOption) error {
if p.shouldError { if p.shouldError {
return errors.New("could not list Secrets") return fmt.Errorf("could not list Secrets")
} }
return p.Client.List(ctx, secretList, opts...) return p.Client.List(ctx, secretList, opts...)
} }
@@ -89,7 +89,7 @@ func TestGenerateParams(t *testing.T) {
selector metav1.LabelSelector selector metav1.LabelSelector
isFlatMode bool isFlatMode bool
values map[string]string values map[string]string
expected []map[string]any expected []map[string]interface{}
// clientError is true if a k8s client error should be simulated // clientError is true if a k8s client error should be simulated
clientError bool clientError bool
expectedError error expectedError error
@@ -106,7 +106,7 @@ func TestGenerateParams(t *testing.T) {
"bat": "{{ metadata.labels.environment }}", "bat": "{{ metadata.labels.environment }}",
"aaa": "{{ server }}", "aaa": "{{ server }}",
"no-op": "{{ this-does-not-exist }}", "no-op": "{{ this-does-not-exist }}",
}, expected: []map[string]any{ }, expected: []map[string]interface{}{
{"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "{{ metadata.annotations.foo.argoproj.io }}", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "{{ metadata.labels.environment }}", "values.aaa": "https://kubernetes.default.svc", "nameNormalized": "in-cluster", "name": "in-cluster", "server": "https://kubernetes.default.svc", "project": ""}, {"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "{{ metadata.annotations.foo.argoproj.io }}", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "{{ metadata.labels.environment }}", "values.aaa": "https://kubernetes.default.svc", "nameNormalized": "in-cluster", "name": "in-cluster", "server": "https://kubernetes.default.svc", "project": ""},
{ {
"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "production", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "production", "values.aaa": "https://production-01.example.com", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar", "values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "production", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "production", "values.aaa": "https://production-01.example.com", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
@@ -129,7 +129,7 @@ func TestGenerateParams(t *testing.T) {
}, },
}, },
values: nil, values: nil,
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
@@ -153,7 +153,7 @@ func TestGenerateParams(t *testing.T) {
values: map[string]string{ values: map[string]string{
"foo": "bar", "foo": "bar",
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"values.foo": "bar", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar", "values.foo": "bar", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
@@ -179,7 +179,7 @@ func TestGenerateParams(t *testing.T) {
values: map[string]string{ values: map[string]string{
"foo": "bar", "foo": "bar",
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"values.foo": "bar", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo", "values.foo": "bar", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
@@ -212,7 +212,7 @@ func TestGenerateParams(t *testing.T) {
values: map[string]string{ values: map[string]string{
"name": "baz", "name": "baz",
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"values.name": "baz", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo", "values.name": "baz", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging", "project": "",
@@ -227,7 +227,7 @@ func TestGenerateParams(t *testing.T) {
values: nil, values: nil,
expected: nil, expected: nil,
clientError: true, clientError: true,
expectedError: errors.New("error getting cluster secrets: could not list Secrets"), expectedError: fmt.Errorf("error getting cluster secrets: could not list Secrets"),
}, },
{ {
name: "flat mode without selectors", name: "flat mode without selectors",
@@ -242,9 +242,9 @@ func TestGenerateParams(t *testing.T) {
"aaa": "{{ server }}", "aaa": "{{ server }}",
"no-op": "{{ this-does-not-exist }}", "no-op": "{{ this-does-not-exist }}",
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"clusters": []map[string]any{ "clusters": []map[string]interface{}{
{"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "{{ metadata.annotations.foo.argoproj.io }}", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "{{ metadata.labels.environment }}", "values.aaa": "https://kubernetes.default.svc", "nameNormalized": "in-cluster", "name": "in-cluster", "server": "https://kubernetes.default.svc", "project": ""}, {"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "{{ metadata.annotations.foo.argoproj.io }}", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "{{ metadata.labels.environment }}", "values.aaa": "https://kubernetes.default.svc", "nameNormalized": "in-cluster", "name": "in-cluster", "server": "https://kubernetes.default.svc", "project": ""},
{ {
"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "production", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "production", "values.aaa": "https://production-01.example.com", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar", "values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "production", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "production", "values.aaa": "https://production-01.example.com", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
@@ -280,9 +280,9 @@ func TestGenerateParams(t *testing.T) {
values: map[string]string{ values: map[string]string{
"foo": "bar", "foo": "bar",
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"clusters": []map[string]any{ "clusters": []map[string]interface{}{
{ {
"values.foo": "bar", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar", "values.foo": "bar", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production", "project": "prod-project",
@@ -315,7 +315,7 @@ func TestGenerateParams(t *testing.T) {
testCase.clientError, testCase.clientError,
} }
clusterGenerator := NewClusterGenerator(t.Context(), cl, appClientset, "namespace") clusterGenerator := NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@@ -398,7 +398,7 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
selector metav1.LabelSelector selector metav1.LabelSelector
values map[string]string values map[string]string
isFlatMode bool isFlatMode bool
expected []map[string]any expected []map[string]interface{}
// clientError is true if a k8s client error should be simulated // clientError is true if a k8s client error should be simulated
clientError bool clientError bool
expectedError error expectedError error
@@ -415,13 +415,13 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"bat": "{{ if not (empty .metadata) }}{{.metadata.labels.environment}}{{ end }}", "bat": "{{ if not (empty .metadata) }}{{.metadata.labels.environment}}{{ end }}",
"aaa": "{{ .server }}", "aaa": "{{ .server }}",
"no-op": "{{ .thisDoesNotExist }}", "no-op": "{{ .thisDoesNotExist }}",
}, expected: []map[string]any{ }, expected: []map[string]interface{}{
{ {
"name": "production_01/west", "name": "production_01/west",
"nameNormalized": "production-01-west", "nameNormalized": "production-01-west",
"server": "https://production-01.example.com", "server": "https://production-01.example.com",
"project": "", "project": "",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
"environment": "production", "environment": "production",
@@ -447,7 +447,7 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"nameNormalized": "staging-01", "nameNormalized": "staging-01",
"server": "https://staging-01.example.com", "server": "https://staging-01.example.com",
"project": "", "project": "",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
"environment": "staging", "environment": "staging",
@@ -496,13 +496,13 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
}, },
}, },
values: nil, values: nil,
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"name": "production_01/west", "name": "production_01/west",
"nameNormalized": "production-01-west", "nameNormalized": "production-01-west",
"server": "https://production-01.example.com", "server": "https://production-01.example.com",
"project": "", "project": "",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
"environment": "production", "environment": "production",
@@ -518,7 +518,7 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"nameNormalized": "staging-01", "nameNormalized": "staging-01",
"server": "https://staging-01.example.com", "server": "https://staging-01.example.com",
"project": "", "project": "",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
"environment": "staging", "environment": "staging",
@@ -543,13 +543,13 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
values: map[string]string{ values: map[string]string{
"foo": "bar", "foo": "bar",
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"name": "production_01/west", "name": "production_01/west",
"nameNormalized": "production-01-west", "nameNormalized": "production-01-west",
"server": "https://production-01.example.com", "server": "https://production-01.example.com",
"project": "", "project": "",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
"environment": "production", "environment": "production",
@@ -584,13 +584,13 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
values: map[string]string{ values: map[string]string{
"foo": "bar", "foo": "bar",
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"name": "production_01/west", "name": "production_01/west",
"nameNormalized": "production-01-west", "nameNormalized": "production-01-west",
"server": "https://production-01.example.com", "server": "https://production-01.example.com",
"project": "", "project": "",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
"environment": "production", "environment": "production",
@@ -609,7 +609,7 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"nameNormalized": "staging-01", "nameNormalized": "staging-01",
"server": "https://staging-01.example.com", "server": "https://staging-01.example.com",
"project": "", "project": "",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
"environment": "staging", "environment": "staging",
@@ -647,13 +647,13 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
values: map[string]string{ values: map[string]string{
"name": "baz", "name": "baz",
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"name": "staging-01", "name": "staging-01",
"nameNormalized": "staging-01", "nameNormalized": "staging-01",
"server": "https://staging-01.example.com", "server": "https://staging-01.example.com",
"project": "", "project": "",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
"environment": "staging", "environment": "staging",
@@ -677,7 +677,7 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
values: nil, values: nil,
expected: nil, expected: nil,
clientError: true, clientError: true,
expectedError: errors.New("error getting cluster secrets: could not list Secrets"), expectedError: fmt.Errorf("error getting cluster secrets: could not list Secrets"),
}, },
{ {
name: "Clusters with flat list mode and no selector", name: "Clusters with flat list mode and no selector",
@@ -693,9 +693,9 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"aaa": "{{ .server }}", "aaa": "{{ .server }}",
"no-op": "{{ .thisDoesNotExist }}", "no-op": "{{ .thisDoesNotExist }}",
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"clusters": []map[string]any{ "clusters": []map[string]interface{}{
{ {
"nameNormalized": "in-cluster", "nameNormalized": "in-cluster",
"name": "in-cluster", "name": "in-cluster",
@@ -717,7 +717,7 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"nameNormalized": "production-01-west", "nameNormalized": "production-01-west",
"server": "https://production-01.example.com", "server": "https://production-01.example.com",
"project": "", "project": "",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
"environment": "production", "environment": "production",
@@ -743,7 +743,7 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"nameNormalized": "staging-01", "nameNormalized": "staging-01",
"server": "https://staging-01.example.com", "server": "https://staging-01.example.com",
"project": "", "project": "",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
"environment": "staging", "environment": "staging",
@@ -788,15 +788,15 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
values: map[string]string{ values: map[string]string{
"foo": "bar", "foo": "bar",
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"clusters": []map[string]any{ "clusters": []map[string]interface{}{
{ {
"name": "production_01/west", "name": "production_01/west",
"nameNormalized": "production-01-west", "nameNormalized": "production-01-west",
"server": "https://production-01.example.com", "server": "https://production-01.example.com",
"project": "", "project": "",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
"environment": "production", "environment": "production",
@@ -815,7 +815,7 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
"nameNormalized": "staging-01", "nameNormalized": "staging-01",
"server": "https://staging-01.example.com", "server": "https://staging-01.example.com",
"project": "", "project": "",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
"environment": "staging", "environment": "staging",
@@ -853,7 +853,7 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
testCase.clientError, testCase.clientError,
} }
clusterGenerator := NewClusterGenerator(t.Context(), cl, appClientset, "namespace") clusterGenerator := NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{

View File

@@ -2,7 +2,6 @@ package generators
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings" "strings"
"time" "time"
@@ -10,33 +9,38 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v2/util/settings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"github.com/argoproj/argo-cd/v3/applicationset/utils" "github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
var _ Generator = (*DuckTypeGenerator)(nil) var _ Generator = (*DuckTypeGenerator)(nil)
// DuckTypeGenerator generates Applications for some or all clusters registered with ArgoCD. // DuckTypeGenerator generates Applications for some or all clusters registered with ArgoCD.
type DuckTypeGenerator struct { type DuckTypeGenerator struct {
ctx context.Context ctx context.Context
dynClient dynamic.Interface dynClient dynamic.Interface
clientset kubernetes.Interface clientset kubernetes.Interface
namespace string // namespace is the Argo CD namespace namespace string // namespace is the Argo CD namespace
settingsManager *settings.SettingsManager
} }
func NewDuckTypeGenerator(ctx context.Context, dynClient dynamic.Interface, clientset kubernetes.Interface, namespace string) Generator { func NewDuckTypeGenerator(ctx context.Context, dynClient dynamic.Interface, clientset kubernetes.Interface, namespace string) Generator {
settingsManager := settings.NewSettingsManager(ctx, clientset, namespace)
g := &DuckTypeGenerator{ g := &DuckTypeGenerator{
ctx: ctx, ctx: ctx,
dynClient: dynClient, dynClient: dynClient,
clientset: clientset, clientset: clientset,
namespace: namespace, namespace: namespace,
settingsManager: settingsManager,
} }
return g return g
} }
@@ -55,14 +59,14 @@ func (g *DuckTypeGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Appl
return &appSetGenerator.ClusterDecisionResource.Template return &appSetGenerator.ClusterDecisionResource.Template
} }
func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) { func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
if appSetGenerator == nil { if appSetGenerator == nil {
return nil, ErrEmptyAppSetGenerator return nil, EmptyAppSetGeneratorError
} }
// Not likely to happen // Not likely to happen
if appSetGenerator.ClusterDecisionResource == nil { if appSetGenerator.ClusterDecisionResource == nil {
return nil, ErrEmptyAppSetGenerator return nil, EmptyAppSetGeneratorError
} }
// ListCluster from Argo CD's util/db package will include the local cluster in the list of clusters // ListCluster from Argo CD's util/db package will include the local cluster in the list of clusters
@@ -92,13 +96,13 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
// Validate the fields // Validate the fields
if kind == "" || versionIdx < 1 { if kind == "" || versionIdx < 1 {
log.Warningf("kind=%v, resourceName=%v, versionIdx=%v", kind, resourceName, versionIdx) log.Warningf("kind=%v, resourceName=%v, versionIdx=%v", kind, resourceName, versionIdx)
return nil, errors.New("there is a problem with the apiVersion, kind or resourceName provided") return nil, fmt.Errorf("There is a problem with the apiVersion, kind or resourceName provided")
} }
if (resourceName == "" && labelSelector.MatchLabels == nil && labelSelector.MatchExpressions == nil) || if (resourceName == "" && labelSelector.MatchLabels == nil && labelSelector.MatchExpressions == nil) ||
(resourceName != "" && (labelSelector.MatchExpressions != nil || labelSelector.MatchLabels != nil)) { (resourceName != "" && (labelSelector.MatchExpressions != nil || labelSelector.MatchLabels != nil)) {
log.Warningf("You must choose either resourceName=%v, labelSelector.matchLabels=%v or labelSelect.matchExpressions=%v", resourceName, labelSelector.MatchLabels, labelSelector.MatchExpressions) log.Warningf("You must choose either resourceName=%v, labelSelector.matchLabels=%v or labelSelect.matchExpressions=%v", resourceName, labelSelector.MatchLabels, labelSelector.MatchExpressions)
return nil, errors.New("there is a problem with the definition of the ClusterDecisionResource generator") return nil, fmt.Errorf("There is a problem with the definition of the ClusterDecisionResource generator")
} }
// Split up the apiVersion // Split up the apiVersion
@@ -126,104 +130,97 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
if len(duckResources.Items) == 0 { if len(duckResources.Items) == 0 {
log.Warning("no resource found, make sure you clusterDecisionResource is defined correctly") log.Warning("no resource found, make sure you clusterDecisionResource is defined correctly")
return nil, errors.New("no clusterDecisionResources found") return nil, fmt.Errorf("no clusterDecisionResources found")
} }
// Override the duck type in the status of the resource // Override the duck type in the status of the resource
statusListKey := "clusters" statusListKey := "clusters"
matchKey := cm.Data["matchKey"]
if cm.Data["statusListKey"] != "" { if cm.Data["statusListKey"] != "" {
statusListKey = cm.Data["statusListKey"] statusListKey = cm.Data["statusListKey"]
} }
matchKey := cm.Data["matchKey"]
if matchKey == "" { if matchKey == "" {
log.WithField("matchKey", matchKey).Warning("matchKey not found in " + cm.Name) log.WithField("matchKey", matchKey).Warning("matchKey not found in " + cm.Name)
return nil, nil return nil, nil
} }
clusterDecisions := buildClusterDecisions(duckResources, statusListKey) res := []map[string]interface{}{}
if len(clusterDecisions) == 0 { clusterDecisions := []interface{}{}
log.Warningf("clusterDecisionResource status.%s missing", statusListKey)
return nil, nil
}
res := []map[string]any{}
for _, clusterDecision := range clusterDecisions {
cluster := findCluster(clustersFromArgoCD, clusterDecision, matchKey, statusListKey)
// if no cluster is found, move to the next cluster
if cluster == nil {
continue
}
// generated instance of cluster params
params := map[string]any{
"name": cluster.Name,
"server": cluster.Server,
}
for key, value := range clusterDecision.(map[string]any) {
params[key] = value.(string)
}
for key, value := range appSetGenerator.ClusterDecisionResource.Values {
collectParams(appSet, params, key, value)
}
res = append(res, params)
}
return res, nil
}
func buildClusterDecisions(duckResources *unstructured.UnstructuredList, statusListKey string) []any {
clusterDecisions := []any{}
// Build the decision slice // Build the decision slice
for _, duckResource := range duckResources.Items { for _, duckResource := range duckResources.Items {
log.WithField("duckResourceName", duckResource.GetName()).Debug("found resource") log.WithField("duckResourceName", duckResource.GetName()).Debug("found resource")
if duckResource.Object["status"] == nil || len(duckResource.Object["status"].(map[string]any)) == 0 { if duckResource.Object["status"] == nil || len(duckResource.Object["status"].(map[string]interface{})) == 0 {
log.Warningf("clusterDecisionResource: %s, has no status", duckResource.GetName()) log.Warningf("clusterDecisionResource: %s, has no status", duckResource.GetName())
continue continue
} }
log.WithField("duckResourceStatus", duckResource.Object["status"]).Debug("found resource") log.WithField("duckResourceStatus", duckResource.Object["status"]).Debug("found resource")
clusterDecisions = append(clusterDecisions, duckResource.Object["status"].(map[string]any)[statusListKey].([]any)...) clusterDecisions = append(clusterDecisions, duckResource.Object["status"].(map[string]interface{})[statusListKey].([]interface{})...)
} }
log.Infof("Number of decisions found: %v", len(clusterDecisions)) log.Infof("Number of decisions found: %v", len(clusterDecisions))
return clusterDecisions
}
func findCluster(clustersFromArgoCD []utils.ClusterSpecifier, cluster any, matchKey string, statusListKey string) *utils.ClusterSpecifier { // Read this outside the loop to improve performance
log.Infof("cluster: %v", cluster) argoClusters := clustersFromArgoCD.Items
matchValue := cluster.(map[string]any)[matchKey]
if matchValue == nil || matchValue.(string) == "" {
log.Warningf("matchKey=%v not found in \"%v\" list: %v\n", matchKey, statusListKey, cluster.(map[string]any))
return nil // no match
}
strMatchValue := matchValue.(string) if len(clusterDecisions) > 0 {
log.WithField(matchKey, strMatchValue).Debug("validate against ArgoCD") for _, cluster := range clusterDecisions {
// generated instance of cluster params
params := map[string]interface{}{}
for _, argoCluster := range clustersFromArgoCD { log.Infof("cluster: %v", cluster)
if argoCluster.Name == strMatchValue { matchValue := cluster.(map[string]interface{})[matchKey]
log.WithField(matchKey, argoCluster.Name).Info("matched cluster in ArgoCD") if matchValue == nil || matchValue.(string) == "" {
return &argoCluster log.Warningf("matchKey=%v not found in \"%v\" list: %v\n", matchKey, statusListKey, cluster.(map[string]interface{}))
continue
}
strMatchValue := matchValue.(string)
log.WithField(matchKey, strMatchValue).Debug("validate against ArgoCD")
found := false
for _, argoCluster := range argoClusters {
if argoCluster.Name == strMatchValue {
log.WithField(matchKey, argoCluster.Name).Info("matched cluster in ArgoCD")
params["name"] = argoCluster.Name
params["server"] = argoCluster.Server
found = true
break // Stop looking
}
}
if !found {
log.WithField(matchKey, strMatchValue).Warning("unmatched cluster in ArgoCD")
continue
}
for key, value := range cluster.(map[string]interface{}) {
params[key] = value.(string)
}
for key, value := range appSetGenerator.ClusterDecisionResource.Values {
if appSet.Spec.GoTemplate {
if params["values"] == nil {
params["values"] = map[string]string{}
}
params["values"].(map[string]string)[key] = value
} else {
params[fmt.Sprintf("values.%s", key)] = value
}
}
res = append(res, params)
} }
}
log.WithField(matchKey, strMatchValue).Warning("unmatched cluster in ArgoCD")
return nil
}
func collectParams(appSet *argoprojiov1alpha1.ApplicationSet, params map[string]any, key string, value string) {
if appSet.Spec.GoTemplate {
if params["values"] == nil {
params["values"] = map[string]string{}
}
params["values"].(map[string]string)[key] = value
} else { } else {
params["values."+key] = value log.Warningf("clusterDecisionResource status.%s missing", statusListKey)
return nil, nil
} }
return res, nil
} }

View File

@@ -1,7 +1,8 @@
package generators package generators
import ( import (
"errors" "context"
"fmt"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -15,11 +16,11 @@ import (
kubefake "k8s.io/client-go/kubernetes/fake" kubefake "k8s.io/client-go/kubernetes/fake"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
const ( const (
resourceAPIVersion = "mallard.io/v1" resourceApiVersion = "mallard.io/v1"
resourceKind = "ducks" resourceKind = "ducks"
resourceName = "quak" resourceName = "quak"
) )
@@ -77,20 +78,20 @@ func TestGenerateParamsForDuckType(t *testing.T) {
} }
duckType := &unstructured.Unstructured{ duckType := &unstructured.Unstructured{
Object: map[string]any{ Object: map[string]interface{}{
"apiVersion": resourceAPIVersion, "apiVersion": resourceApiVersion,
"kind": "Duck", "kind": "Duck",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"name": resourceName, "name": resourceName,
"namespace": "namespace", "namespace": "namespace",
"labels": map[string]any{"duck": "all-species"}, "labels": map[string]interface{}{"duck": "all-species"},
}, },
"status": map[string]any{ "status": map[string]interface{}{
"decisions": []any{ "decisions": []interface{}{
map[string]any{ map[string]interface{}{
"clusterName": "staging-01", "clusterName": "staging-01",
}, },
map[string]any{ map[string]interface{}{
"clusterName": "production-01", "clusterName": "production-01",
}, },
}, },
@@ -99,17 +100,17 @@ func TestGenerateParamsForDuckType(t *testing.T) {
} }
duckTypeProdOnly := &unstructured.Unstructured{ duckTypeProdOnly := &unstructured.Unstructured{
Object: map[string]any{ Object: map[string]interface{}{
"apiVersion": resourceAPIVersion, "apiVersion": resourceApiVersion,
"kind": "Duck", "kind": "Duck",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"name": resourceName, "name": resourceName,
"namespace": "namespace", "namespace": "namespace",
"labels": map[string]any{"duck": "spotted"}, "labels": map[string]interface{}{"duck": "spotted"},
}, },
"status": map[string]any{ "status": map[string]interface{}{
"decisions": []any{ "decisions": []interface{}{
map[string]any{ map[string]interface{}{
"clusterName": "production-01", "clusterName": "production-01",
}, },
}, },
@@ -118,15 +119,15 @@ func TestGenerateParamsForDuckType(t *testing.T) {
} }
duckTypeEmpty := &unstructured.Unstructured{ duckTypeEmpty := &unstructured.Unstructured{
Object: map[string]any{ Object: map[string]interface{}{
"apiVersion": resourceAPIVersion, "apiVersion": resourceApiVersion,
"kind": "Duck", "kind": "Duck",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"name": resourceName, "name": resourceName,
"namespace": "namespace", "namespace": "namespace",
"labels": map[string]any{"duck": "canvasback"}, "labels": map[string]interface{}{"duck": "canvasback"},
}, },
"status": map[string]any{}, "status": map[string]interface{}{},
}, },
} }
@@ -136,7 +137,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
Namespace: "namespace", Namespace: "namespace",
}, },
Data: map[string]string{ Data: map[string]string{
"apiVersion": resourceAPIVersion, "apiVersion": resourceApiVersion,
"kind": resourceKind, "kind": resourceKind,
"statusListKey": "decisions", "statusListKey": "decisions",
"matchKey": "clusterName", "matchKey": "clusterName",
@@ -150,7 +151,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
labelSelector metav1.LabelSelector labelSelector metav1.LabelSelector
resource *unstructured.Unstructured resource *unstructured.Unstructured
values map[string]string values map[string]string
expected []map[string]any expected []map[string]interface{}
expectedError error expectedError error
}{ }{
{ {
@@ -158,8 +159,8 @@ func TestGenerateParamsForDuckType(t *testing.T) {
resourceName: "", resourceName: "",
resource: duckType, resource: duckType,
values: nil, values: nil,
expected: []map[string]any{}, expected: []map[string]interface{}{},
expectedError: errors.New("there is a problem with the definition of the ClusterDecisionResource generator"), expectedError: fmt.Errorf("There is a problem with the definition of the ClusterDecisionResource generator"),
}, },
/*** This does not work with the FAKE runtime client, fieldSelectors are broken. /*** This does not work with the FAKE runtime client, fieldSelectors are broken.
{ {
@@ -176,7 +177,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
resourceName: resourceName, resourceName: resourceName,
resource: duckType, resource: duckType,
values: nil, values: nil,
expected: []map[string]any{ expected: []map[string]interface{}{
{"clusterName": "production-01", "name": "production-01", "server": "https://production-01.example.com"}, {"clusterName": "production-01", "name": "production-01", "server": "https://production-01.example.com"},
{"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"}, {"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"},
@@ -190,7 +191,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
values: map[string]string{ values: map[string]string{
"foo": "bar", "foo": "bar",
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{"clusterName": "production-01", "values.foo": "bar", "name": "production-01", "server": "https://production-01.example.com"}, {"clusterName": "production-01", "values.foo": "bar", "name": "production-01", "server": "https://production-01.example.com"},
}, },
expectedError: nil, expectedError: nil,
@@ -218,7 +219,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
labelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"duck": "all-species"}}, labelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"duck": "all-species"}},
resource: duckType, resource: duckType,
values: nil, values: nil,
expected: []map[string]any{ expected: []map[string]interface{}{
{"clusterName": "production-01", "name": "production-01", "server": "https://production-01.example.com"}, {"clusterName": "production-01", "name": "production-01", "server": "https://production-01.example.com"},
{"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"}, {"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"},
@@ -233,7 +234,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
values: map[string]string{ values: map[string]string{
"foo": "bar", "foo": "bar",
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{"clusterName": "production-01", "values.foo": "bar", "name": "production-01", "server": "https://production-01.example.com"}, {"clusterName": "production-01", "values.foo": "bar", "name": "production-01", "server": "https://production-01.example.com"},
}, },
expectedError: nil, expectedError: nil,
@@ -250,7 +251,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
}}, }},
resource: duckType, resource: duckType,
values: nil, values: nil,
expected: []map[string]any{ expected: []map[string]interface{}{
{"clusterName": "production-01", "name": "production-01", "server": "https://production-01.example.com"}, {"clusterName": "production-01", "name": "production-01", "server": "https://production-01.example.com"},
{"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"}, {"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"},
@@ -270,7 +271,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
resource: duckType, resource: duckType,
values: nil, values: nil,
expected: nil, expected: nil,
expectedError: errors.New("there is a problem with the definition of the ClusterDecisionResource generator"), expectedError: fmt.Errorf("There is a problem with the definition of the ClusterDecisionResource generator"),
}, },
} }
@@ -292,7 +293,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, testCase.resource) fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, testCase.resource)
duckTypeGenerator := NewDuckTypeGenerator(t.Context(), fakeDynClient, appClientset, "namespace") duckTypeGenerator := NewDuckTypeGenerator(context.Background(), fakeDynClient, appClientset, "namespace")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@@ -373,20 +374,20 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
} }
duckType := &unstructured.Unstructured{ duckType := &unstructured.Unstructured{
Object: map[string]any{ Object: map[string]interface{}{
"apiVersion": resourceAPIVersion, "apiVersion": resourceApiVersion,
"kind": "Duck", "kind": "Duck",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"name": resourceName, "name": resourceName,
"namespace": "namespace", "namespace": "namespace",
"labels": map[string]any{"duck": "all-species"}, "labels": map[string]interface{}{"duck": "all-species"},
}, },
"status": map[string]any{ "status": map[string]interface{}{
"decisions": []any{ "decisions": []interface{}{
map[string]any{ map[string]interface{}{
"clusterName": "staging-01", "clusterName": "staging-01",
}, },
map[string]any{ map[string]interface{}{
"clusterName": "production-01", "clusterName": "production-01",
}, },
}, },
@@ -395,17 +396,17 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
} }
duckTypeProdOnly := &unstructured.Unstructured{ duckTypeProdOnly := &unstructured.Unstructured{
Object: map[string]any{ Object: map[string]interface{}{
"apiVersion": resourceAPIVersion, "apiVersion": resourceApiVersion,
"kind": "Duck", "kind": "Duck",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"name": resourceName, "name": resourceName,
"namespace": "namespace", "namespace": "namespace",
"labels": map[string]any{"duck": "spotted"}, "labels": map[string]interface{}{"duck": "spotted"},
}, },
"status": map[string]any{ "status": map[string]interface{}{
"decisions": []any{ "decisions": []interface{}{
map[string]any{ map[string]interface{}{
"clusterName": "production-01", "clusterName": "production-01",
}, },
}, },
@@ -414,15 +415,15 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
} }
duckTypeEmpty := &unstructured.Unstructured{ duckTypeEmpty := &unstructured.Unstructured{
Object: map[string]any{ Object: map[string]interface{}{
"apiVersion": resourceAPIVersion, "apiVersion": resourceApiVersion,
"kind": "Duck", "kind": "Duck",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"name": resourceName, "name": resourceName,
"namespace": "namespace", "namespace": "namespace",
"labels": map[string]any{"duck": "canvasback"}, "labels": map[string]interface{}{"duck": "canvasback"},
}, },
"status": map[string]any{}, "status": map[string]interface{}{},
}, },
} }
@@ -432,7 +433,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
Namespace: "namespace", Namespace: "namespace",
}, },
Data: map[string]string{ Data: map[string]string{
"apiVersion": resourceAPIVersion, "apiVersion": resourceApiVersion,
"kind": resourceKind, "kind": resourceKind,
"statusListKey": "decisions", "statusListKey": "decisions",
"matchKey": "clusterName", "matchKey": "clusterName",
@@ -446,7 +447,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
labelSelector metav1.LabelSelector labelSelector metav1.LabelSelector
resource *unstructured.Unstructured resource *unstructured.Unstructured
values map[string]string values map[string]string
expected []map[string]any expected []map[string]interface{}
expectedError error expectedError error
}{ }{
{ {
@@ -454,8 +455,8 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
resourceName: "", resourceName: "",
resource: duckType, resource: duckType,
values: nil, values: nil,
expected: []map[string]any{}, expected: []map[string]interface{}{},
expectedError: errors.New("there is a problem with the definition of the ClusterDecisionResource generator"), expectedError: fmt.Errorf("There is a problem with the definition of the ClusterDecisionResource generator"),
}, },
/*** This does not work with the FAKE runtime client, fieldSelectors are broken. /*** This does not work with the FAKE runtime client, fieldSelectors are broken.
{ {
@@ -472,7 +473,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
resourceName: resourceName, resourceName: resourceName,
resource: duckType, resource: duckType,
values: nil, values: nil,
expected: []map[string]any{ expected: []map[string]interface{}{
{"clusterName": "production-01", "name": "production-01", "server": "https://production-01.example.com"}, {"clusterName": "production-01", "name": "production-01", "server": "https://production-01.example.com"},
{"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"}, {"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"},
@@ -486,7 +487,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
values: map[string]string{ values: map[string]string{
"foo": "bar", "foo": "bar",
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{"clusterName": "production-01", "values": map[string]string{"foo": "bar"}, "name": "production-01", "server": "https://production-01.example.com"}, {"clusterName": "production-01", "values": map[string]string{"foo": "bar"}, "name": "production-01", "server": "https://production-01.example.com"},
}, },
expectedError: nil, expectedError: nil,
@@ -514,7 +515,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
labelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"duck": "all-species"}}, labelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"duck": "all-species"}},
resource: duckType, resource: duckType,
values: nil, values: nil,
expected: []map[string]any{ expected: []map[string]interface{}{
{"clusterName": "production-01", "name": "production-01", "server": "https://production-01.example.com"}, {"clusterName": "production-01", "name": "production-01", "server": "https://production-01.example.com"},
{"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"}, {"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"},
@@ -529,7 +530,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
values: map[string]string{ values: map[string]string{
"foo": "bar", "foo": "bar",
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{"clusterName": "production-01", "values": map[string]string{"foo": "bar"}, "name": "production-01", "server": "https://production-01.example.com"}, {"clusterName": "production-01", "values": map[string]string{"foo": "bar"}, "name": "production-01", "server": "https://production-01.example.com"},
}, },
expectedError: nil, expectedError: nil,
@@ -546,7 +547,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
}}, }},
resource: duckType, resource: duckType,
values: nil, values: nil,
expected: []map[string]any{ expected: []map[string]interface{}{
{"clusterName": "production-01", "name": "production-01", "server": "https://production-01.example.com"}, {"clusterName": "production-01", "name": "production-01", "server": "https://production-01.example.com"},
{"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"}, {"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"},
@@ -566,7 +567,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
resource: duckType, resource: duckType,
values: nil, values: nil,
expected: nil, expected: nil,
expectedError: errors.New("there is a problem with the definition of the ClusterDecisionResource generator"), expectedError: fmt.Errorf("There is a problem with the definition of the ClusterDecisionResource generator"),
}, },
} }
@@ -588,7 +589,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, testCase.resource) fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, testCase.resource)
duckTypeGenerator := NewDuckTypeGenerator(t.Context(), fakeDynClient, appClientset, "namespace") duckTypeGenerator := NewDuckTypeGenerator(context.Background(), fakeDynClient, appClientset, "namespace")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{

View File

@@ -7,13 +7,13 @@ import (
"github.com/jeremywohl/flatten" "github.com/jeremywohl/flatten"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v3/applicationset/utils" "github.com/argoproj/argo-cd/v2/applicationset/utils"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"dario.cat/mergo" "github.com/imdario/mergo"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@@ -22,12 +22,12 @@ const (
) )
type TransformResult struct { type TransformResult struct {
Params []map[string]any Params []map[string]interface{}
Template argoprojiov1alpha1.ApplicationSetTemplate Template argoprojiov1alpha1.ApplicationSetTemplate
} }
// Transform a spec generator to list of paramSets and a template // Transform a spec generator to list of paramSets and a template
func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, allGenerators map[string]Generator, baseTemplate argoprojiov1alpha1.ApplicationSetTemplate, appSet *argoprojiov1alpha1.ApplicationSet, genParams map[string]any, client client.Client) ([]TransformResult, error) { func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, allGenerators map[string]Generator, baseTemplate argoprojiov1alpha1.ApplicationSetTemplate, appSet *argoprojiov1alpha1.ApplicationSet, genParams map[string]interface{}, client client.Client) ([]TransformResult, error) {
// This is a custom version of the `LabelSelectorAsSelector` that is in k8s.io/apimachinery. This has been copied // This is a custom version of the `LabelSelectorAsSelector` that is in k8s.io/apimachinery. This has been copied
// verbatim from that package, with the difference that we do not have any restrictions on label values. This is done // verbatim from that package, with the difference that we do not have any restrictions on label values. This is done
// so that, among other things, we can match on cluster urls. // so that, among other things, we can match on cluster urls.
@@ -52,7 +52,7 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
} }
continue continue
} }
var params []map[string]any var params []map[string]interface{}
if len(genParams) != 0 { if len(genParams) != 0 {
tempInterpolatedGenerator, err := InterpolateGenerator(&requestedGenerator, genParams, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions) tempInterpolatedGenerator, err := InterpolateGenerator(&requestedGenerator, genParams, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
interpolatedGenerator = &tempInterpolatedGenerator interpolatedGenerator = &tempInterpolatedGenerator
@@ -74,7 +74,7 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
} }
continue continue
} }
var filterParams []map[string]any var filterParams []map[string]interface{}
for _, param := range params { for _, param := range params {
flatParam, err := flattenParameters(param) flatParam, err := flattenParameters(param)
if err != nil { if err != nil {
@@ -123,7 +123,7 @@ func GetRelevantGenerators(requestedGenerator *argoprojiov1alpha1.ApplicationSet
return res return res
} }
func flattenParameters(in map[string]any) (map[string]string, error) { func flattenParameters(in map[string]interface{}) (map[string]string, error) {
flat, err := flatten.Flatten(in, "", flatten.DotStyle) flat, err := flatten.Flatten(in, "", flatten.DotStyle)
if err != nil { if err != nil {
return nil, fmt.Errorf("error flatenning parameters: %w", err) return nil, fmt.Errorf("error flatenning parameters: %w", err)
@@ -149,7 +149,7 @@ func mergeGeneratorTemplate(g Generator, requestedGenerator *argoprojiov1alpha1.
// InterpolateGenerator allows interpolating the matrix's 2nd child generator with values from the 1st child generator // InterpolateGenerator allows interpolating the matrix's 2nd child generator with values from the 1st child generator
// "params" parameter is an array, where each index corresponds to a generator. Each index contains a map w/ that generator's parameters. // "params" parameter is an array, where each index corresponds to a generator. Each index contains a map w/ that generator's parameters.
func InterpolateGenerator(requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, params map[string]any, useGoTemplate bool, goTemplateOptions []string) (argoprojiov1alpha1.ApplicationSetGenerator, error) { func InterpolateGenerator(requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, params map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (argoprojiov1alpha1.ApplicationSetGenerator, error) {
render := utils.Render{} render := utils.Render{}
interpolatedGenerator, err := render.RenderGeneratorParams(requestedGenerator, params, useGoTemplate, goTemplateOptions) interpolatedGenerator, err := render.RenderGeneratorParams(requestedGenerator, params, useGoTemplate, goTemplateOptions)
if err != nil { if err != nil {
@@ -159,3 +159,16 @@ func InterpolateGenerator(requestedGenerator *argoprojiov1alpha1.ApplicationSetG
return *interpolatedGenerator, nil return *interpolatedGenerator, nil
} }
// Fixes https://github.com/argoproj/argo-cd/issues/11982 while ensuring backwards compatibility.
// This is only a short-term solution and should be removed in a future major version.
func dropDisabledNestedSelectors(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator) bool {
var foundSelector bool
for i := range generators {
if generators[i].Selector != nil {
foundSelector = true
generators[i].Selector = nil
}
}
return foundSelector
}

View File

@@ -10,9 +10,9 @@ import (
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/argoproj/argo-cd/v3/applicationset/services/mocks" "github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
argov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@@ -27,19 +27,19 @@ func TestMatchValues(t *testing.T) {
name string name string
elements []apiextensionsv1.JSON elements []apiextensionsv1.JSON
selector *metav1.LabelSelector selector *metav1.LabelSelector
expected []map[string]any expected []map[string]interface{}
}{ }{
{ {
name: "no filter", name: "no filter",
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}}, elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
selector: &metav1.LabelSelector{}, selector: &metav1.LabelSelector{},
expected: []map[string]any{{"cluster": "cluster", "url": "url"}}, expected: []map[string]interface{}{{"cluster": "cluster", "url": "url"}},
}, },
{ {
name: "nil", name: "nil",
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}}, elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
selector: nil, selector: nil,
expected: []map[string]any{{"cluster": "cluster", "url": "url"}}, expected: []map[string]interface{}{{"cluster": "cluster", "url": "url"}},
}, },
{ {
name: "values.foo should be foo but is ignore element", name: "values.foo should be foo but is ignore element",
@@ -49,7 +49,7 @@ func TestMatchValues(t *testing.T) {
"values.foo": "foo", "values.foo": "foo",
}, },
}, },
expected: []map[string]any{}, expected: []map[string]interface{}{},
}, },
{ {
name: "values.foo should be bar", name: "values.foo should be bar",
@@ -59,7 +59,7 @@ func TestMatchValues(t *testing.T) {
"values.foo": "bar", "values.foo": "bar",
}, },
}, },
expected: []map[string]any{{"cluster": "cluster", "url": "url", "values.foo": "bar"}}, expected: []map[string]interface{}{{"cluster": "cluster", "url": "url", "values.foo": "bar"}},
}, },
} }
@@ -101,19 +101,19 @@ func TestMatchValuesGoTemplate(t *testing.T) {
name string name string
elements []apiextensionsv1.JSON elements []apiextensionsv1.JSON
selector *metav1.LabelSelector selector *metav1.LabelSelector
expected []map[string]any expected []map[string]interface{}
}{ }{
{ {
name: "no filter", name: "no filter",
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}}, elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
selector: &metav1.LabelSelector{}, selector: &metav1.LabelSelector{},
expected: []map[string]any{{"cluster": "cluster", "url": "url"}}, expected: []map[string]interface{}{{"cluster": "cluster", "url": "url"}},
}, },
{ {
name: "nil", name: "nil",
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}}, elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
selector: nil, selector: nil,
expected: []map[string]any{{"cluster": "cluster", "url": "url"}}, expected: []map[string]interface{}{{"cluster": "cluster", "url": "url"}},
}, },
{ {
name: "values.foo should be foo but is ignore element", name: "values.foo should be foo but is ignore element",
@@ -123,7 +123,7 @@ func TestMatchValuesGoTemplate(t *testing.T) {
"values.foo": "foo", "values.foo": "foo",
}, },
}, },
expected: []map[string]any{}, expected: []map[string]interface{}{},
}, },
{ {
name: "values.foo should be bar", name: "values.foo should be bar",
@@ -133,7 +133,7 @@ func TestMatchValuesGoTemplate(t *testing.T) {
"values.foo": "bar", "values.foo": "bar",
}, },
}, },
expected: []map[string]any{{"cluster": "cluster", "url": "url", "values": map[string]any{"foo": "bar"}}}, expected: []map[string]interface{}{{"cluster": "cluster", "url": "url", "values": map[string]interface{}{"foo": "bar"}}},
}, },
{ {
name: "values.0 should be bar", name: "values.0 should be bar",
@@ -143,7 +143,7 @@ func TestMatchValuesGoTemplate(t *testing.T) {
"values.0": "bar", "values.0": "bar",
}, },
}, },
expected: []map[string]any{{"cluster": "cluster", "url": "url", "values": []any{"bar"}}}, expected: []map[string]interface{}{{"cluster": "cluster", "url": "url", "values": []interface{}{"bar"}}},
}, },
} }
@@ -184,14 +184,14 @@ func TestTransForm(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
selector *metav1.LabelSelector selector *metav1.LabelSelector
expected []map[string]any expected []map[string]interface{}
}{ }{
{ {
name: "server filter", name: "server filter",
selector: &metav1.LabelSelector{ selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"server": "https://production-01.example.com"}, MatchLabels: map[string]string{"server": "https://production-01.example.com"},
}, },
expected: []map[string]any{{ expected: []map[string]interface{}{{
"metadata.annotations.foo.argoproj.io": "production", "metadata.annotations.foo.argoproj.io": "production",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.labels.argocd.argoproj.io/secret-type": "cluster",
"metadata.labels.environment": "production", "metadata.labels.environment": "production",
@@ -207,7 +207,7 @@ func TestTransForm(t *testing.T) {
selector: &metav1.LabelSelector{ selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"server": "https://some-really-long-url-that-will-exceed-63-characters.com"}, MatchLabels: map[string]string{"server": "https://some-really-long-url-that-will-exceed-63-characters.com"},
}, },
expected: []map[string]any{{ expected: []map[string]interface{}{{
"metadata.annotations.foo.argoproj.io": "production", "metadata.annotations.foo.argoproj.io": "production",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.labels.argocd.argoproj.io/secret-type": "cluster",
"metadata.labels.environment": "production", "metadata.labels.environment": "production",
@@ -342,7 +342,7 @@ func getMockClusterGenerator() Generator {
appClientset := kubefake.NewSimpleClientset(runtimeClusters...) appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build() fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
return NewClusterGenerator(context.Background(), fakeClient, appClientset, "namespace") return NewClusterGenerator(fakeClient, context.Background(), appClientset, "namespace")
} }
func getMockGitGenerator() Generator { func getMockGitGenerator() Generator {
@@ -413,7 +413,7 @@ func TestInterpolateGenerator(t *testing.T) {
}, },
}, },
} }
gitGeneratorParams := map[string]any{ gitGeneratorParams := map[string]interface{}{
"path": "p1/p2/app3", "path": "p1/p2/app3",
"path.basename": "app3", "path.basename": "app3",
"path[0]": "p1", "path[0]": "p1",
@@ -442,7 +442,7 @@ func TestInterpolateGenerator(t *testing.T) {
Template: argov1alpha1.ApplicationSetTemplate{}, Template: argov1alpha1.ApplicationSetTemplate{},
}, },
} }
clusterGeneratorParams := map[string]any{ clusterGeneratorParams := map[string]interface{}{
"name": "production_01/west", "server": "https://production-01.example.com", "name": "production_01/west", "server": "https://production-01.example.com",
} }
interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, false, nil) interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, false, nil)
@@ -468,8 +468,8 @@ func TestInterpolateGenerator_go(t *testing.T) {
}, },
}, },
} }
gitGeneratorParams := map[string]any{ gitGeneratorParams := map[string]interface{}{
"path": map[string]any{ "path": map[string]interface{}{
"path": "p1/p2/app3", "path": "p1/p2/app3",
"segments": []string{"p1", "p2", "app3"}, "segments": []string{"p1", "p2", "app3"},
}, },
@@ -497,7 +497,7 @@ func TestInterpolateGenerator_go(t *testing.T) {
Template: argov1alpha1.ApplicationSetTemplate{}, Template: argov1alpha1.ApplicationSetTemplate{},
}, },
} }
clusterGeneratorParams := map[string]any{ clusterGeneratorParams := map[string]interface{}{
"name": "production_01/west", "server": "https://production-01.example.com", "name": "production_01/west", "server": "https://production-01.example.com",
} }
interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, true, nil) interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, true, nil)
@@ -512,7 +512,7 @@ func TestInterpolateGenerator_go(t *testing.T) {
func TestInterpolateGeneratorError(t *testing.T) { func TestInterpolateGeneratorError(t *testing.T) {
type args struct { type args struct {
requestedGenerator *argov1alpha1.ApplicationSetGenerator requestedGenerator *argov1alpha1.ApplicationSetGenerator
params map[string]any params map[string]interface{}
useGoTemplate bool useGoTemplate bool
goTemplateOptions []string goTemplateOptions []string
} }
@@ -530,7 +530,7 @@ func TestInterpolateGeneratorError(t *testing.T) {
}, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: "generator is empty"}, }, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: "generator is empty"},
{name: "No Params", args: args{ {name: "No Params", args: args{
requestedGenerator: &argov1alpha1.ApplicationSetGenerator{}, requestedGenerator: &argov1alpha1.ApplicationSetGenerator{},
params: map[string]any{}, params: map[string]interface{}{},
useGoTemplate: false, useGoTemplate: false,
goTemplateOptions: nil, goTemplateOptions: nil,
}, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: ""}, }, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: ""},
@@ -545,7 +545,7 @@ func TestInterpolateGeneratorError(t *testing.T) {
"resolved": "{{ index .rmap (default .override .test) }}", "resolved": "{{ index .rmap (default .override .test) }}",
}, },
}}, }},
params: map[string]any{ params: map[string]interface{}{
"name": "in-cluster", "name": "in-cluster",
"override": "foo", "override": "foo",
}, },

View File

@@ -15,10 +15,10 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"github.com/argoproj/argo-cd/v3/applicationset/services" "github.com/argoproj/argo-cd/v2/applicationset/services"
"github.com/argoproj/argo-cd/v3/applicationset/utils" "github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v3/util/gpg" "github.com/argoproj/argo-cd/v2/util/gpg"
) )
var _ Generator = (*GitGenerator)(nil) var _ Generator = (*GitGenerator)(nil)
@@ -28,7 +28,6 @@ type GitGenerator struct {
namespace string namespace string
} }
// NewGitGenerator creates a new instance of Git Generator
func NewGitGenerator(repos services.Repos, namespace string) Generator { func NewGitGenerator(repos services.Repos, namespace string) Generator {
g := &GitGenerator{ g := &GitGenerator{
repos: repos, repos: repos,
@@ -38,17 +37,13 @@ func NewGitGenerator(repos services.Repos, namespace string) Generator {
return g return g
} }
// GetTemplate returns the ApplicationSetTemplate associated with the Git generator
// from the provided ApplicationSetGenerator. This template defines how each
// generated Argo CD Application should be rendered.
func (g *GitGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate { func (g *GitGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
return &appSetGenerator.Git.Template return &appSetGenerator.Git.Template
} }
// GetRequeueAfter returns the duration after which the Git generator should be
// requeued for reconciliation. If RequeueAfterSeconds is set in the generator spec,
// it uses that value. Otherwise, it falls back to a default requeue interval (3 minutes).
func (g *GitGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration { func (g *GitGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
// Return a requeue default of 3 minutes, if no default is specified.
if appSetGenerator.Git.RequeueAfterSeconds != nil { if appSetGenerator.Git.RequeueAfterSeconds != nil {
return time.Duration(*appSetGenerator.Git.RequeueAfterSeconds) * time.Second return time.Duration(*appSetGenerator.Git.RequeueAfterSeconds) * time.Second
} }
@@ -56,15 +51,13 @@ func (g *GitGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Appli
return getDefaultRequeueAfter() return getDefaultRequeueAfter()
} }
// GenerateParams generates a list of parameter maps for the ApplicationSet by evaluating the Git generator's configuration. func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]interface{}, error) {
// It supports both directory-based and file-based Git generators.
func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]any, error) {
if appSetGenerator == nil { if appSetGenerator == nil {
return nil, ErrEmptyAppSetGenerator return nil, EmptyAppSetGeneratorError
} }
if appSetGenerator.Git == nil { if appSetGenerator.Git == nil {
return nil, ErrEmptyAppSetGenerator return nil, EmptyAppSetGeneratorError
} }
noRevisionCache := appSet.RefreshRequired() noRevisionCache := appSet.RefreshRequired()
@@ -74,7 +67,6 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
// When the project field is templated, the contents of the git repo are required to run the git generator and get the templated value, // When the project field is templated, the contents of the git repo are required to run the git generator and get the templated value,
// but git generator cannot be called without verifying the commit signature. // but git generator cannot be called without verifying the commit signature.
// In this case, we skip the signature verification. // In this case, we skip the signature verification.
// If the project is templated, we skip the commit verification
if !strings.Contains(appSet.Spec.Template.Spec.Project, "{{") { if !strings.Contains(appSet.Spec.Template.Spec.Project, "{{") {
project := appSet.Spec.Template.Spec.Project project := appSet.Spec.Template.Spec.Project
appProject := &argoprojiov1alpha1.AppProject{} appProject := &argoprojiov1alpha1.AppProject{}
@@ -89,19 +81,14 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
verifyCommit = len(appProject.Spec.SignatureKeys) > 0 && gpg.IsGPGEnabled() verifyCommit = len(appProject.Spec.SignatureKeys) > 0 && gpg.IsGPGEnabled()
} }
// If the project field is templated, we cannot resolve the project name, so we pass an empty string to the repo-server.
// This means only "globally-scoped" repo credentials can be used for such appsets.
project := resolveProjectName(appSet.Spec.Template.Spec.Project)
var err error var err error
var res []map[string]any var res []map[string]interface{}
switch { if len(appSetGenerator.Git.Directories) != 0 {
case len(appSetGenerator.Git.Directories) != 0: res, err = g.generateParamsForGitDirectories(appSetGenerator, noRevisionCache, verifyCommit, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
res, err = g.generateParamsForGitDirectories(appSetGenerator, noRevisionCache, verifyCommit, appSet.Spec.GoTemplate, project, appSet.Spec.GoTemplateOptions) } else if len(appSetGenerator.Git.Files) != 0 {
case len(appSetGenerator.Git.Files) != 0: res, err = g.generateParamsForGitFiles(appSetGenerator, noRevisionCache, verifyCommit, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
res, err = g.generateParamsForGitFiles(appSetGenerator, noRevisionCache, verifyCommit, appSet.Spec.GoTemplate, project, appSet.Spec.GoTemplateOptions) } else {
default: return nil, EmptyAppSetGeneratorError
return nil, ErrEmptyAppSetGenerator
} }
if err != nil { if err != nil {
return nil, fmt.Errorf("error generating params from git: %w", err) return nil, fmt.Errorf("error generating params from git: %w", err)
@@ -110,11 +97,9 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
return res, nil return res, nil
} }
// generateParamsForGitDirectories generates parameters for an ApplicationSet using a directory-based Git generator. func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit bool, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
// It fetches all directories from the given Git repository and revision, optionally using a revision cache and verifying commits. // Directories, not files
// It then filters the directories based on the generator's configuration and renders parameters for the resulting applications allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, noRevisionCache, verifyCommit)
func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit, useGoTemplate bool, project string, goTemplateOptions []string) ([]map[string]any, error) {
allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, project, noRevisionCache, verifyCommit)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting directories from repo: %w", err) return nil, fmt.Errorf("error getting directories from repo: %w", err)
} }
@@ -137,96 +122,49 @@ func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoproj
return res, nil return res, nil
} }
// generateParamsForGitFiles generates parameters for an ApplicationSet using a file-based Git generator. func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit bool, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
// It retrieves and processes specified files from the Git repository, supporting both YAML and JSON formats, // Get all files that match the requested path string, removing duplicates
// and returns a list of parameter maps extracted from the content. allFiles := make(map[string][]byte)
func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit, useGoTemplate bool, project string, goTemplateOptions []string) ([]map[string]any, error) { for _, requestedPath := range appSetGenerator.Git.Files {
// fileContentMap maps absolute file paths to their byte content files, err := g.repos.GetFiles(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, requestedPath.Path, noRevisionCache, verifyCommit)
fileContentMap := make(map[string][]byte)
var includePatterns []string
var excludePatterns []string
for _, req := range appSetGenerator.Git.Files {
if req.Exclude {
excludePatterns = append(excludePatterns, req.Path)
} else {
includePatterns = append(includePatterns, req.Path)
}
}
// Fetch all files from include patterns
for _, includePattern := range includePatterns {
retrievedFiles, err := g.repos.GetFiles(
context.TODO(),
appSetGenerator.Git.RepoURL,
appSetGenerator.Git.Revision,
project,
includePattern,
noRevisionCache,
verifyCommit,
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for absPath, content := range retrievedFiles { for filePath, content := range files {
fileContentMap[absPath] = content allFiles[filePath] = content
} }
} }
// Now remove files matching any exclude pattern // Extract the unduplicated map into a list, and sort by path to ensure a deterministic
for _, excludePattern := range excludePatterns { // processing order in the subsequent step
matchingFiles, err := g.repos.GetFiles( allPaths := []string{}
context.TODO(), for path := range allFiles {
appSetGenerator.Git.RepoURL, allPaths = append(allPaths, path)
appSetGenerator.Git.Revision,
project,
excludePattern,
noRevisionCache,
verifyCommit,
)
if err != nil {
return nil, err
}
for absPath := range matchingFiles {
// if the file doesn't exist already and you try to delete it from the map
// the operation is a no-op. Its safe and doesn't return an error or panic.
// Hence, we can simply try to delete the file from the path without checking
// if that file already exists in the map.
delete(fileContentMap, absPath)
}
} }
sort.Strings(allPaths)
// Get a sorted list of file paths to ensure deterministic processing order // Generate params from each path, and return
var filePaths []string res := []map[string]interface{}{}
for path := range fileContentMap { for _, path := range allPaths {
filePaths = append(filePaths, path)
}
sort.Strings(filePaths)
var allParams []map[string]any
for _, filePath := range filePaths {
// A JSON / YAML file path can contain multiple sets of parameters (ie it is an array) // A JSON / YAML file path can contain multiple sets of parameters (ie it is an array)
paramsFromFileArray, err := g.generateParamsFromGitFile(filePath, fileContentMap[filePath], appSetGenerator.Git.Values, useGoTemplate, goTemplateOptions, appSetGenerator.Git.PathParamPrefix) paramsArray, err := g.generateParamsFromGitFile(path, allFiles[path], appSetGenerator.Git.Values, useGoTemplate, goTemplateOptions, appSetGenerator.Git.PathParamPrefix)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to process file '%s': %w", filePath, err) return nil, fmt.Errorf("unable to process file '%s': %w", path, err)
} }
allParams = append(allParams, paramsFromFileArray...)
}
return allParams, nil res = append(res, paramsArray...)
}
return res, nil
} }
// generateParamsFromGitFile parses the content of a Git-tracked file and generates a slice of parameter maps. func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []byte, values map[string]string, useGoTemplate bool, goTemplateOptions []string, pathParamPrefix string) ([]map[string]interface{}, error) {
// The file can contain a single YAML/JSON object or an array of such objects. Depending on the useGoTemplate flag, objectsFound := []map[string]interface{}{}
// it either preserves structure for Go templating or flattens the objects for use as plain key-value parameters.
func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []byte, values map[string]string, useGoTemplate bool, goTemplateOptions []string, pathParamPrefix string) ([]map[string]any, error) {
objectsFound := []map[string]any{}
// First, we attempt to parse as an array // First, we attempt to parse as an array
err := yaml.Unmarshal(fileContent, &objectsFound) err := yaml.Unmarshal(fileContent, &objectsFound)
if err != nil { if err != nil {
// If unable to parse as an array, attempt to parse as a single object // If unable to parse as an array, attempt to parse as a single object
singleObj := make(map[string]any) singleObj := make(map[string]interface{})
err = yaml.Unmarshal(fileContent, &singleObj) err = yaml.Unmarshal(fileContent, &singleObj)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to parse file: %w", err) return nil, fmt.Errorf("unable to parse file: %w", err)
@@ -234,20 +172,20 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
objectsFound = append(objectsFound, singleObj) objectsFound = append(objectsFound, singleObj)
} else if len(objectsFound) == 0 { } else if len(objectsFound) == 0 {
// If file is valid but empty, add a default empty item // If file is valid but empty, add a default empty item
objectsFound = append(objectsFound, map[string]any{}) objectsFound = append(objectsFound, map[string]interface{}{})
} }
res := []map[string]any{} res := []map[string]interface{}{}
for _, objectFound := range objectsFound { for _, objectFound := range objectsFound {
params := map[string]any{} params := map[string]interface{}{}
if useGoTemplate { if useGoTemplate {
for k, v := range objectFound { for k, v := range objectFound {
params[k] = v params[k] = v
} }
paramPath := map[string]any{} paramPath := map[string]interface{}{}
paramPath["path"] = path.Dir(filePath) paramPath["path"] = path.Dir(filePath)
paramPath["basename"] = path.Base(paramPath["path"].(string)) paramPath["basename"] = path.Base(paramPath["path"].(string))
@@ -256,7 +194,7 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
paramPath["filenameNormalized"] = utils.SanitizeName(path.Base(paramPath["filename"].(string))) paramPath["filenameNormalized"] = utils.SanitizeName(path.Base(paramPath["filename"].(string)))
paramPath["segments"] = strings.Split(paramPath["path"].(string), "/") paramPath["segments"] = strings.Split(paramPath["path"].(string), "/")
if pathParamPrefix != "" { if pathParamPrefix != "" {
params[pathParamPrefix] = map[string]any{"path": paramPath} params[pathParamPrefix] = map[string]interface{}{"path": paramPath}
} else { } else {
params["path"] = paramPath params["path"] = paramPath
} }
@@ -278,7 +216,7 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
params[pathParamName+".basenameNormalized"] = utils.SanitizeName(path.Base(params[pathParamName].(string))) params[pathParamName+".basenameNormalized"] = utils.SanitizeName(path.Base(params[pathParamName].(string)))
params[pathParamName+".filenameNormalized"] = utils.SanitizeName(path.Base(params[pathParamName+".filename"].(string))) params[pathParamName+".filenameNormalized"] = utils.SanitizeName(path.Base(params[pathParamName+".filename"].(string)))
for k, v := range strings.Split(params[pathParamName].(string), "/") { for k, v := range strings.Split(params[pathParamName].(string), "/") {
if v != "" { if len(v) > 0 {
params[pathParamName+"["+strconv.Itoa(k)+"]"] = v params[pathParamName+"["+strconv.Itoa(k)+"]"] = v
} }
} }
@@ -295,10 +233,8 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
return res, nil return res, nil
} }
// filterApps filters the list of all application paths based on inclusion and exclusion rules
// defined in GitDirectoryGeneratorItems. Each item can either include or exclude matching paths.
func (g *GitGenerator) filterApps(directories []argoprojiov1alpha1.GitDirectoryGeneratorItem, allPaths []string) []string { func (g *GitGenerator) filterApps(directories []argoprojiov1alpha1.GitDirectoryGeneratorItem, allPaths []string) []string {
var res []string res := []string{}
for _, appPath := range allPaths { for _, appPath := range allPaths {
appInclude := false appInclude := false
appExclude := false appExclude := false
@@ -325,22 +261,19 @@ func (g *GitGenerator) filterApps(directories []argoprojiov1alpha1.GitDirectoryG
return res return res
} }
// generateParamsFromApps generates a list of parameter maps based on the given app paths. func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
// Each app path is converted into a parameter object with path metadata (basename, segments, etc.). res := make([]map[string]interface{}, len(requestedApps))
// It supports both Go templates and flat key-value parameters.
func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool, goTemplateOptions []string) ([]map[string]any, error) {
res := make([]map[string]any, len(requestedApps))
for i, a := range requestedApps { for i, a := range requestedApps {
params := make(map[string]any, 5) params := make(map[string]interface{}, 5)
if useGoTemplate { if useGoTemplate {
paramPath := map[string]any{} paramPath := map[string]interface{}{}
paramPath["path"] = a paramPath["path"] = a
paramPath["basename"] = path.Base(a) paramPath["basename"] = path.Base(a)
paramPath["basenameNormalized"] = utils.SanitizeName(path.Base(a)) paramPath["basenameNormalized"] = utils.SanitizeName(path.Base(a))
paramPath["segments"] = strings.Split(paramPath["path"].(string), "/") paramPath["segments"] = strings.Split(paramPath["path"].(string), "/")
if appSetGenerator.Git.PathParamPrefix != "" { if appSetGenerator.Git.PathParamPrefix != "" {
params[appSetGenerator.Git.PathParamPrefix] = map[string]any{"path": paramPath} params[appSetGenerator.Git.PathParamPrefix] = map[string]interface{}{"path": paramPath}
} else { } else {
params["path"] = paramPath params["path"] = paramPath
} }
@@ -353,7 +286,7 @@ func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGene
params[pathParamName+".basename"] = path.Base(a) params[pathParamName+".basename"] = path.Base(a)
params[pathParamName+".basenameNormalized"] = utils.SanitizeName(path.Base(a)) params[pathParamName+".basenameNormalized"] = utils.SanitizeName(path.Base(a))
for k, v := range strings.Split(params[pathParamName].(string), "/") { for k, v := range strings.Split(params[pathParamName].(string), "/") {
if v != "" { if len(v) > 0 {
params[pathParamName+"["+strconv.Itoa(k)+"]"] = v params[pathParamName+"["+strconv.Itoa(k)+"]"] = v
} }
} }
@@ -369,12 +302,3 @@ func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGene
return res, nil return res, nil
} }
// resolveProjectName resolves a project name whether templated or not
func resolveProjectName(project string) string {
if strings.Contains(project, "{{") {
return ""
}
return project
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
package generators package generators
import ( import (
"errors" "fmt"
"time" "time"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v3/util/env" "github.com/argoproj/argo-cd/v2/util/env"
) )
// Generator defines the interface implemented by all ApplicationSet generators. // Generator defines the interface implemented by all ApplicationSet generators.
@@ -15,7 +15,7 @@ type Generator interface {
// GenerateParams interprets the ApplicationSet and generates all relevant parameters for the application template. // GenerateParams interprets the ApplicationSet and generates all relevant parameters for the application template.
// The expected / desired list of parameters is returned, it then will be render and reconciled // The expected / desired list of parameters is returned, it then will be render and reconciled
// against the current state of the Applications in the cluster. // against the current state of the Applications in the cluster.
GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]any, error) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]interface{}, error)
// GetRequeueAfter is the generator can controller the next reconciled loop // GetRequeueAfter is the generator can controller the next reconciled loop
// In case there is more then one generator the time will be the minimum of the times. // In case there is more then one generator the time will be the minimum of the times.
@@ -27,15 +27,15 @@ type Generator interface {
} }
var ( var (
ErrEmptyAppSetGenerator = errors.New("ApplicationSet is empty") EmptyAppSetGeneratorError = fmt.Errorf("ApplicationSet is empty")
NoRequeueAfter time.Duration NoRequeueAfter time.Duration
) )
const ( const (
DefaultRequeueAfter = 3 * time.Minute DefaultRequeueAfterSeconds = 3 * time.Minute
) )
func getDefaultRequeueAfter() time.Duration { func getDefaultRequeueAfter() time.Duration {
// Default is 3 minutes, min is 1 second, max is 1 year // Default is 3 minutes, min is 1 second, max is 1 year
return env.ParseDurationFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_REQUEUE_AFTER", DefaultRequeueAfter, 1*time.Second, 8760*time.Hour) return env.ParseDurationFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_REQUEUE_AFTER", DefaultRequeueAfterSeconds, 1*time.Second, 8760*time.Hour)
} }

View File

@@ -13,12 +13,12 @@ func Test_getDefaultRequeueAfter(t *testing.T) {
requeueAfterEnv string requeueAfterEnv string
want time.Duration want time.Duration
}{ }{
{name: "Default", requeueAfterEnv: "", want: DefaultRequeueAfter}, {name: "Default", requeueAfterEnv: "", want: DefaultRequeueAfterSeconds},
{name: "Min", requeueAfterEnv: "1s", want: 1 * time.Second}, {name: "Min", requeueAfterEnv: "1s", want: 1 * time.Second},
{name: "Max", requeueAfterEnv: "8760h", want: 8760 * time.Hour}, {name: "Max", requeueAfterEnv: "8760h", want: 8760 * time.Hour},
{name: "Override", requeueAfterEnv: "10m", want: 10 * time.Minute}, {name: "Override", requeueAfterEnv: "10m", want: 10 * time.Minute},
{name: "LessThanMin", requeueAfterEnv: "1ms", want: DefaultRequeueAfter}, {name: "LessThanMin", requeueAfterEnv: "1ms", want: DefaultRequeueAfterSeconds},
{name: "MoreThanMax", requeueAfterEnv: "8761h", want: DefaultRequeueAfter}, {name: "MoreThanMax", requeueAfterEnv: "8761h", want: DefaultRequeueAfterSeconds},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@@ -2,14 +2,13 @@ package generators
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"time" "time"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
var _ Generator = (*ListGenerator)(nil) var _ Generator = (*ListGenerator)(nil)
@@ -21,7 +20,7 @@ func NewListGenerator() Generator {
return g return g
} }
func (g *ListGenerator) GetRequeueAfter(_ *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration { func (g *ListGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
return NoRequeueAfter return NoRequeueAfter
} }
@@ -29,20 +28,20 @@ func (g *ListGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Applicat
return &appSetGenerator.List.Template return &appSetGenerator.List.Template
} }
func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) { func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
if appSetGenerator == nil { if appSetGenerator == nil {
return nil, ErrEmptyAppSetGenerator return nil, EmptyAppSetGeneratorError
} }
if appSetGenerator.List == nil { if appSetGenerator.List == nil {
return nil, ErrEmptyAppSetGenerator return nil, EmptyAppSetGeneratorError
} }
res := make([]map[string]any, len(appSetGenerator.List.Elements)) res := make([]map[string]interface{}, len(appSetGenerator.List.Elements))
for i, tmpItem := range appSetGenerator.List.Elements { for i, tmpItem := range appSetGenerator.List.Elements {
params := map[string]any{} params := map[string]interface{}{}
var element map[string]any var element map[string]interface{}
err := json.Unmarshal(tmpItem.Raw, &element) err := json.Unmarshal(tmpItem.Raw, &element)
if err != nil { if err != nil {
return nil, fmt.Errorf("error unmarshling list element %w", err) return nil, fmt.Errorf("error unmarshling list element %w", err)
@@ -53,16 +52,16 @@ func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appli
} else { } else {
for key, value := range element { for key, value := range element {
if key == "values" { if key == "values" {
values, ok := (value).(map[string]any) values, ok := (value).(map[string]interface{})
if !ok { if !ok {
return nil, errors.New("error parsing values map") return nil, fmt.Errorf("error parsing values map")
} }
for k, v := range values { for k, v := range values {
value, ok := v.(string) value, ok := v.(string)
if !ok { if !ok {
return nil, fmt.Errorf("error parsing value as string %w", err) return nil, fmt.Errorf("error parsing value as string %w", err)
} }
params["values."+k] = value params[fmt.Sprintf("values.%s", k)] = value
} }
} else { } else {
v, ok := value.(string) v, ok := value.(string)
@@ -77,8 +76,8 @@ func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appli
} }
// Append elements from ElementsYaml to the response // Append elements from ElementsYaml to the response
if appSetGenerator.List.ElementsYaml != "" { if len(appSetGenerator.List.ElementsYaml) > 0 {
var yamlElements []map[string]any var yamlElements []map[string]interface{}
err := yaml.Unmarshal([]byte(appSetGenerator.List.ElementsYaml), &yamlElements) err := yaml.Unmarshal([]byte(appSetGenerator.List.ElementsYaml), &yamlElements)
if err != nil { if err != nil {
return nil, fmt.Errorf("error unmarshling decoded ElementsYaml %w", err) return nil, fmt.Errorf("error unmarshling decoded ElementsYaml %w", err)

View File

@@ -8,20 +8,20 @@ import (
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
func TestGenerateListParams(t *testing.T) { func TestGenerateListParams(t *testing.T) {
testCases := []struct { testCases := []struct {
elements []apiextensionsv1.JSON elements []apiextensionsv1.JSON
expected []map[string]any expected []map[string]interface{}
}{ }{
{ {
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}}, elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
expected: []map[string]any{{"cluster": "cluster", "url": "url"}}, expected: []map[string]interface{}{{"cluster": "cluster", "url": "url"}},
}, { }, {
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}}, elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}},
expected: []map[string]any{{"cluster": "cluster", "url": "url", "values.foo": "bar"}}, expected: []map[string]interface{}{{"cluster": "cluster", "url": "url", "values.foo": "bar"}},
}, },
} }
@@ -49,14 +49,14 @@ func TestGenerateListParams(t *testing.T) {
func TestGenerateListParamsGoTemplate(t *testing.T) { func TestGenerateListParamsGoTemplate(t *testing.T) {
testCases := []struct { testCases := []struct {
elements []apiextensionsv1.JSON elements []apiextensionsv1.JSON
expected []map[string]any expected []map[string]interface{}
}{ }{
{ {
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}}, elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
expected: []map[string]any{{"cluster": "cluster", "url": "url"}}, expected: []map[string]interface{}{{"cluster": "cluster", "url": "url"}},
}, { }, {
elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}}, elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}},
expected: []map[string]any{{"cluster": "cluster", "url": "url", "values": map[string]any{"foo": "bar"}}}, expected: []map[string]interface{}{{"cluster": "cluster", "url": "url", "values": map[string]interface{}{"foo": "bar"}}},
}, },
} }

View File

@@ -1,23 +1,24 @@
package generators package generators
import ( import (
"errors"
"fmt" "fmt"
"time" "time"
"dario.cat/mergo" "github.com/imdario/mergo"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v3/applicationset/utils" "github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
log "github.com/sirupsen/logrus"
) )
var _ Generator = (*MatrixGenerator)(nil) var _ Generator = (*MatrixGenerator)(nil)
var ( var (
ErrMoreThanTwoGenerators = errors.New("found more than two generators, Matrix support only two") ErrMoreThanTwoGenerators = fmt.Errorf("found more than two generators, Matrix support only two")
ErrLessThanTwoGenerators = errors.New("found less than two generators, Matrix support only two") ErrLessThanTwoGenerators = fmt.Errorf("found less than two generators, Matrix support only two")
ErrMoreThenOneInnerGenerators = errors.New("found more than one generator in matrix.Generators") ErrMoreThenOneInnerGenerators = fmt.Errorf("found more than one generator in matrix.Generators")
) )
type MatrixGenerator struct { type MatrixGenerator struct {
@@ -32,9 +33,9 @@ func NewMatrixGenerator(supportedGenerators map[string]Generator) Generator {
return m return m
} }
func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]any, error) { func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]interface{}, error) {
if appSetGenerator.Matrix == nil { if appSetGenerator.Matrix == nil {
return nil, ErrEmptyAppSetGenerator return nil, EmptyAppSetGeneratorError
} }
if len(appSetGenerator.Matrix.Generators) < 2 { if len(appSetGenerator.Matrix.Generators) < 2 {
@@ -45,7 +46,7 @@ func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.App
return nil, ErrMoreThanTwoGenerators return nil, ErrMoreThanTwoGenerators
} }
res := []map[string]any{} res := []map[string]interface{}{}
g0, err := m.getParams(appSetGenerator.Matrix.Generators[0], appSet, nil, client) g0, err := m.getParams(appSetGenerator.Matrix.Generators[0], appSet, nil, client)
if err != nil { if err != nil {
@@ -58,7 +59,7 @@ func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.App
} }
for _, b := range g1 { for _, b := range g1 {
if appSet.Spec.GoTemplate { if appSet.Spec.GoTemplate {
tmp := map[string]any{} tmp := map[string]interface{}{}
if err := mergo.Merge(&tmp, b, mergo.WithOverride); err != nil { if err := mergo.Merge(&tmp, b, mergo.WithOverride); err != nil {
return nil, fmt.Errorf("failed to merge params from the second generator in the matrix generator with temp map: %w", err) return nil, fmt.Errorf("failed to merge params from the second generator in the matrix generator with temp map: %w", err)
} }
@@ -71,7 +72,7 @@ func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.App
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to combine string maps with merging params for the matrix generator: %w", err) return nil, fmt.Errorf("failed to combine string maps with merging params for the matrix generator: %w", err)
} }
res = append(res, val) res = append(res, utils.ConvertToMapStringInterface(val))
} }
} }
} }
@@ -79,15 +80,27 @@ func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.App
return res, nil return res, nil
} }
func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, params map[string]any, client client.Client) ([]map[string]any, error) { func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, params map[string]interface{}, client client.Client) ([]map[string]interface{}, error) {
matrixGen, err := getMatrixGenerator(appSetBaseGenerator) matrixGen, err := getMatrixGenerator(appSetBaseGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if matrixGen != nil && !appSet.Spec.ApplyNestedSelectors {
foundSelector := dropDisabledNestedSelectors(matrixGen.Generators)
if foundSelector {
log.Warnf("AppSet '%v' defines selector on nested matrix generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selectors", appSet.Name)
}
}
mergeGen, err := getMergeGenerator(appSetBaseGenerator) mergeGen, err := getMergeGenerator(appSetBaseGenerator)
if err != nil { if err != nil {
return nil, fmt.Errorf("error retrieving merge generator: %w", err) return nil, fmt.Errorf("error retrieving merge generator: %w", err)
} }
if mergeGen != nil && !appSet.Spec.ApplyNestedSelectors {
foundSelector := dropDisabledNestedSelectors(mergeGen.Generators)
if foundSelector {
log.Warnf("AppSet '%v' defines selector on nested merge generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selectors", appSet.Name)
}
}
t, err := Transform( t, err := Transform(
argoprojiov1alpha1.ApplicationSetGenerator{ argoprojiov1alpha1.ApplicationSetGenerator{
@@ -112,7 +125,7 @@ func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Appli
} }
if len(t) == 0 { if len(t) == 0 {
return nil, errors.New("child generator generated no parameters") return nil, fmt.Errorf("child generator generated no parameters")
} }
if len(t) > 1 { if len(t) > 1 {
@@ -155,8 +168,9 @@ func (m *MatrixGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Ap
if found { if found {
return res return res
} else {
return NoRequeueAfter
} }
return NoRequeueAfter
} }
func getMatrixGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*argoprojiov1alpha1.MatrixGenerator, error) { func getMatrixGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*argoprojiov1alpha1.MatrixGenerator, error) {

View File

@@ -1,6 +1,7 @@
package generators package generators
import ( import (
"context"
"testing" "testing"
"time" "time"
@@ -12,35 +13,36 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/argoproj/argo-cd/v3/applicationset/services/mocks" "github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
func TestMatrixGenerate(t *testing.T) { func TestMatrixGenerate(t *testing.T) {
gitGenerator := &v1alpha1.GitGenerator{ gitGenerator := &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL", RepoURL: "RepoURL",
Revision: "Revision", Revision: "Revision",
Directories: []v1alpha1.GitDirectoryGeneratorItem{{Path: "*"}}, Directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
} }
listGenerator := &v1alpha1.ListGenerator{ listGenerator := &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url", "templated": "test-{{path.basenameNormalized}}"}`)}}, Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url", "templated": "test-{{path.basenameNormalized}}"}`)}},
} }
testCases := []struct { testCases := []struct {
name string name string
baseGenerators []v1alpha1.ApplicationSetNestedGenerator baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
expectedErr error expectedErr error
expected []map[string]any expected []map[string]interface{}
}{ }{
{ {
name: "happy flow - generate params", name: "happy flow - generate params",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
Git: gitGenerator, Git: gitGenerator,
}, },
@@ -48,16 +50,16 @@ func TestMatrixGenerate(t *testing.T) {
List: listGenerator, List: listGenerator,
}, },
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "cluster": "Cluster", "url": "Url", "templated": "test-app1"}, {"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "cluster": "Cluster", "url": "Url", "templated": "test-app1"},
{"path": "app2", "path.basename": "app2", "path.basenameNormalized": "app2", "cluster": "Cluster", "url": "Url", "templated": "test-app2"}, {"path": "app2", "path.basename": "app2", "path.basenameNormalized": "app2", "cluster": "Cluster", "url": "Url", "templated": "test-app2"},
}, },
}, },
{ {
name: "happy flow - generate params from two lists", name: "happy flow - generate params from two lists",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
List: &v1alpha1.ListGenerator{ List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{ Elements: []apiextensionsv1.JSON{
{Raw: []byte(`{"a": "1"}`)}, {Raw: []byte(`{"a": "1"}`)},
{Raw: []byte(`{"a": "2"}`)}, {Raw: []byte(`{"a": "2"}`)},
@@ -65,7 +67,7 @@ func TestMatrixGenerate(t *testing.T) {
}, },
}, },
{ {
List: &v1alpha1.ListGenerator{ List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{ Elements: []apiextensionsv1.JSON{
{Raw: []byte(`{"b": "1"}`)}, {Raw: []byte(`{"b": "1"}`)},
{Raw: []byte(`{"b": "2"}`)}, {Raw: []byte(`{"b": "2"}`)},
@@ -73,7 +75,7 @@ func TestMatrixGenerate(t *testing.T) {
}, },
}, },
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{"a": "1", "b": "1"}, {"a": "1", "b": "1"},
{"a": "1", "b": "2"}, {"a": "1", "b": "2"},
{"a": "2", "b": "1"}, {"a": "2", "b": "1"},
@@ -82,7 +84,7 @@ func TestMatrixGenerate(t *testing.T) {
}, },
{ {
name: "returns error if there is less than two base generators", name: "returns error if there is less than two base generators",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
Git: gitGenerator, Git: gitGenerator,
}, },
@@ -91,7 +93,7 @@ func TestMatrixGenerate(t *testing.T) {
}, },
{ {
name: "returns error if there is more than two base generators", name: "returns error if there is more than two base generators",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
List: listGenerator, List: listGenerator,
}, },
@@ -106,7 +108,7 @@ func TestMatrixGenerate(t *testing.T) {
}, },
{ {
name: "returns error if there is more than one inner generator in the first base generator", name: "returns error if there is more than one inner generator in the first base generator",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
Git: gitGenerator, Git: gitGenerator,
List: listGenerator, List: listGenerator,
@@ -119,7 +121,7 @@ func TestMatrixGenerate(t *testing.T) {
}, },
{ {
name: "returns error if there is more than one inner generator in the second base generator", name: "returns error if there is more than one inner generator in the second base generator",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
List: listGenerator, List: listGenerator,
}, },
@@ -137,19 +139,19 @@ func TestMatrixGenerate(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) { t.Run(testCaseCopy.name, func(t *testing.T) {
genMock := &generatorMock{} genMock := &generatorMock{}
appSet := &v1alpha1.ApplicationSet{ appSet := &argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "set", Name: "set",
}, },
Spec: v1alpha1.ApplicationSetSpec{}, Spec: argoprojiov1alpha1.ApplicationSetSpec{},
} }
for _, g := range testCaseCopy.baseGenerators { for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := v1alpha1.ApplicationSetGenerator{ gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git, Git: g.Git,
List: g.List, List: g.List,
} }
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet, mock.Anything).Return([]map[string]any{ genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet, mock.Anything).Return([]map[string]interface{}{
{ {
"path": "app1", "path": "app1",
"path.basename": "app1", "path.basename": "app1",
@@ -163,7 +165,7 @@ func TestMatrixGenerate(t *testing.T) {
}, nil) }, nil)
genMock.On("GetTemplate", &gitGeneratorSpec). genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&v1alpha1.ApplicationSetTemplate{}) Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
} }
matrixGenerator := NewMatrixGenerator( matrixGenerator := NewMatrixGenerator(
@@ -173,10 +175,10 @@ func TestMatrixGenerate(t *testing.T) {
}, },
) )
got, err := matrixGenerator.GenerateParams(&v1alpha1.ApplicationSetGenerator{ got, err := matrixGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
Matrix: &v1alpha1.MatrixGenerator{ Matrix: &argoprojiov1alpha1.MatrixGenerator{
Generators: testCaseCopy.baseGenerators, Generators: testCaseCopy.baseGenerators,
Template: v1alpha1.ApplicationSetTemplate{}, Template: argoprojiov1alpha1.ApplicationSetTemplate{},
}, },
}, appSet, nil) }, appSet, nil)
@@ -191,25 +193,25 @@ func TestMatrixGenerate(t *testing.T) {
} }
func TestMatrixGenerateGoTemplate(t *testing.T) { func TestMatrixGenerateGoTemplate(t *testing.T) {
gitGenerator := &v1alpha1.GitGenerator{ gitGenerator := &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL", RepoURL: "RepoURL",
Revision: "Revision", Revision: "Revision",
Directories: []v1alpha1.GitDirectoryGeneratorItem{{Path: "*"}}, Directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
} }
listGenerator := &v1alpha1.ListGenerator{ listGenerator := &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url"}`)}}, Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url"}`)}},
} }
testCases := []struct { testCases := []struct {
name string name string
baseGenerators []v1alpha1.ApplicationSetNestedGenerator baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
expectedErr error expectedErr error
expected []map[string]any expected []map[string]interface{}
}{ }{
{ {
name: "happy flow - generate params", name: "happy flow - generate params",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
Git: gitGenerator, Git: gitGenerator,
}, },
@@ -217,7 +219,7 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
List: listGenerator, List: listGenerator,
}, },
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"path": map[string]string{ "path": map[string]string{
"path": "app1", "path": "app1",
@@ -240,9 +242,9 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
}, },
{ {
name: "happy flow - generate params from two lists", name: "happy flow - generate params from two lists",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
List: &v1alpha1.ListGenerator{ List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{ Elements: []apiextensionsv1.JSON{
{Raw: []byte(`{"a": "1"}`)}, {Raw: []byte(`{"a": "1"}`)},
{Raw: []byte(`{"a": "2"}`)}, {Raw: []byte(`{"a": "2"}`)},
@@ -250,7 +252,7 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
}, },
}, },
{ {
List: &v1alpha1.ListGenerator{ List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{ Elements: []apiextensionsv1.JSON{
{Raw: []byte(`{"b": "1"}`)}, {Raw: []byte(`{"b": "1"}`)},
{Raw: []byte(`{"b": "2"}`)}, {Raw: []byte(`{"b": "2"}`)},
@@ -258,7 +260,7 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
}, },
}, },
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{"a": "1", "b": "1"}, {"a": "1", "b": "1"},
{"a": "1", "b": "2"}, {"a": "1", "b": "2"},
{"a": "2", "b": "1"}, {"a": "2", "b": "1"},
@@ -267,29 +269,29 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
}, },
{ {
name: "parameter override: first list elements take precedence", name: "parameter override: first list elements take precedence",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
List: &v1alpha1.ListGenerator{ List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{ Elements: []apiextensionsv1.JSON{
{Raw: []byte(`{"booleanFalse": false, "booleanTrue": true, "stringFalse": "false", "stringTrue": "true"}`)}, {Raw: []byte(`{"booleanFalse": false, "booleanTrue": true, "stringFalse": "false", "stringTrue": "true"}`)},
}, },
}, },
}, },
{ {
List: &v1alpha1.ListGenerator{ List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{ Elements: []apiextensionsv1.JSON{
{Raw: []byte(`{"booleanFalse": true, "booleanTrue": false, "stringFalse": "true", "stringTrue": "false"}`)}, {Raw: []byte(`{"booleanFalse": true, "booleanTrue": false, "stringFalse": "true", "stringTrue": "false"}`)},
}, },
}, },
}, },
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{"booleanFalse": false, "booleanTrue": true, "stringFalse": "false", "stringTrue": "true"}, {"booleanFalse": false, "booleanTrue": true, "stringFalse": "false", "stringTrue": "true"},
}, },
}, },
{ {
name: "returns error if there is less than two base generators", name: "returns error if there is less than two base generators",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
Git: gitGenerator, Git: gitGenerator,
}, },
@@ -298,7 +300,7 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
}, },
{ {
name: "returns error if there is more than two base generators", name: "returns error if there is more than two base generators",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
List: listGenerator, List: listGenerator,
}, },
@@ -313,7 +315,7 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
}, },
{ {
name: "returns error if there is more than one inner generator in the first base generator", name: "returns error if there is more than one inner generator in the first base generator",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
Git: gitGenerator, Git: gitGenerator,
List: listGenerator, List: listGenerator,
@@ -326,7 +328,7 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
}, },
{ {
name: "returns error if there is more than one inner generator in the second base generator", name: "returns error if there is more than one inner generator in the second base generator",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
List: listGenerator, List: listGenerator,
}, },
@@ -344,21 +346,21 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) { t.Run(testCaseCopy.name, func(t *testing.T) {
genMock := &generatorMock{} genMock := &generatorMock{}
appSet := &v1alpha1.ApplicationSet{ appSet := &argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "set", Name: "set",
}, },
Spec: v1alpha1.ApplicationSetSpec{ Spec: argoprojiov1alpha1.ApplicationSetSpec{
GoTemplate: true, GoTemplate: true,
}, },
} }
for _, g := range testCaseCopy.baseGenerators { for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := v1alpha1.ApplicationSetGenerator{ gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git, Git: g.Git,
List: g.List, List: g.List,
} }
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet, mock.Anything).Return([]map[string]any{ genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet, mock.Anything).Return([]map[string]interface{}{
{ {
"path": map[string]string{ "path": map[string]string{
"path": "app1", "path": "app1",
@@ -376,7 +378,7 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
}, nil) }, nil)
genMock.On("GetTemplate", &gitGeneratorSpec). genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&v1alpha1.ApplicationSetTemplate{}) Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
} }
matrixGenerator := NewMatrixGenerator( matrixGenerator := NewMatrixGenerator(
@@ -386,10 +388,10 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
}, },
) )
got, err := matrixGenerator.GenerateParams(&v1alpha1.ApplicationSetGenerator{ got, err := matrixGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
Matrix: &v1alpha1.MatrixGenerator{ Matrix: &argoprojiov1alpha1.MatrixGenerator{
Generators: testCaseCopy.baseGenerators, Generators: testCaseCopy.baseGenerators,
Template: v1alpha1.ApplicationSetTemplate{}, Template: argoprojiov1alpha1.ApplicationSetTemplate{},
}, },
}, appSet, nil) }, appSet, nil)
@@ -404,31 +406,31 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
} }
func TestMatrixGetRequeueAfter(t *testing.T) { func TestMatrixGetRequeueAfter(t *testing.T) {
gitGenerator := &v1alpha1.GitGenerator{ gitGenerator := &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL", RepoURL: "RepoURL",
Revision: "Revision", Revision: "Revision",
Directories: []v1alpha1.GitDirectoryGeneratorItem{{Path: "*"}}, Directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
} }
listGenerator := &v1alpha1.ListGenerator{ listGenerator := &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url"}`)}}, Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url"}`)}},
} }
pullRequestGenerator := &v1alpha1.PullRequestGenerator{} pullRequestGenerator := &argoprojiov1alpha1.PullRequestGenerator{}
scmGenerator := &v1alpha1.SCMProviderGenerator{} scmGenerator := &argoprojiov1alpha1.SCMProviderGenerator{}
duckTypeGenerator := &v1alpha1.DuckTypeGenerator{} duckTypeGenerator := &argoprojiov1alpha1.DuckTypeGenerator{}
testCases := []struct { testCases := []struct {
name string name string
baseGenerators []v1alpha1.ApplicationSetNestedGenerator baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
gitGetRequeueAfter time.Duration gitGetRequeueAfter time.Duration
expected time.Duration expected time.Duration
}{ }{
{ {
name: "return NoRequeueAfter if all the inner baseGenerators returns it", name: "return NoRequeueAfter if all the inner baseGenerators returns it",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
Git: gitGenerator, Git: gitGenerator,
}, },
@@ -441,7 +443,7 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
}, },
{ {
name: "returns the minimal time", name: "returns the minimal time",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
Git: gitGenerator, Git: gitGenerator,
}, },
@@ -454,7 +456,7 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
}, },
{ {
name: "returns the minimal time for pull request", name: "returns the minimal time for pull request",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
Git: gitGenerator, Git: gitGenerator,
}, },
@@ -467,7 +469,7 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
}, },
{ {
name: "returns the default time if no requeueAfterSeconds is provided", name: "returns the default time if no requeueAfterSeconds is provided",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
Git: gitGenerator, Git: gitGenerator,
}, },
@@ -479,7 +481,7 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
}, },
{ {
name: "returns the default time for duck type generator", name: "returns the default time for duck type generator",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
Git: gitGenerator, Git: gitGenerator,
}, },
@@ -491,7 +493,7 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
}, },
{ {
name: "returns the default time for scm generator", name: "returns the default time for scm generator",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
Git: gitGenerator, Git: gitGenerator,
}, },
@@ -510,7 +512,7 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
mock := &generatorMock{} mock := &generatorMock{}
for _, g := range testCaseCopy.baseGenerators { for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := v1alpha1.ApplicationSetGenerator{ gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git, Git: g.Git,
List: g.List, List: g.List,
PullRequest: g.PullRequest, PullRequest: g.PullRequest,
@@ -530,10 +532,10 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
}, },
) )
got := matrixGenerator.GetRequeueAfter(&v1alpha1.ApplicationSetGenerator{ got := matrixGenerator.GetRequeueAfter(&argoprojiov1alpha1.ApplicationSetGenerator{
Matrix: &v1alpha1.MatrixGenerator{ Matrix: &argoprojiov1alpha1.MatrixGenerator{
Generators: testCaseCopy.baseGenerators, Generators: testCaseCopy.baseGenerators,
Template: v1alpha1.ApplicationSetTemplate{}, Template: argoprojiov1alpha1.ApplicationSetTemplate{},
}, },
}) })
@@ -543,16 +545,16 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
} }
func TestInterpolatedMatrixGenerate(t *testing.T) { func TestInterpolatedMatrixGenerate(t *testing.T) {
interpolatedGitGenerator := &v1alpha1.GitGenerator{ interpolatedGitGenerator := &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL", RepoURL: "RepoURL",
Revision: "Revision", Revision: "Revision",
Files: []v1alpha1.GitFileGeneratorItem{ Files: []argoprojiov1alpha1.GitFileGeneratorItem{
{Path: "examples/git-generator-files-discovery/cluster-config/dev/config.json"}, {Path: "examples/git-generator-files-discovery/cluster-config/dev/config.json"},
{Path: "examples/git-generator-files-discovery/cluster-config/prod/config.json"}, {Path: "examples/git-generator-files-discovery/cluster-config/prod/config.json"},
}, },
} }
interpolatedClusterGenerator := &v1alpha1.ClusterGenerator{ interpolatedClusterGenerator := &argoprojiov1alpha1.ClusterGenerator{
Selector: metav1.LabelSelector{ Selector: metav1.LabelSelector{
MatchLabels: map[string]string{"environment": "{{path.basename}}"}, MatchLabels: map[string]string{"environment": "{{path.basename}}"},
MatchExpressions: nil, MatchExpressions: nil,
@@ -560,14 +562,14 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
} }
testCases := []struct { testCases := []struct {
name string name string
baseGenerators []v1alpha1.ApplicationSetNestedGenerator baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
expectedErr error expectedErr error
expected []map[string]any expected []map[string]interface{}
clientError bool clientError bool
}{ }{
{ {
name: "happy flow - generate interpolated params", name: "happy flow - generate interpolated params",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
Git: interpolatedGitGenerator, Git: interpolatedGitGenerator,
}, },
@@ -575,7 +577,7 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
Clusters: interpolatedClusterGenerator, Clusters: interpolatedClusterGenerator,
}, },
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{"path": "examples/git-generator-files-discovery/cluster-config/dev/config.json", "path.basename": "dev", "path.basenameNormalized": "dev", "name": "dev-01", "nameNormalized": "dev-01", "server": "https://dev-01.example.com", "metadata.labels.environment": "dev", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "project": ""}, {"path": "examples/git-generator-files-discovery/cluster-config/dev/config.json", "path.basename": "dev", "path.basenameNormalized": "dev", "name": "dev-01", "nameNormalized": "dev-01", "server": "https://dev-01.example.com", "metadata.labels.environment": "dev", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "project": ""},
{"path": "examples/git-generator-files-discovery/cluster-config/prod/config.json", "path.basename": "prod", "path.basenameNormalized": "prod", "name": "prod-01", "nameNormalized": "prod-01", "server": "https://prod-01.example.com", "metadata.labels.environment": "prod", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "project": ""}, {"path": "examples/git-generator-files-discovery/cluster-config/prod/config.json", "path.basename": "prod", "path.basenameNormalized": "prod", "name": "prod-01", "nameNormalized": "prod-01", "server": "https://prod-01.example.com", "metadata.labels.environment": "prod", "metadata.labels.argocd.argoproj.io/secret-type": "cluster", "project": ""},
}, },
@@ -635,7 +637,7 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) { t.Run(testCaseCopy.name, func(t *testing.T) {
genMock := &generatorMock{} genMock := &generatorMock{}
appSet := &v1alpha1.ApplicationSet{} appSet := &argoprojiov1alpha1.ApplicationSet{}
appClientset := kubefake.NewSimpleClientset(runtimeClusters...) appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build() fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
@@ -643,14 +645,14 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
fakeClient, fakeClient,
testCase.clientError, testCase.clientError,
} }
clusterGenerator := NewClusterGenerator(t.Context(), cl, appClientset, "namespace") clusterGenerator := NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
for _, g := range testCaseCopy.baseGenerators { for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := v1alpha1.ApplicationSetGenerator{ gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git, Git: g.Git,
Clusters: g.Clusters, Clusters: g.Clusters,
} }
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]any{ genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]interface{}{
{ {
"path": "examples/git-generator-files-discovery/cluster-config/dev/config.json", "path": "examples/git-generator-files-discovery/cluster-config/dev/config.json",
"path.basename": "dev", "path.basename": "dev",
@@ -663,7 +665,7 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
}, },
}, nil) }, nil)
genMock.On("GetTemplate", &gitGeneratorSpec). genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&v1alpha1.ApplicationSetTemplate{}) Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
} }
matrixGenerator := NewMatrixGenerator( matrixGenerator := NewMatrixGenerator(
map[string]Generator{ map[string]Generator{
@@ -672,10 +674,10 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
}, },
) )
got, err := matrixGenerator.GenerateParams(&v1alpha1.ApplicationSetGenerator{ got, err := matrixGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
Matrix: &v1alpha1.MatrixGenerator{ Matrix: &argoprojiov1alpha1.MatrixGenerator{
Generators: testCaseCopy.baseGenerators, Generators: testCaseCopy.baseGenerators,
Template: v1alpha1.ApplicationSetTemplate{}, Template: argoprojiov1alpha1.ApplicationSetTemplate{},
}, },
}, appSet, nil) }, appSet, nil)
@@ -690,16 +692,16 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
} }
func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) { func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
interpolatedGitGenerator := &v1alpha1.GitGenerator{ interpolatedGitGenerator := &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL", RepoURL: "RepoURL",
Revision: "Revision", Revision: "Revision",
Files: []v1alpha1.GitFileGeneratorItem{ Files: []argoprojiov1alpha1.GitFileGeneratorItem{
{Path: "examples/git-generator-files-discovery/cluster-config/dev/config.json"}, {Path: "examples/git-generator-files-discovery/cluster-config/dev/config.json"},
{Path: "examples/git-generator-files-discovery/cluster-config/prod/config.json"}, {Path: "examples/git-generator-files-discovery/cluster-config/prod/config.json"},
}, },
} }
interpolatedClusterGenerator := &v1alpha1.ClusterGenerator{ interpolatedClusterGenerator := &argoprojiov1alpha1.ClusterGenerator{
Selector: metav1.LabelSelector{ Selector: metav1.LabelSelector{
MatchLabels: map[string]string{"environment": "{{.path.basename}}"}, MatchLabels: map[string]string{"environment": "{{.path.basename}}"},
MatchExpressions: nil, MatchExpressions: nil,
@@ -707,14 +709,14 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
} }
testCases := []struct { testCases := []struct {
name string name string
baseGenerators []v1alpha1.ApplicationSetNestedGenerator baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
expectedErr error expectedErr error
expected []map[string]any expected []map[string]interface{}
clientError bool clientError bool
}{ }{
{ {
name: "happy flow - generate interpolated params", name: "happy flow - generate interpolated params",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
Git: interpolatedGitGenerator, Git: interpolatedGitGenerator,
}, },
@@ -722,7 +724,7 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
Clusters: interpolatedClusterGenerator, Clusters: interpolatedClusterGenerator,
}, },
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"path": map[string]string{ "path": map[string]string{
"path": "examples/git-generator-files-discovery/cluster-config/dev/config.json", "path": "examples/git-generator-files-discovery/cluster-config/dev/config.json",
@@ -733,7 +735,7 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
"nameNormalized": "dev-01", "nameNormalized": "dev-01",
"server": "https://dev-01.example.com", "server": "https://dev-01.example.com",
"project": "", "project": "",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"environment": "dev", "environment": "dev",
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
@@ -750,7 +752,7 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
"nameNormalized": "prod-01", "nameNormalized": "prod-01",
"server": "https://prod-01.example.com", "server": "https://prod-01.example.com",
"project": "", "project": "",
"metadata": map[string]any{ "metadata": map[string]interface{}{
"labels": map[string]string{ "labels": map[string]string{
"environment": "prod", "environment": "prod",
"argocd.argoproj.io/secret-type": "cluster", "argocd.argoproj.io/secret-type": "cluster",
@@ -814,8 +816,8 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) { t.Run(testCaseCopy.name, func(t *testing.T) {
genMock := &generatorMock{} genMock := &generatorMock{}
appSet := &v1alpha1.ApplicationSet{ appSet := &argoprojiov1alpha1.ApplicationSet{
Spec: v1alpha1.ApplicationSetSpec{ Spec: argoprojiov1alpha1.ApplicationSetSpec{
GoTemplate: true, GoTemplate: true,
}, },
} }
@@ -826,14 +828,14 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
fakeClient, fakeClient,
testCase.clientError, testCase.clientError,
} }
clusterGenerator := NewClusterGenerator(t.Context(), cl, appClientset, "namespace") clusterGenerator := NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
for _, g := range testCaseCopy.baseGenerators { for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := v1alpha1.ApplicationSetGenerator{ gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git, Git: g.Git,
Clusters: g.Clusters, Clusters: g.Clusters,
} }
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]any{ genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]interface{}{
{ {
"path": map[string]string{ "path": map[string]string{
"path": "examples/git-generator-files-discovery/cluster-config/dev/config.json", "path": "examples/git-generator-files-discovery/cluster-config/dev/config.json",
@@ -850,7 +852,7 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
}, },
}, nil) }, nil)
genMock.On("GetTemplate", &gitGeneratorSpec). genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&v1alpha1.ApplicationSetTemplate{}) Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
} }
matrixGenerator := NewMatrixGenerator( matrixGenerator := NewMatrixGenerator(
map[string]Generator{ map[string]Generator{
@@ -859,10 +861,10 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
}, },
) )
got, err := matrixGenerator.GenerateParams(&v1alpha1.ApplicationSetGenerator{ got, err := matrixGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
Matrix: &v1alpha1.MatrixGenerator{ Matrix: &argoprojiov1alpha1.MatrixGenerator{
Generators: testCaseCopy.baseGenerators, Generators: testCaseCopy.baseGenerators,
Template: v1alpha1.ApplicationSetTemplate{}, Template: argoprojiov1alpha1.ApplicationSetTemplate{},
}, },
}, appSet, nil) }, appSet, nil)
@@ -877,28 +879,28 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
} }
func TestMatrixGenerateListElementsYaml(t *testing.T) { func TestMatrixGenerateListElementsYaml(t *testing.T) {
gitGenerator := &v1alpha1.GitGenerator{ gitGenerator := &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL", RepoURL: "RepoURL",
Revision: "Revision", Revision: "Revision",
Files: []v1alpha1.GitFileGeneratorItem{ Files: []argoprojiov1alpha1.GitFileGeneratorItem{
{Path: "config.yaml"}, {Path: "config.yaml"},
}, },
} }
listGenerator := &v1alpha1.ListGenerator{ listGenerator := &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{}, Elements: []apiextensionsv1.JSON{},
ElementsYaml: "{{ .foo.bar | toJson }}", ElementsYaml: "{{ .foo.bar | toJson }}",
} }
testCases := []struct { testCases := []struct {
name string name string
baseGenerators []v1alpha1.ApplicationSetNestedGenerator baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
expectedErr error expectedErr error
expected []map[string]any expected []map[string]interface{}
}{ }{
{ {
name: "happy flow - generate params", name: "happy flow - generate params",
baseGenerators: []v1alpha1.ApplicationSetNestedGenerator{ baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
Git: gitGenerator, Git: gitGenerator,
}, },
@@ -906,23 +908,23 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
List: listGenerator, List: listGenerator,
}, },
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"chart": "a", "chart": "a",
"version": "1", "version": "1",
"foo": map[string]any{ "foo": map[string]interface{}{
"bar": []any{ "bar": []interface{}{
map[string]any{ map[string]interface{}{
"chart": "a", "chart": "a",
"version": "1", "version": "1",
}, },
map[string]any{ map[string]interface{}{
"chart": "b", "chart": "b",
"version": "2", "version": "2",
}, },
}, },
}, },
"path": map[string]any{ "path": map[string]interface{}{
"basename": "dir", "basename": "dir",
"basenameNormalized": "dir", "basenameNormalized": "dir",
"filename": "file_name.yaml", "filename": "file_name.yaml",
@@ -937,19 +939,19 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
{ {
"chart": "b", "chart": "b",
"version": "2", "version": "2",
"foo": map[string]any{ "foo": map[string]interface{}{
"bar": []any{ "bar": []interface{}{
map[string]any{ map[string]interface{}{
"chart": "a", "chart": "a",
"version": "1", "version": "1",
}, },
map[string]any{ map[string]interface{}{
"chart": "b", "chart": "b",
"version": "2", "version": "2",
}, },
}, },
}, },
"path": map[string]any{ "path": map[string]interface{}{
"basename": "dir", "basename": "dir",
"basenameNormalized": "dir", "basenameNormalized": "dir",
"filename": "file_name.yaml", "filename": "file_name.yaml",
@@ -970,34 +972,34 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) { t.Run(testCaseCopy.name, func(t *testing.T) {
genMock := &generatorMock{} genMock := &generatorMock{}
appSet := &v1alpha1.ApplicationSet{ appSet := &argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "set", Name: "set",
}, },
Spec: v1alpha1.ApplicationSetSpec{ Spec: argoprojiov1alpha1.ApplicationSetSpec{
GoTemplate: true, GoTemplate: true,
}, },
} }
for _, g := range testCaseCopy.baseGenerators { for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := v1alpha1.ApplicationSetGenerator{ gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git, Git: g.Git,
List: g.List, List: g.List,
} }
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]any{{ genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]any{{
"foo": map[string]any{ "foo": map[string]interface{}{
"bar": []any{ "bar": []interface{}{
map[string]any{ map[string]interface{}{
"chart": "a", "chart": "a",
"version": "1", "version": "1",
}, },
map[string]any{ map[string]interface{}{
"chart": "b", "chart": "b",
"version": "2", "version": "2",
}, },
}, },
}, },
"path": map[string]any{ "path": map[string]interface{}{
"basename": "dir", "basename": "dir",
"basenameNormalized": "dir", "basenameNormalized": "dir",
"filename": "file_name.yaml", "filename": "file_name.yaml",
@@ -1010,7 +1012,7 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
}, },
}}, nil) }}, nil)
genMock.On("GetTemplate", &gitGeneratorSpec). genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&v1alpha1.ApplicationSetTemplate{}) Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
} }
matrixGenerator := NewMatrixGenerator( matrixGenerator := NewMatrixGenerator(
@@ -1020,10 +1022,10 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
}, },
) )
got, err := matrixGenerator.GenerateParams(&v1alpha1.ApplicationSetGenerator{ got, err := matrixGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
Matrix: &v1alpha1.MatrixGenerator{ Matrix: &argoprojiov1alpha1.MatrixGenerator{
Generators: testCaseCopy.baseGenerators, Generators: testCaseCopy.baseGenerators,
Template: v1alpha1.ApplicationSetTemplate{}, Template: argoprojiov1alpha1.ApplicationSetTemplate{},
}, },
}, appSet, nil) }, appSet, nil)
@@ -1041,19 +1043,19 @@ type generatorMock struct {
mock.Mock mock.Mock
} }
func (g *generatorMock) GetTemplate(appSetGenerator *v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate { func (g *generatorMock) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
args := g.Called(appSetGenerator) args := g.Called(appSetGenerator)
return args.Get(0).(*v1alpha1.ApplicationSetTemplate) return args.Get(0).(*argoprojiov1alpha1.ApplicationSetTemplate)
} }
func (g *generatorMock) GenerateParams(appSetGenerator *v1alpha1.ApplicationSetGenerator, appSet *v1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) { func (g *generatorMock) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
args := g.Called(appSetGenerator, appSet) args := g.Called(appSetGenerator, appSet)
return args.Get(0).([]map[string]any), args.Error(1) return args.Get(0).([]map[string]interface{}), args.Error(1)
} }
func (g *generatorMock) GetRequeueAfter(appSetGenerator *v1alpha1.ApplicationSetGenerator) time.Duration { func (g *generatorMock) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
args := g.Called(appSetGenerator) args := g.Called(appSetGenerator)
return args.Get(0).(time.Duration) return args.Get(0).(time.Duration)
@@ -1073,20 +1075,20 @@ func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
// of that bug. // of that bug.
listGeneratorMock := &generatorMock{} listGeneratorMock := &generatorMock{}
listGeneratorMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).Return([]map[string]any{ listGeneratorMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).Return([]map[string]interface{}{
{"some": "value"}, {"some": "value"},
}, nil) }, nil)
listGeneratorMock.On("GetTemplate", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator")).Return(&v1alpha1.ApplicationSetTemplate{}) listGeneratorMock.On("GetTemplate", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator")).Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
gitGeneratorSpec := &v1alpha1.GitGenerator{ gitGeneratorSpec := &argoprojiov1alpha1.GitGenerator{
RepoURL: "https://git.example.com", RepoURL: "https://git.example.com",
Files: []v1alpha1.GitFileGeneratorItem{ Files: []argoprojiov1alpha1.GitFileGeneratorItem{
{Path: "some/path.json"}, {Path: "some/path.json"},
}, },
} }
repoServiceMock := &mocks.Repos{} repoServiceMock := &mocks.Repos{}
repoServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(map[string][]byte{ repoServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(map[string][]byte{
"some/path.json": []byte("test: content"), "some/path.json": []byte("test: content"),
}, nil) }, nil)
gitGenerator := NewGitGenerator(repoServiceMock, "") gitGenerator := NewGitGenerator(repoServiceMock, "")
@@ -1096,10 +1098,10 @@ func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
"Git": gitGenerator, "Git": gitGenerator,
}) })
matrixGeneratorSpec := &v1alpha1.MatrixGenerator{ matrixGeneratorSpec := &argoprojiov1alpha1.MatrixGenerator{
Generators: []v1alpha1.ApplicationSetNestedGenerator{ Generators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{ {
List: &v1alpha1.ListGenerator{ List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{ Elements: []apiextensionsv1.JSON{
{ {
Raw: []byte(`{"some": "value"}`), Raw: []byte(`{"some": "value"}`),
@@ -1116,15 +1118,15 @@ func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
scheme := runtime.NewScheme() scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme) err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err) require.NoError(t, err)
appProject := v1alpha1.AppProject{} appProject := argoprojiov1alpha1.AppProject{}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build() client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
params, err := matrixGenerator.GenerateParams(&v1alpha1.ApplicationSetGenerator{ params, err := matrixGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
Matrix: matrixGeneratorSpec, Matrix: matrixGeneratorSpec,
}, &v1alpha1.ApplicationSet{}, client) }, &argoprojiov1alpha1.ApplicationSet{}, client)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []map[string]any{{ assert.Equal(t, []map[string]interface{}{{
"path": "some", "path": "some",
"path.basename": "some", "path.basename": "some",
"path.basenameNormalized": "some", "path.basenameNormalized": "some",

View File

@@ -2,23 +2,24 @@ package generators
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"maps"
"time" "time"
"dario.cat/mergo" "github.com/imdario/mergo"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
log "github.com/sirupsen/logrus"
) )
var _ Generator = (*MergeGenerator)(nil) var _ Generator = (*MergeGenerator)(nil)
var ( var (
ErrLessThanTwoGeneratorsInMerge = errors.New("found less than two generators, Merge requires two or more") ErrLessThanTwoGeneratorsInMerge = fmt.Errorf("found less than two generators, Merge requires two or more")
ErrNoMergeKeys = errors.New("no merge keys were specified, Merge requires at least one") ErrNoMergeKeys = fmt.Errorf("no merge keys were specified, Merge requires at least one")
ErrNonUniqueParamSets = errors.New("the parameters from a generator were not unique by the given mergeKeys, Merge requires all param sets to be unique") ErrNonUniqueParamSets = fmt.Errorf("the parameters from a generator were not unique by the given mergeKeys, Merge requires all param sets to be unique")
) )
type MergeGenerator struct { type MergeGenerator struct {
@@ -36,8 +37,8 @@ func NewMergeGenerator(supportedGenerators map[string]Generator) Generator {
// getParamSetsForAllGenerators generates params for each child generator in a MergeGenerator. Param sets are returned // getParamSetsForAllGenerators generates params for each child generator in a MergeGenerator. Param sets are returned
// in slices ordered according to the order of the given generators. // in slices ordered according to the order of the given generators.
func (m *MergeGenerator) getParamSetsForAllGenerators(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([][]map[string]any, error) { func (m *MergeGenerator) getParamSetsForAllGenerators(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([][]map[string]interface{}, error) {
var paramSets [][]map[string]any var paramSets [][]map[string]interface{}
for i, generator := range generators { for i, generator := range generators {
generatorParamSets, err := m.getParams(generator, appSet, client) generatorParamSets, err := m.getParams(generator, appSet, client)
if err != nil { if err != nil {
@@ -50,9 +51,9 @@ func (m *MergeGenerator) getParamSetsForAllGenerators(generators []argoprojiov1a
} }
// GenerateParams gets the params produced by the MergeGenerator. // GenerateParams gets the params produced by the MergeGenerator.
func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]any, error) { func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]interface{}, error) {
if appSetGenerator.Merge == nil { if appSetGenerator.Merge == nil {
return nil, ErrEmptyAppSetGenerator return nil, EmptyAppSetGeneratorError
} }
if len(appSetGenerator.Merge.Generators) < 2 { if len(appSetGenerator.Merge.Generators) < 2 {
@@ -83,18 +84,21 @@ func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appl
} }
baseParamSetsByMergeKey[mergeKeyValue] = baseParamSet baseParamSetsByMergeKey[mergeKeyValue] = baseParamSet
} else { } else {
maps.Copy(baseParamSet, overrideParamSet) overriddenParamSet, err := utils.CombineStringMapsAllowDuplicates(baseParamSet, overrideParamSet)
baseParamSetsByMergeKey[mergeKeyValue] = baseParamSet if err != nil {
return nil, fmt.Errorf("error combining string maps: %w", err)
}
baseParamSetsByMergeKey[mergeKeyValue] = utils.ConvertToMapStringInterface(overriddenParamSet)
} }
} }
} }
} }
mergedParamSets := make([]map[string]any, len(baseParamSetsByMergeKey)) mergedParamSets := make([]map[string]interface{}, len(baseParamSetsByMergeKey))
i := 0 i := 0
for _, mergedParamSet := range baseParamSetsByMergeKey { for _, mergedParamSet := range baseParamSetsByMergeKey {
mergedParamSets[i] = mergedParamSet mergedParamSets[i] = mergedParamSet
i++ i += 1
} }
return mergedParamSets, nil return mergedParamSets, nil
@@ -103,7 +107,7 @@ func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appl
// getParamSetsByMergeKey converts the given list of parameter sets to a map of parameter sets where the key is the // getParamSetsByMergeKey converts the given list of parameter sets to a map of parameter sets where the key is the
// unique key of the parameter set as determined by the given mergeKeys. If any two parameter sets share the same merge // unique key of the parameter set as determined by the given mergeKeys. If any two parameter sets share the same merge
// key, getParamSetsByMergeKey will throw NonUniqueParamSets. // key, getParamSetsByMergeKey will throw NonUniqueParamSets.
func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]any) (map[string]map[string]any, error) { func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]interface{}) (map[string]map[string]interface{}, error) {
if len(mergeKeys) < 1 { if len(mergeKeys) < 1 {
return nil, ErrNoMergeKeys return nil, ErrNoMergeKeys
} }
@@ -113,17 +117,17 @@ func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]any) (map
deDuplicatedMergeKeys[mergeKey] = false deDuplicatedMergeKeys[mergeKey] = false
} }
paramSetsByMergeKey := make(map[string]map[string]any, len(paramSets)) paramSetsByMergeKey := make(map[string]map[string]interface{}, len(paramSets))
for _, paramSet := range paramSets { for _, paramSet := range paramSets {
paramSetKey := make(map[string]any) paramSetKey := make(map[string]interface{})
for mergeKey := range deDuplicatedMergeKeys { for mergeKey := range deDuplicatedMergeKeys {
paramSetKey[mergeKey] = paramSet[mergeKey] paramSetKey[mergeKey] = paramSet[mergeKey]
} }
paramSetKeyJSON, err := json.Marshal(paramSetKey) paramSetKeyJson, err := json.Marshal(paramSetKey)
if err != nil { if err != nil {
return nil, fmt.Errorf("error marshalling param set key json: %w", err) return nil, fmt.Errorf("error marshalling param set key json: %w", err)
} }
paramSetKeyString := string(paramSetKeyJSON) paramSetKeyString := string(paramSetKeyJson)
if _, exists := paramSetsByMergeKey[paramSetKeyString]; exists { if _, exists := paramSetsByMergeKey[paramSetKeyString]; exists {
return nil, fmt.Errorf("%w. Duplicate key was %s", ErrNonUniqueParamSets, paramSetKeyString) return nil, fmt.Errorf("%w. Duplicate key was %s", ErrNonUniqueParamSets, paramSetKeyString)
} }
@@ -134,15 +138,27 @@ func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]any) (map
} }
// getParams get the parameters generated by this generator. // getParams get the parameters generated by this generator.
func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]any, error) { func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]interface{}, error) {
matrixGen, err := getMatrixGenerator(appSetBaseGenerator) matrixGen, err := getMatrixGenerator(appSetBaseGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if matrixGen != nil && !appSet.Spec.ApplyNestedSelectors {
foundSelector := dropDisabledNestedSelectors(matrixGen.Generators)
if foundSelector {
log.Warnf("AppSet '%v' defines selector on nested matrix generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selector", appSet.Name)
}
}
mergeGen, err := getMergeGenerator(appSetBaseGenerator) mergeGen, err := getMergeGenerator(appSetBaseGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if mergeGen != nil && !appSet.Spec.ApplyNestedSelectors {
foundSelector := dropDisabledNestedSelectors(mergeGen.Generators)
if foundSelector {
log.Warnf("AppSet '%v' defines selector on nested merge generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selector", appSet.Name)
}
}
t, err := Transform( t, err := Transform(
argoprojiov1alpha1.ApplicationSetGenerator{ argoprojiov1alpha1.ApplicationSetGenerator{
@@ -160,13 +176,13 @@ func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Applic
m.supportedGenerators, m.supportedGenerators,
argoprojiov1alpha1.ApplicationSetTemplate{}, argoprojiov1alpha1.ApplicationSetTemplate{},
appSet, appSet,
map[string]any{}, client) map[string]interface{}{}, client)
if err != nil { if err != nil {
return nil, fmt.Errorf("child generator returned an error on parameter generation: %w", err) return nil, fmt.Errorf("child generator returned an error on parameter generation: %w", err)
} }
if len(t) == 0 { if len(t) == 0 {
return nil, errors.New("child generator generated no parameters") return nil, fmt.Errorf("child generator generated no parameters")
} }
if len(t) > 1 { if len(t) > 1 {
@@ -207,8 +223,9 @@ func (m *MergeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.App
if found { if found {
return res return res
} else {
return NoRequeueAfter
} }
return NoRequeueAfter
} }
func getMergeGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*argoprojiov1alpha1.MergeGenerator, error) { func getMergeGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*argoprojiov1alpha1.MergeGenerator, error) {

View File

@@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
func getNestedListGenerator(json string) *argoprojiov1alpha1.ApplicationSetNestedGenerator { func getNestedListGenerator(json string) *argoprojiov1alpha1.ApplicationSetNestedGenerator {
@@ -36,28 +36,26 @@ func getTerminalListGeneratorMultiple(jsons []string) argoprojiov1alpha1.Applica
return generator return generator
} }
func listOfMapsToSet(maps []map[string]any) (map[string]bool, error) { func listOfMapsToSet(maps []map[string]interface{}) (map[string]bool, error) {
set := make(map[string]bool, len(maps)) set := make(map[string]bool, len(maps))
for _, paramMap := range maps { for _, paramMap := range maps {
paramMapAsJSON, err := json.Marshal(paramMap) paramMapAsJson, err := json.Marshal(paramMap)
if err != nil { if err != nil {
return nil, err return nil, err
} }
set[string(paramMapAsJSON)] = false set[string(paramMapAsJson)] = false
} }
return set, nil return set, nil
} }
func TestMergeGenerate(t *testing.T) { func TestMergeGenerate(t *testing.T) {
t.Parallel()
testCases := []struct { testCases := []struct {
name string name string
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
mergeKeys []string mergeKeys []string
expectedErr error expectedErr error
expected []map[string]any expected []map[string]interface{}
}{ }{
{ {
name: "no generators", name: "no generators",
@@ -81,7 +79,7 @@ func TestMergeGenerate(t *testing.T) {
*getNestedListGenerator(`{"a": "3_1","b": "different","c": "3_3"}`), // gets ignored because its merge key value isn't in the base params set *getNestedListGenerator(`{"a": "3_1","b": "different","c": "3_3"}`), // gets ignored because its merge key value isn't in the base params set
}, },
mergeKeys: []string{"b"}, mergeKeys: []string{"b"},
expected: []map[string]any{ expected: []map[string]interface{}{
{"a": "2_1", "b": "same", "c": "1_3"}, {"a": "2_1", "b": "same", "c": "1_3"},
}, },
}, },
@@ -92,7 +90,7 @@ func TestMergeGenerate(t *testing.T) {
*getNestedListGenerator(`{"a": "a"}`), *getNestedListGenerator(`{"a": "a"}`),
}, },
mergeKeys: []string{"b"}, mergeKeys: []string{"b"},
expected: []map[string]any{ expected: []map[string]interface{}{
{"a": "a"}, {"a": "a"},
}, },
}, },
@@ -103,7 +101,7 @@ func TestMergeGenerate(t *testing.T) {
*getNestedListGenerator(`{"b": "b"}`), *getNestedListGenerator(`{"b": "b"}`),
}, },
mergeKeys: []string{"b"}, mergeKeys: []string{"b"},
expected: []map[string]any{ expected: []map[string]interface{}{
{"a": "a"}, {"a": "a"},
}, },
}, },
@@ -121,7 +119,7 @@ func TestMergeGenerate(t *testing.T) {
*getNestedListGenerator(`{"a": "1", "b": "1", "c": "added"}`), *getNestedListGenerator(`{"a": "1", "b": "1", "c": "added"}`),
}, },
mergeKeys: []string{"a", "b"}, mergeKeys: []string{"a", "b"},
expected: []map[string]any{ expected: []map[string]interface{}{
{"a": "1", "b": "1", "c": "added"}, {"a": "1", "b": "1", "c": "added"},
{"a": "1", "b": "2"}, {"a": "1", "b": "2"},
{"a": "2", "b": "1"}, {"a": "2", "b": "1"},
@@ -143,7 +141,7 @@ func TestMergeGenerate(t *testing.T) {
*getNestedListGenerator(`{"a": "1", "b": "3", "d": "added"}`), *getNestedListGenerator(`{"a": "1", "b": "3", "d": "added"}`),
}, },
mergeKeys: []string{"a", "b"}, mergeKeys: []string{"a", "b"},
expected: []map[string]any{ expected: []map[string]interface{}{
{"a": "1", "b": "3", "c": "added", "d": "added"}, {"a": "1", "b": "3", "c": "added", "d": "added"},
{"a": "2", "b": "2"}, {"a": "2", "b": "2"},
}, },
@@ -198,7 +196,7 @@ func TestMergeGenerate(t *testing.T) {
} }
} }
func toAPIExtensionsJSON(t *testing.T, g any) *apiextensionsv1.JSON { func toAPIExtensionsJSON(t *testing.T, g interface{}) *apiextensionsv1.JSON {
t.Helper() t.Helper()
resVal, err := json.Marshal(g) resVal, err := json.Marshal(g)
if err != nil { if err != nil {
@@ -212,14 +210,12 @@ func toAPIExtensionsJSON(t *testing.T, g any) *apiextensionsv1.JSON {
} }
func TestParamSetsAreUniqueByMergeKeys(t *testing.T) { func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
t.Parallel()
testCases := []struct { testCases := []struct {
name string name string
mergeKeys []string mergeKeys []string
paramSets []map[string]any paramSets []map[string]interface{}
expectedErr error expectedErr error
expected map[string]map[string]any expected map[string]map[string]interface{}
}{ }{
{ {
name: "no merge keys", name: "no merge keys",
@@ -229,13 +225,13 @@ func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
{ {
name: "no paramSets", name: "no paramSets",
mergeKeys: []string{"key"}, mergeKeys: []string{"key"},
expected: make(map[string]map[string]any), expected: make(map[string]map[string]interface{}),
}, },
{ {
name: "simple key, unique paramSets", name: "simple key, unique paramSets",
mergeKeys: []string{"key"}, mergeKeys: []string{"key"},
paramSets: []map[string]any{{"key": "a"}, {"key": "b"}}, paramSets: []map[string]interface{}{{"key": "a"}, {"key": "b"}},
expected: map[string]map[string]any{ expected: map[string]map[string]interface{}{
`{"key":"a"}`: {"key": "a"}, `{"key":"a"}`: {"key": "a"},
`{"key":"b"}`: {"key": "b"}, `{"key":"b"}`: {"key": "b"},
}, },
@@ -243,23 +239,23 @@ func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
{ {
name: "simple key object, unique paramSets", name: "simple key object, unique paramSets",
mergeKeys: []string{"key"}, mergeKeys: []string{"key"},
paramSets: []map[string]any{{"key": map[string]any{"hello": "world"}}, {"key": "b"}}, paramSets: []map[string]interface{}{{"key": map[string]interface{}{"hello": "world"}}, {"key": "b"}},
expected: map[string]map[string]any{ expected: map[string]map[string]interface{}{
`{"key":{"hello":"world"}}`: {"key": map[string]any{"hello": "world"}}, `{"key":{"hello":"world"}}`: {"key": map[string]interface{}{"hello": "world"}},
`{"key":"b"}`: {"key": "b"}, `{"key":"b"}`: {"key": "b"},
}, },
}, },
{ {
name: "simple key, non-unique paramSets", name: "simple key, non-unique paramSets",
mergeKeys: []string{"key"}, mergeKeys: []string{"key"},
paramSets: []map[string]any{{"key": "a"}, {"key": "b"}, {"key": "b"}}, paramSets: []map[string]interface{}{{"key": "a"}, {"key": "b"}, {"key": "b"}},
expectedErr: fmt.Errorf("%w. Duplicate key was %s", ErrNonUniqueParamSets, `{"key":"b"}`), expectedErr: fmt.Errorf("%w. Duplicate key was %s", ErrNonUniqueParamSets, `{"key":"b"}`),
}, },
{ {
name: "simple key, duplicated key name, unique paramSets", name: "simple key, duplicated key name, unique paramSets",
mergeKeys: []string{"key", "key"}, mergeKeys: []string{"key", "key"},
paramSets: []map[string]any{{"key": "a"}, {"key": "b"}}, paramSets: []map[string]interface{}{{"key": "a"}, {"key": "b"}},
expected: map[string]map[string]any{ expected: map[string]map[string]interface{}{
`{"key":"a"}`: {"key": "a"}, `{"key":"a"}`: {"key": "a"},
`{"key":"b"}`: {"key": "b"}, `{"key":"b"}`: {"key": "b"},
}, },
@@ -267,18 +263,18 @@ func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
{ {
name: "simple key, duplicated key name, non-unique paramSets", name: "simple key, duplicated key name, non-unique paramSets",
mergeKeys: []string{"key", "key"}, mergeKeys: []string{"key", "key"},
paramSets: []map[string]any{{"key": "a"}, {"key": "b"}, {"key": "b"}}, paramSets: []map[string]interface{}{{"key": "a"}, {"key": "b"}, {"key": "b"}},
expectedErr: fmt.Errorf("%w. Duplicate key was %s", ErrNonUniqueParamSets, `{"key":"b"}`), expectedErr: fmt.Errorf("%w. Duplicate key was %s", ErrNonUniqueParamSets, `{"key":"b"}`),
}, },
{ {
name: "compound key, unique paramSets", name: "compound key, unique paramSets",
mergeKeys: []string{"key1", "key2"}, mergeKeys: []string{"key1", "key2"},
paramSets: []map[string]any{ paramSets: []map[string]interface{}{
{"key1": "a", "key2": "a"}, {"key1": "a", "key2": "a"},
{"key1": "a", "key2": "b"}, {"key1": "a", "key2": "b"},
{"key1": "b", "key2": "a"}, {"key1": "b", "key2": "a"},
}, },
expected: map[string]map[string]any{ expected: map[string]map[string]interface{}{
`{"key1":"a","key2":"a"}`: {"key1": "a", "key2": "a"}, `{"key1":"a","key2":"a"}`: {"key1": "a", "key2": "a"},
`{"key1":"a","key2":"b"}`: {"key1": "a", "key2": "b"}, `{"key1":"a","key2":"b"}`: {"key1": "a", "key2": "b"},
`{"key1":"b","key2":"a"}`: {"key1": "b", "key2": "a"}, `{"key1":"b","key2":"a"}`: {"key1": "b", "key2": "a"},
@@ -287,13 +283,13 @@ func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
{ {
name: "compound key object, unique paramSets", name: "compound key object, unique paramSets",
mergeKeys: []string{"key1", "key2"}, mergeKeys: []string{"key1", "key2"},
paramSets: []map[string]any{ paramSets: []map[string]interface{}{
{"key1": "a", "key2": map[string]any{"hello": "world"}}, {"key1": "a", "key2": map[string]interface{}{"hello": "world"}},
{"key1": "a", "key2": "b"}, {"key1": "a", "key2": "b"},
{"key1": "b", "key2": "a"}, {"key1": "b", "key2": "a"},
}, },
expected: map[string]map[string]any{ expected: map[string]map[string]interface{}{
`{"key1":"a","key2":{"hello":"world"}}`: {"key1": "a", "key2": map[string]any{"hello": "world"}}, `{"key1":"a","key2":{"hello":"world"}}`: {"key1": "a", "key2": map[string]interface{}{"hello": "world"}},
`{"key1":"a","key2":"b"}`: {"key1": "a", "key2": "b"}, `{"key1":"a","key2":"b"}`: {"key1": "a", "key2": "b"},
`{"key1":"b","key2":"a"}`: {"key1": "b", "key2": "a"}, `{"key1":"b","key2":"a"}`: {"key1": "b", "key2": "a"},
}, },
@@ -301,12 +297,12 @@ func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
{ {
name: "compound key, duplicate key names, unique paramSets", name: "compound key, duplicate key names, unique paramSets",
mergeKeys: []string{"key1", "key1", "key2"}, mergeKeys: []string{"key1", "key1", "key2"},
paramSets: []map[string]any{ paramSets: []map[string]interface{}{
{"key1": "a", "key2": "a"}, {"key1": "a", "key2": "a"},
{"key1": "a", "key2": "b"}, {"key1": "a", "key2": "b"},
{"key1": "b", "key2": "a"}, {"key1": "b", "key2": "a"},
}, },
expected: map[string]map[string]any{ expected: map[string]map[string]interface{}{
`{"key1":"a","key2":"a"}`: {"key1": "a", "key2": "a"}, `{"key1":"a","key2":"a"}`: {"key1": "a", "key2": "a"},
`{"key1":"a","key2":"b"}`: {"key1": "a", "key2": "b"}, `{"key1":"a","key2":"b"}`: {"key1": "a", "key2": "b"},
`{"key1":"b","key2":"a"}`: {"key1": "b", "key2": "a"}, `{"key1":"b","key2":"a"}`: {"key1": "b", "key2": "a"},
@@ -315,7 +311,7 @@ func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
{ {
name: "compound key, non-unique paramSets", name: "compound key, non-unique paramSets",
mergeKeys: []string{"key1", "key2"}, mergeKeys: []string{"key1", "key2"},
paramSets: []map[string]any{ paramSets: []map[string]interface{}{
{"key1": "a", "key2": "a"}, {"key1": "a", "key2": "a"},
{"key1": "a", "key2": "a"}, {"key1": "a", "key2": "a"},
{"key1": "b", "key2": "a"}, {"key1": "b", "key2": "a"},
@@ -325,7 +321,7 @@ func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
{ {
name: "compound key, duplicate key names, non-unique paramSets", name: "compound key, duplicate key names, non-unique paramSets",
mergeKeys: []string{"key1", "key1", "key2"}, mergeKeys: []string{"key1", "key1", "key2"},
paramSets: []map[string]any{ paramSets: []map[string]interface{}{
{"key1": "a", "key2": "a"}, {"key1": "a", "key2": "a"},
{"key1": "a", "key2": "a"}, {"key1": "a", "key2": "a"},
{"key1": "b", "key2": "a"}, {"key1": "b", "key2": "a"},

View File

@@ -1,17 +1,90 @@
// Code generated by mockery; DO NOT EDIT. // Code generated by mockery v2.43.2. DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
package mocks package mocks
import ( import (
"time" client "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
mock "github.com/stretchr/testify/mock" mock "github.com/stretchr/testify/mock"
"sigs.k8s.io/controller-runtime/pkg/client"
time "time"
v1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
// Generator is an autogenerated mock type for the Generator type
type Generator struct {
mock.Mock
}
// GenerateParams provides a mock function with given fields: appSetGenerator, applicationSetInfo, _a2
func (_m *Generator) GenerateParams(appSetGenerator *v1alpha1.ApplicationSetGenerator, applicationSetInfo *v1alpha1.ApplicationSet, _a2 client.Client) ([]map[string]interface{}, error) {
ret := _m.Called(appSetGenerator, applicationSetInfo, _a2)
if len(ret) == 0 {
panic("no return value specified for GenerateParams")
}
var r0 []map[string]interface{}
var r1 error
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) ([]map[string]interface{}, error)); ok {
return rf(appSetGenerator, applicationSetInfo, _a2)
}
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) []map[string]interface{}); ok {
r0 = rf(appSetGenerator, applicationSetInfo, _a2)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]map[string]interface{})
}
}
if rf, ok := ret.Get(1).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) error); ok {
r1 = rf(appSetGenerator, applicationSetInfo, _a2)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetRequeueAfter provides a mock function with given fields: appSetGenerator
func (_m *Generator) GetRequeueAfter(appSetGenerator *v1alpha1.ApplicationSetGenerator) time.Duration {
ret := _m.Called(appSetGenerator)
if len(ret) == 0 {
panic("no return value specified for GetRequeueAfter")
}
var r0 time.Duration
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator) time.Duration); ok {
r0 = rf(appSetGenerator)
} else {
r0 = ret.Get(0).(time.Duration)
}
return r0
}
// GetTemplate provides a mock function with given fields: appSetGenerator
func (_m *Generator) GetTemplate(appSetGenerator *v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate {
ret := _m.Called(appSetGenerator)
if len(ret) == 0 {
panic("no return value specified for GetTemplate")
}
var r0 *v1alpha1.ApplicationSetTemplate
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate); ok {
r0 = rf(appSetGenerator)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.ApplicationSetTemplate)
}
}
return r0
}
// NewGenerator creates a new instance of Generator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // NewGenerator creates a new instance of Generator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value. // The first argument is typically a *testing.T value.
func NewGenerator(t interface { func NewGenerator(t interface {
@@ -25,194 +98,3 @@ func NewGenerator(t interface {
return mock return mock
} }
// Generator is an autogenerated mock type for the Generator type
type Generator struct {
mock.Mock
}
type Generator_Expecter struct {
mock *mock.Mock
}
func (_m *Generator) EXPECT() *Generator_Expecter {
return &Generator_Expecter{mock: &_m.Mock}
}
// GenerateParams provides a mock function for the type Generator
func (_mock *Generator) GenerateParams(appSetGenerator *v1alpha1.ApplicationSetGenerator, applicationSetInfo *v1alpha1.ApplicationSet, client1 client.Client) ([]map[string]any, error) {
ret := _mock.Called(appSetGenerator, applicationSetInfo, client1)
if len(ret) == 0 {
panic("no return value specified for GenerateParams")
}
var r0 []map[string]any
var r1 error
if returnFunc, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) ([]map[string]any, error)); ok {
return returnFunc(appSetGenerator, applicationSetInfo, client1)
}
if returnFunc, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) []map[string]any); ok {
r0 = returnFunc(appSetGenerator, applicationSetInfo, client1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]map[string]any)
}
}
if returnFunc, ok := ret.Get(1).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) error); ok {
r1 = returnFunc(appSetGenerator, applicationSetInfo, client1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Generator_GenerateParams_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GenerateParams'
type Generator_GenerateParams_Call struct {
*mock.Call
}
// GenerateParams is a helper method to define mock.On call
// - appSetGenerator *v1alpha1.ApplicationSetGenerator
// - applicationSetInfo *v1alpha1.ApplicationSet
// - client1 client.Client
func (_e *Generator_Expecter) GenerateParams(appSetGenerator interface{}, applicationSetInfo interface{}, client1 interface{}) *Generator_GenerateParams_Call {
return &Generator_GenerateParams_Call{Call: _e.mock.On("GenerateParams", appSetGenerator, applicationSetInfo, client1)}
}
func (_c *Generator_GenerateParams_Call) Run(run func(appSetGenerator *v1alpha1.ApplicationSetGenerator, applicationSetInfo *v1alpha1.ApplicationSet, client1 client.Client)) *Generator_GenerateParams_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 *v1alpha1.ApplicationSetGenerator
if args[0] != nil {
arg0 = args[0].(*v1alpha1.ApplicationSetGenerator)
}
var arg1 *v1alpha1.ApplicationSet
if args[1] != nil {
arg1 = args[1].(*v1alpha1.ApplicationSet)
}
var arg2 client.Client
if args[2] != nil {
arg2 = args[2].(client.Client)
}
run(
arg0,
arg1,
arg2,
)
})
return _c
}
func (_c *Generator_GenerateParams_Call) Return(stringToVs []map[string]any, err error) *Generator_GenerateParams_Call {
_c.Call.Return(stringToVs, err)
return _c
}
func (_c *Generator_GenerateParams_Call) RunAndReturn(run func(appSetGenerator *v1alpha1.ApplicationSetGenerator, applicationSetInfo *v1alpha1.ApplicationSet, client1 client.Client) ([]map[string]any, error)) *Generator_GenerateParams_Call {
_c.Call.Return(run)
return _c
}
// GetRequeueAfter provides a mock function for the type Generator
func (_mock *Generator) GetRequeueAfter(appSetGenerator *v1alpha1.ApplicationSetGenerator) time.Duration {
ret := _mock.Called(appSetGenerator)
if len(ret) == 0 {
panic("no return value specified for GetRequeueAfter")
}
var r0 time.Duration
if returnFunc, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator) time.Duration); ok {
r0 = returnFunc(appSetGenerator)
} else {
r0 = ret.Get(0).(time.Duration)
}
return r0
}
// Generator_GetRequeueAfter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRequeueAfter'
type Generator_GetRequeueAfter_Call struct {
*mock.Call
}
// GetRequeueAfter is a helper method to define mock.On call
// - appSetGenerator *v1alpha1.ApplicationSetGenerator
func (_e *Generator_Expecter) GetRequeueAfter(appSetGenerator interface{}) *Generator_GetRequeueAfter_Call {
return &Generator_GetRequeueAfter_Call{Call: _e.mock.On("GetRequeueAfter", appSetGenerator)}
}
func (_c *Generator_GetRequeueAfter_Call) Run(run func(appSetGenerator *v1alpha1.ApplicationSetGenerator)) *Generator_GetRequeueAfter_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 *v1alpha1.ApplicationSetGenerator
if args[0] != nil {
arg0 = args[0].(*v1alpha1.ApplicationSetGenerator)
}
run(
arg0,
)
})
return _c
}
func (_c *Generator_GetRequeueAfter_Call) Return(duration time.Duration) *Generator_GetRequeueAfter_Call {
_c.Call.Return(duration)
return _c
}
func (_c *Generator_GetRequeueAfter_Call) RunAndReturn(run func(appSetGenerator *v1alpha1.ApplicationSetGenerator) time.Duration) *Generator_GetRequeueAfter_Call {
_c.Call.Return(run)
return _c
}
// GetTemplate provides a mock function for the type Generator
func (_mock *Generator) GetTemplate(appSetGenerator *v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate {
ret := _mock.Called(appSetGenerator)
if len(ret) == 0 {
panic("no return value specified for GetTemplate")
}
var r0 *v1alpha1.ApplicationSetTemplate
if returnFunc, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate); ok {
r0 = returnFunc(appSetGenerator)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.ApplicationSetTemplate)
}
}
return r0
}
// Generator_GetTemplate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTemplate'
type Generator_GetTemplate_Call struct {
*mock.Call
}
// GetTemplate is a helper method to define mock.On call
// - appSetGenerator *v1alpha1.ApplicationSetGenerator
func (_e *Generator_Expecter) GetTemplate(appSetGenerator interface{}) *Generator_GetTemplate_Call {
return &Generator_GetTemplate_Call{Call: _e.mock.On("GetTemplate", appSetGenerator)}
}
func (_c *Generator_GetTemplate_Call) Run(run func(appSetGenerator *v1alpha1.ApplicationSetGenerator)) *Generator_GetTemplate_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 *v1alpha1.ApplicationSetGenerator
if args[0] != nil {
arg0 = args[0].(*v1alpha1.ApplicationSetGenerator)
}
run(
arg0,
)
})
return _c
}
func (_c *Generator_GetTemplate_Call) Return(applicationSetTemplate *v1alpha1.ApplicationSetTemplate) *Generator_GetTemplate_Call {
_c.Call.Return(applicationSetTemplate)
return _c
}
func (_c *Generator_GetTemplate_Call) RunAndReturn(run func(appSetGenerator *v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate) *Generator_GetTemplate_Call {
_c.Call.Return(run)
return _c
}

View File

@@ -2,7 +2,6 @@ package generators
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
@@ -10,28 +9,33 @@ import (
"github.com/jeremywohl/flatten" "github.com/jeremywohl/flatten"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v3/util/settings" "github.com/argoproj/argo-cd/v2/util/settings"
"github.com/argoproj/argo-cd/v3/applicationset/services/plugin" "github.com/argoproj/argo-cd/v2/applicationset/services/plugin"
) )
const ( const (
DefaultPluginRequeueAfter = 30 * time.Minute DefaultPluginRequeueAfterSeconds = 30 * time.Minute
) )
var _ Generator = (*PluginGenerator)(nil) var _ Generator = (*PluginGenerator)(nil)
type PluginGenerator struct { type PluginGenerator struct {
client client.Client client client.Client
ctx context.Context
clientset kubernetes.Interface
namespace string namespace string
} }
func NewPluginGenerator(client client.Client, namespace string) Generator { func NewPluginGenerator(client client.Client, ctx context.Context, clientset kubernetes.Interface, namespace string) Generator {
g := &PluginGenerator{ g := &PluginGenerator{
client: client, client: client,
ctx: ctx,
clientset: clientset,
namespace: namespace, namespace: namespace,
} }
return g return g
@@ -44,20 +48,20 @@ func (g *PluginGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Ap
return time.Duration(*appSetGenerator.Plugin.RequeueAfterSeconds) * time.Second return time.Duration(*appSetGenerator.Plugin.RequeueAfterSeconds) * time.Second
} }
return DefaultPluginRequeueAfter return DefaultPluginRequeueAfterSeconds
} }
func (g *PluginGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate { func (g *PluginGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
return &appSetGenerator.Plugin.Template return &appSetGenerator.Plugin.Template
} }
func (g *PluginGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) { func (g *PluginGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
if appSetGenerator == nil { if appSetGenerator == nil {
return nil, ErrEmptyAppSetGenerator return nil, EmptyAppSetGeneratorError
} }
if appSetGenerator.Plugin == nil { if appSetGenerator.Plugin == nil {
return nil, ErrEmptyAppSetGenerator return nil, EmptyAppSetGeneratorError
} }
ctx := context.Background() ctx := context.Background()
@@ -101,18 +105,18 @@ func (g *PluginGenerator) getPluginFromGenerator(ctx context.Context, appSetName
} }
} }
pluginClient, err := plugin.NewPluginService(appSetName, cm["baseUrl"], token, requestTimeout) pluginClient, err := plugin.NewPluginService(ctx, appSetName, cm["baseUrl"], token, requestTimeout)
if err != nil { if err != nil {
return nil, fmt.Errorf("error initializing plugin client: %w", err) return nil, fmt.Errorf("error initializing plugin client: %w", err)
} }
return pluginClient, nil return pluginClient, nil
} }
func (g *PluginGenerator) generateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, objectsFound []map[string]any, pluginParams argoprojiov1alpha1.PluginParameters, useGoTemplate bool) ([]map[string]any, error) { func (g *PluginGenerator) generateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, objectsFound []map[string]interface{}, pluginParams argoprojiov1alpha1.PluginParameters, useGoTemplate bool) ([]map[string]interface{}, error) {
res := []map[string]any{} res := []map[string]interface{}{}
for _, objectFound := range objectsFound { for _, objectFound := range objectsFound {
params := map[string]any{} params := map[string]interface{}{}
if useGoTemplate { if useGoTemplate {
for k, v := range objectFound { for k, v := range objectFound {
@@ -128,7 +132,7 @@ func (g *PluginGenerator) generateParams(appSetGenerator *argoprojiov1alpha1.App
} }
} }
params["generator"] = map[string]any{ params["generator"] = map[string]interface{}{
"input": map[string]argoprojiov1alpha1.PluginParameters{ "input": map[string]argoprojiov1alpha1.PluginParameters{
"parameters": pluginParams, "parameters": pluginParams,
}, },
@@ -188,14 +192,14 @@ func (g *PluginGenerator) getConfigMap(ctx context.Context, configMapRef string)
return nil, err return nil, err
} }
baseURL, ok := cm.Data["baseUrl"] baseUrl, ok := cm.Data["baseUrl"]
if !ok || baseURL == "" { if !ok || baseUrl == "" {
return nil, errors.New("baseUrl not found in ConfigMap") return nil, fmt.Errorf("baseUrl not found in ConfigMap")
} }
token, ok := cm.Data["token"] token, ok := cm.Data["token"]
if !ok || token == "" { if !ok || token == "" {
return nil, errors.New("token not found in ConfigMap") return nil, fmt.Errorf("token not found in ConfigMap")
} }
return cm.Data, nil return cm.Data, nil

View File

@@ -1,8 +1,8 @@
package generators package generators
import ( import (
"context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@@ -11,31 +11,33 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
kubefake "k8s.io/client-go/kubernetes/fake"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/argoproj/argo-cd/v3/applicationset/services/plugin" "github.com/argoproj/argo-cd/v2/applicationset/services/plugin"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
func TestPluginGenerateParams(t *testing.T) { func TestPluginGenerateParams(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
configmap *corev1.ConfigMap configmap *v1.ConfigMap
secret *corev1.Secret secret *v1.Secret
inputParameters map[string]apiextensionsv1.JSON inputParameters map[string]apiextensionsv1.JSON
values map[string]string values map[string]string
gotemplate bool gotemplate bool
expected []map[string]any expected []map[string]interface{}
content []byte content []byte
expectedError error expectedError error
}{ }{
{ {
name: "simple case", name: "simple case",
configmap: &corev1.ConfigMap{ configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm", Name: "first-plugin-cm",
Namespace: "default", Namespace: "default",
@@ -45,7 +47,7 @@ func TestPluginGenerateParams(t *testing.T) {
"token": "$plugin.token", "token": "$plugin.token",
}, },
}, },
secret: &corev1.Secret{ secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret", Name: "argocd-secret",
Namespace: "default", Namespace: "default",
@@ -71,13 +73,13 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": 123 "key3": 123
}] }]
}}`), }}`),
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"key1": "val1", "key1": "val1",
"key2.key2_1": "val2_1", "key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1", "key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123", "key3": "123",
"generator": map[string]any{ "generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{ "input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{ Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)}, "pkey1": {Raw: []byte(`"val1"`)},
@@ -91,7 +93,7 @@ func TestPluginGenerateParams(t *testing.T) {
}, },
{ {
name: "simple case with values", name: "simple case with values",
configmap: &corev1.ConfigMap{ configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm", Name: "first-plugin-cm",
Namespace: "default", Namespace: "default",
@@ -101,7 +103,7 @@ func TestPluginGenerateParams(t *testing.T) {
"token": "$plugin.token", "token": "$plugin.token",
}, },
}, },
secret: &corev1.Secret{ secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret", Name: "argocd-secret",
Namespace: "default", Namespace: "default",
@@ -131,7 +133,7 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": 123 "key3": 123
}] }]
}}`), }}`),
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"key1": "val1", "key1": "val1",
"key2.key2_1": "val2_1", "key2.key2_1": "val2_1",
@@ -139,7 +141,7 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": "123", "key3": "123",
"values.valuekey1": "valuevalue1", "values.valuekey1": "valuevalue1",
"values.valuekey2": "templated-val1", "values.valuekey2": "templated-val1",
"generator": map[string]any{ "generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{ "input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{ Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)}, "pkey1": {Raw: []byte(`"val1"`)},
@@ -153,7 +155,7 @@ func TestPluginGenerateParams(t *testing.T) {
}, },
{ {
name: "simple case with gotemplate", name: "simple case with gotemplate",
configmap: &corev1.ConfigMap{ configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm", Name: "first-plugin-cm",
Namespace: "default", Namespace: "default",
@@ -163,7 +165,7 @@ func TestPluginGenerateParams(t *testing.T) {
"token": "$plugin.token", "token": "$plugin.token",
}, },
}, },
secret: &corev1.Secret{ secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret", Name: "argocd-secret",
Namespace: "default", Namespace: "default",
@@ -189,17 +191,17 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": 123 "key3": 123
}] }]
}}`), }}`),
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"key1": "val1", "key1": "val1",
"key2": map[string]any{ "key2": map[string]interface{}{
"key2_1": "val2_1", "key2_1": "val2_1",
"key2_2": map[string]any{ "key2_2": map[string]interface{}{
"key2_2_1": "val2_2_1", "key2_2_1": "val2_2_1",
}, },
}, },
"key3": float64(123), "key3": float64(123),
"generator": map[string]any{ "generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{ "input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{ Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)}, "pkey1": {Raw: []byte(`"val1"`)},
@@ -213,7 +215,7 @@ func TestPluginGenerateParams(t *testing.T) {
}, },
{ {
name: "simple case with appended params", name: "simple case with appended params",
configmap: &corev1.ConfigMap{ configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm", Name: "first-plugin-cm",
Namespace: "default", Namespace: "default",
@@ -223,7 +225,7 @@ func TestPluginGenerateParams(t *testing.T) {
"token": "$plugin.token", "token": "$plugin.token",
}, },
}, },
secret: &corev1.Secret{ secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret", Name: "argocd-secret",
Namespace: "default", Namespace: "default",
@@ -248,14 +250,14 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": 123, "key3": 123,
"pkey2": "valplugin" "pkey2": "valplugin"
}]}}`), }]}}`),
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"key1": "val1", "key1": "val1",
"key2.key2_1": "val2_1", "key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1", "key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123", "key3": "123",
"pkey2": "valplugin", "pkey2": "valplugin",
"generator": map[string]any{ "generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{ "input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{ Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)}, "pkey1": {Raw: []byte(`"val1"`)},
@@ -269,7 +271,7 @@ func TestPluginGenerateParams(t *testing.T) {
}, },
{ {
name: "no params", name: "no params",
configmap: &corev1.ConfigMap{ configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm", Name: "first-plugin-cm",
Namespace: "default", Namespace: "default",
@@ -279,7 +281,7 @@ func TestPluginGenerateParams(t *testing.T) {
"token": "$plugin.token", "token": "$plugin.token",
}, },
}, },
secret: &corev1.Secret{ secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret", Name: "argocd-secret",
Namespace: "default", Namespace: "default",
@@ -302,14 +304,14 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": 123 "key3": 123
}] }]
}}`), }}`),
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"key1": "val1", "key1": "val1",
"key2.key2_1": "val2_1", "key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1", "key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123", "key3": "123",
"generator": map[string]any{ "generator": map[string]interface{}{
"input": map[string]map[string]any{ "input": map[string]map[string]interface{}{
"parameters": {}, "parameters": {},
}, },
}, },
@@ -319,7 +321,7 @@ func TestPluginGenerateParams(t *testing.T) {
}, },
{ {
name: "empty return", name: "empty return",
configmap: &corev1.ConfigMap{ configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm", Name: "first-plugin-cm",
Namespace: "default", Namespace: "default",
@@ -329,7 +331,7 @@ func TestPluginGenerateParams(t *testing.T) {
"token": "$plugin.token", "token": "$plugin.token",
}, },
}, },
secret: &corev1.Secret{ secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret", Name: "argocd-secret",
Namespace: "default", Namespace: "default",
@@ -341,12 +343,12 @@ func TestPluginGenerateParams(t *testing.T) {
inputParameters: map[string]apiextensionsv1.JSON{}, inputParameters: map[string]apiextensionsv1.JSON{},
gotemplate: false, gotemplate: false,
content: []byte(`{"input": {"parameters": []}}`), content: []byte(`{"input": {"parameters": []}}`),
expected: []map[string]any{}, expected: []map[string]interface{}{},
expectedError: nil, expectedError: nil,
}, },
{ {
name: "wrong return", name: "wrong return",
configmap: &corev1.ConfigMap{ configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm", Name: "first-plugin-cm",
Namespace: "default", Namespace: "default",
@@ -356,7 +358,7 @@ func TestPluginGenerateParams(t *testing.T) {
"token": "$plugin.token", "token": "$plugin.token",
}, },
}, },
secret: &corev1.Secret{ secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret", Name: "argocd-secret",
Namespace: "default", Namespace: "default",
@@ -368,12 +370,12 @@ func TestPluginGenerateParams(t *testing.T) {
inputParameters: map[string]apiextensionsv1.JSON{}, inputParameters: map[string]apiextensionsv1.JSON{},
gotemplate: false, gotemplate: false,
content: []byte(`wrong body ...`), content: []byte(`wrong body ...`),
expected: []map[string]any{}, expected: []map[string]interface{}{},
expectedError: errors.New("error listing params: error get api 'set': invalid character 'w' looking for beginning of value: wrong body ..."), expectedError: fmt.Errorf("error listing params: error get api 'set': invalid character 'w' looking for beginning of value: wrong body ..."),
}, },
{ {
name: "external secret", name: "external secret",
configmap: &corev1.ConfigMap{ configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm", Name: "first-plugin-cm",
Namespace: "default", Namespace: "default",
@@ -383,7 +385,7 @@ func TestPluginGenerateParams(t *testing.T) {
"token": "$plugin-secret:plugin.token", "token": "$plugin-secret:plugin.token",
}, },
}, },
secret: &corev1.Secret{ secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "plugin-secret", Name: "plugin-secret",
Namespace: "default", Namespace: "default",
@@ -408,14 +410,14 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": 123, "key3": 123,
"pkey2": "valplugin" "pkey2": "valplugin"
}]}}`), }]}}`),
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"key1": "val1", "key1": "val1",
"key2.key2_1": "val2_1", "key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1", "key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123", "key3": "123",
"pkey2": "valplugin", "pkey2": "valplugin",
"generator": map[string]any{ "generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{ "input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{ Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)}, "pkey1": {Raw: []byte(`"val1"`)},
@@ -429,7 +431,7 @@ func TestPluginGenerateParams(t *testing.T) {
}, },
{ {
name: "no secret", name: "no secret",
configmap: &corev1.ConfigMap{ configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm", Name: "first-plugin-cm",
Namespace: "default", Namespace: "default",
@@ -439,7 +441,7 @@ func TestPluginGenerateParams(t *testing.T) {
"token": "$plugin.token", "token": "$plugin.token",
}, },
}, },
secret: &corev1.Secret{}, secret: &v1.Secret{},
inputParameters: map[string]apiextensionsv1.JSON{ inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)}, "pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)}, "pkey2": {Raw: []byte(`"val2"`)},
@@ -457,13 +459,13 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": 123 "key3": 123
}] }]
}}`), }}`),
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"key1": "val1", "key1": "val1",
"key2.key2_1": "val2_1", "key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1", "key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123", "key3": "123",
"generator": map[string]any{ "generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{ "input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{ Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)}, "pkey1": {Raw: []byte(`"val1"`)},
@@ -473,12 +475,12 @@ func TestPluginGenerateParams(t *testing.T) {
}, },
}, },
}, },
expectedError: errors.New("error getting plugin from generator: error fetching Secret token: error fetching secret default/argocd-secret: secrets \"argocd-secret\" not found"), expectedError: fmt.Errorf("error getting plugin from generator: error fetching Secret token: error fetching secret default/argocd-secret: secrets \"argocd-secret\" not found"),
}, },
{ {
name: "no configmap", name: "no configmap",
configmap: &corev1.ConfigMap{}, configmap: &v1.ConfigMap{},
secret: &corev1.Secret{ secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret", Name: "argocd-secret",
Namespace: "default", Namespace: "default",
@@ -504,13 +506,13 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": 123 "key3": 123
}] }]
}}`), }}`),
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"key1": "val1", "key1": "val1",
"key2.key2_1": "val2_1", "key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1", "key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123", "key3": "123",
"generator": map[string]any{ "generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{ "input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{ Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)}, "pkey1": {Raw: []byte(`"val1"`)},
@@ -520,11 +522,11 @@ func TestPluginGenerateParams(t *testing.T) {
}, },
}, },
}, },
expectedError: errors.New("error getting plugin from generator: error fetching ConfigMap: configmaps \"\" not found"), expectedError: fmt.Errorf("error getting plugin from generator: error fetching ConfigMap: configmaps \"\" not found"),
}, },
{ {
name: "no baseUrl", name: "no baseUrl",
configmap: &corev1.ConfigMap{ configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm", Name: "first-plugin-cm",
Namespace: "default", Namespace: "default",
@@ -533,7 +535,7 @@ func TestPluginGenerateParams(t *testing.T) {
"token": "$plugin.token", "token": "$plugin.token",
}, },
}, },
secret: &corev1.Secret{ secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret", Name: "argocd-secret",
Namespace: "default", Namespace: "default",
@@ -559,13 +561,13 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": 123 "key3": 123
}] }]
}}`), }}`),
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"key1": "val1", "key1": "val1",
"key2.key2_1": "val2_1", "key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1", "key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123", "key3": "123",
"generator": map[string]any{ "generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{ "input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{ Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)}, "pkey1": {Raw: []byte(`"val1"`)},
@@ -575,11 +577,11 @@ func TestPluginGenerateParams(t *testing.T) {
}, },
}, },
}, },
expectedError: errors.New("error getting plugin from generator: error fetching ConfigMap: baseUrl not found in ConfigMap"), expectedError: fmt.Errorf("error getting plugin from generator: error fetching ConfigMap: baseUrl not found in ConfigMap"),
}, },
{ {
name: "no token", name: "no token",
configmap: &corev1.ConfigMap{ configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm", Name: "first-plugin-cm",
Namespace: "default", Namespace: "default",
@@ -588,7 +590,7 @@ func TestPluginGenerateParams(t *testing.T) {
"baseUrl": "http://127.0.0.1", "baseUrl": "http://127.0.0.1",
}, },
}, },
secret: &corev1.Secret{}, secret: &v1.Secret{},
inputParameters: map[string]apiextensionsv1.JSON{ inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)}, "pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)}, "pkey2": {Raw: []byte(`"val2"`)},
@@ -606,13 +608,13 @@ func TestPluginGenerateParams(t *testing.T) {
"key3": 123 "key3": 123
}] }]
}}`), }}`),
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"key1": "val1", "key1": "val1",
"key2.key2_1": "val2_1", "key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1", "key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123", "key3": "123",
"generator": map[string]any{ "generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{ "input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{ Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)}, "pkey1": {Raw: []byte(`"val1"`)},
@@ -622,10 +624,12 @@ func TestPluginGenerateParams(t *testing.T) {
}, },
}, },
}, },
expectedError: errors.New("error getting plugin from generator: error fetching ConfigMap: token not found in ConfigMap"), expectedError: fmt.Errorf("error getting plugin from generator: error fetching ConfigMap: token not found in ConfigMap"),
}, },
} }
ctx := context.Background()
for _, testCase := range testCases { for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
generatorConfig := argoprojiov1alpha1.ApplicationSetGenerator{ generatorConfig := argoprojiov1alpha1.ApplicationSetGenerator{
@@ -662,9 +666,11 @@ func TestPluginGenerateParams(t *testing.T) {
testCase.configmap.Data["baseUrl"] = fakeServer.URL testCase.configmap.Data["baseUrl"] = fakeServer.URL
} }
fakeClient := kubefake.NewSimpleClientset(append([]runtime.Object{}, testCase.configmap, testCase.secret)...)
fakeClientWithCache := fake.NewClientBuilder().WithObjects([]client.Object{testCase.configmap, testCase.secret}...).Build() fakeClientWithCache := fake.NewClientBuilder().WithObjects([]client.Object{testCase.configmap, testCase.secret}...).Build()
pluginGenerator := NewPluginGenerator(fakeClientWithCache, "default") pluginGenerator := NewPluginGenerator(fakeClientWithCache, ctx, fakeClient, "default")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@@ -684,11 +690,11 @@ func TestPluginGenerateParams(t *testing.T) {
require.EqualError(t, err, testCase.expectedError.Error()) require.EqualError(t, err, testCase.expectedError.Error())
} else { } else {
require.NoError(t, err) require.NoError(t, err)
expectedJSON, err := json.Marshal(testCase.expected) expectedJson, err := json.Marshal(testCase.expected)
require.NoError(t, err) require.NoError(t, err)
gotJSON, err := json.Marshal(got) gotJson, err := json.Marshal(got)
require.NoError(t, err) require.NoError(t, err)
assert.JSONEq(t, string(expectedJSON), string(gotJSON)) assert.JSONEq(t, string(expectedJson), string(gotJson))
} }
}) })
} }

View File

@@ -2,9 +2,7 @@ package generators
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net/http"
"strconv" "strconv"
"time" "time"
@@ -12,16 +10,15 @@ import (
"github.com/gosimple/slug" "github.com/gosimple/slug"
"github.com/argoproj/argo-cd/v3/applicationset/services" pullrequest "github.com/argoproj/argo-cd/v2/applicationset/services/pull_request"
pullrequest "github.com/argoproj/argo-cd/v3/applicationset/services/pull_request" "github.com/argoproj/argo-cd/v2/applicationset/utils"
"github.com/argoproj/argo-cd/v3/applicationset/utils" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
) )
var _ Generator = (*PullRequestGenerator)(nil) var _ Generator = (*PullRequestGenerator)(nil)
const ( const (
DefaultPullRequestRequeueAfter = 30 * time.Minute DefaultPullRequestRequeueAfterSeconds = 30 * time.Minute
) )
type PullRequestGenerator struct { type PullRequestGenerator struct {
@@ -46,20 +43,20 @@ func (g *PullRequestGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alph
return time.Duration(*appSetGenerator.PullRequest.RequeueAfterSeconds) * time.Second return time.Duration(*appSetGenerator.PullRequest.RequeueAfterSeconds) * time.Second
} }
return DefaultPullRequestRequeueAfter return DefaultPullRequestRequeueAfterSeconds
} }
func (g *PullRequestGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate { func (g *PullRequestGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
return &appSetGenerator.PullRequest.Template return &appSetGenerator.PullRequest.Template
} }
func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) { func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
if appSetGenerator == nil { if appSetGenerator == nil {
return nil, ErrEmptyAppSetGenerator return nil, EmptyAppSetGeneratorError
} }
if appSetGenerator.PullRequest == nil { if appSetGenerator.PullRequest == nil {
return nil, ErrEmptyAppSetGenerator return nil, EmptyAppSetGeneratorError
} }
ctx := context.Background() ctx := context.Background()
@@ -72,7 +69,7 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
if err != nil { if err != nil {
return nil, fmt.Errorf("error listing repos: %w", err) return nil, fmt.Errorf("error listing repos: %w", err)
} }
params := make([]map[string]any, 0, len(pulls)) params := make([]map[string]interface{}, 0, len(pulls))
// In order to follow the DNS label standard as defined in RFC 1123, // In order to follow the DNS label standard as defined in RFC 1123,
// we need to limit the 'branch' to 50 to give room to append/suffix-ing it // we need to limit the 'branch' to 50 to give room to append/suffix-ing it
@@ -98,7 +95,7 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
shortSHALength7 = len(pull.HeadSHA) shortSHALength7 = len(pull.HeadSHA)
} }
paramMap := map[string]any{ paramMap := map[string]interface{}{
"number": strconv.Itoa(pull.Number), "number": strconv.Itoa(pull.Number),
"title": pull.Title, "title": pull.Title,
"branch": pull.Branch, "branch": pull.Branch,
@@ -111,11 +108,6 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
"author": pull.Author, "author": pull.Author,
} }
err := appendTemplatedValues(appSetGenerator.PullRequest.Values, paramMap, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions)
if err != nil {
return nil, fmt.Errorf("failed to append templated values: %w", err)
}
// PR lables will only be supported for Go Template appsets, since fasttemplate will be deprecated. // PR lables will only be supported for Go Template appsets, since fasttemplate will be deprecated.
if applicationSetInfo != nil && applicationSetInfo.Spec.GoTemplate { if applicationSetInfo != nil && applicationSetInfo.Spec.GoTemplate {
paramMap["labels"] = pull.Labels paramMap["labels"] = pull.Labels
@@ -151,7 +143,7 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err) return nil, fmt.Errorf("error fetching Secret token: %w", err)
} }
return pullrequest.NewGitLabService(token, providerConfig.API, providerConfig.Project, providerConfig.Labels, providerConfig.PullRequestState, g.scmRootCAPath, providerConfig.Insecure, caCerts) return pullrequest.NewGitLabService(ctx, token, providerConfig.API, providerConfig.Project, providerConfig.Labels, providerConfig.PullRequestState, g.scmRootCAPath, providerConfig.Insecure, caCerts)
} }
if generatorConfig.Gitea != nil { if generatorConfig.Gitea != nil {
providerConfig := generatorConfig.Gitea providerConfig := generatorConfig.Gitea
@@ -159,8 +151,7 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err) return nil, fmt.Errorf("error fetching Secret token: %w", err)
} }
return pullrequest.NewGiteaService(ctx, token, providerConfig.API, providerConfig.Owner, providerConfig.Repo, providerConfig.Insecure)
return pullrequest.NewGiteaService(token, providerConfig.API, providerConfig.Owner, providerConfig.Repo, providerConfig.Labels, providerConfig.Insecure)
} }
if generatorConfig.BitbucketServer != nil { if generatorConfig.BitbucketServer != nil {
providerConfig := generatorConfig.BitbucketServer providerConfig := generatorConfig.BitbucketServer
@@ -184,8 +175,9 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
return nil, fmt.Errorf("error fetching Secret token: %w", err) return nil, fmt.Errorf("error fetching Secret token: %w", err)
} }
return pullrequest.NewBitbucketServiceBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.Repo, g.scmRootCAPath, providerConfig.Insecure, caCerts) return pullrequest.NewBitbucketServiceBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.Repo, g.scmRootCAPath, providerConfig.Insecure, caCerts)
} else {
return pullrequest.NewBitbucketServiceNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.Repo, g.scmRootCAPath, providerConfig.Insecure, caCerts)
} }
return pullrequest.NewBitbucketServiceNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.Repo, g.scmRootCAPath, providerConfig.Insecure, caCerts)
} }
if generatorConfig.Bitbucket != nil { if generatorConfig.Bitbucket != nil {
providerConfig := generatorConfig.Bitbucket providerConfig := generatorConfig.Bitbucket
@@ -201,8 +193,9 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
return nil, fmt.Errorf("error fetching Secret token: %w", err) return nil, fmt.Errorf("error fetching Secret token: %w", err)
} }
return pullrequest.NewBitbucketCloudServiceBasicAuth(providerConfig.API, providerConfig.BasicAuth.Username, password, providerConfig.Owner, providerConfig.Repo) return pullrequest.NewBitbucketCloudServiceBasicAuth(providerConfig.API, providerConfig.BasicAuth.Username, password, providerConfig.Owner, providerConfig.Repo)
} else {
return pullrequest.NewBitbucketCloudServiceNoAuth(providerConfig.API, providerConfig.Owner, providerConfig.Repo)
} }
return pullrequest.NewBitbucketCloudServiceNoAuth(providerConfig.API, providerConfig.Owner, providerConfig.Repo)
} }
if generatorConfig.AzureDevOps != nil { if generatorConfig.AzureDevOps != nil {
providerConfig := generatorConfig.AzureDevOps providerConfig := generatorConfig.AzureDevOps
@@ -210,33 +203,18 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err) return nil, fmt.Errorf("error fetching Secret token: %w", err)
} }
return pullrequest.NewAzureDevOpsService(token, providerConfig.API, providerConfig.Organization, providerConfig.Project, providerConfig.Repo, providerConfig.Labels) return pullrequest.NewAzureDevOpsService(ctx, token, providerConfig.API, providerConfig.Organization, providerConfig.Project, providerConfig.Repo, providerConfig.Labels)
} }
return nil, errors.New("no Pull Request provider implementation configured") return nil, fmt.Errorf("no Pull Request provider implementation configured")
} }
func (g *PullRequestGenerator) github(ctx context.Context, cfg *argoprojiov1alpha1.PullRequestGeneratorGithub, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) { func (g *PullRequestGenerator) github(ctx context.Context, cfg *argoprojiov1alpha1.PullRequestGeneratorGithub, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
var metricsCtx *services.MetricsContext
var httpClient *http.Client
if g.enableGitHubAPIMetrics {
metricsCtx = &services.MetricsContext{
AppSetNamespace: applicationSetInfo.Namespace,
AppSetName: applicationSetInfo.Name,
}
httpClient = services.NewGitHubMetricsClient(metricsCtx)
}
// use an app if it was configured // use an app if it was configured
if cfg.AppSecretName != "" { if cfg.AppSecretName != "" {
auth, err := g.GitHubApps.GetAuthSecret(ctx, cfg.AppSecretName) auth, err := g.GitHubApps.GetAuthSecret(ctx, cfg.AppSecretName)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting GitHub App secret: %w", err) return nil, fmt.Errorf("error getting GitHub App secret: %w", err)
} }
if g.enableGitHubAPIMetrics {
return pullrequest.NewGithubAppService(*auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels, httpClient)
}
return pullrequest.NewGithubAppService(*auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels) return pullrequest.NewGithubAppService(*auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
} }
@@ -245,9 +223,5 @@ func (g *PullRequestGenerator) github(ctx context.Context, cfg *argoprojiov1alph
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err) return nil, fmt.Errorf("error fetching Secret token: %w", err)
} }
return pullrequest.NewGithubService(ctx, token, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
if g.enableGitHubAPIMetrics {
return pullrequest.NewGithubService(token, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels, httpClient)
}
return pullrequest.NewGithubService(token, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
} }

View File

@@ -2,23 +2,22 @@ package generators
import ( import (
"context" "context"
"errors" "fmt"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
pullrequest "github.com/argoproj/argo-cd/v3/applicationset/services/pull_request" pullrequest "github.com/argoproj/argo-cd/v2/applicationset/services/pull_request"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
func TestPullRequestGithubGenerateParams(t *testing.T) { func TestPullRequestGithubGenerateParams(t *testing.T) {
ctx := t.Context() ctx := context.Background()
cases := []struct { cases := []struct {
selectFunc func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) selectFunc func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error)
values map[string]string expected []map[string]interface{}
expected []map[string]any
expectedErr error expectedErr error
applicationSet argoprojiov1alpha1.ApplicationSet applicationSet argoprojiov1alpha1.ApplicationSet
}{ }{
@@ -39,7 +38,7 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
nil, nil,
) )
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"number": "1", "number": "1",
"title": "title1", "title": "title1",
@@ -72,7 +71,7 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
nil, nil,
) )
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"number": "2", "number": "2",
"title": "title2", "title": "title2",
@@ -105,7 +104,7 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
nil, nil,
) )
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"number": "1", "number": "1",
"title": "title1", "title": "title1",
@@ -125,51 +124,12 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) { selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
return pullrequest.NewFakeService( return pullrequest.NewFakeService(
ctx, ctx,
[]*pullrequest.PullRequest{
{
Number: 1,
Title: "title1",
Branch: "my_branch",
TargetBranch: "master",
HeadSHA: "abcd",
Author: "testName",
},
},
nil, nil,
) fmt.Errorf("fake error"),
},
values: map[string]string{
"foo": "bar",
"pr_branch": "{{ branch }}",
},
expected: []map[string]any{
{
"number": "1",
"title": "title1",
"branch": "my_branch",
"branch_slug": "my-branch",
"target_branch": "master",
"target_branch_slug": "master",
"head_sha": "abcd",
"head_short_sha": "abcd",
"head_short_sha_7": "abcd",
"author": "testName",
"values.foo": "bar",
"values.pr_branch": "my_branch",
},
},
expectedErr: nil,
},
{
selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
return pullrequest.NewFakeService(
ctx,
nil,
errors.New("fake error"),
) )
}, },
expected: nil, expected: nil,
expectedErr: errors.New("error listing repos: fake error"), expectedErr: fmt.Errorf("error listing repos: fake error"),
}, },
{ {
selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) { selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
@@ -189,7 +149,7 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
nil, nil,
) )
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"number": "1", "number": "1",
"title": "title1", "title": "title1",
@@ -230,7 +190,7 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
nil, nil,
) )
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"number": "1", "number": "1",
"title": "title1", "title": "title1",
@@ -259,14 +219,12 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
selectServiceProviderFunc: c.selectFunc, selectServiceProviderFunc: c.selectFunc,
} }
generatorConfig := argoprojiov1alpha1.ApplicationSetGenerator{ generatorConfig := argoprojiov1alpha1.ApplicationSetGenerator{
PullRequest: &argoprojiov1alpha1.PullRequestGenerator{ PullRequest: &argoprojiov1alpha1.PullRequestGenerator{},
Values: c.values,
},
} }
got, gotErr := gen.GenerateParams(&generatorConfig, &c.applicationSet, nil) got, gotErr := gen.GenerateParams(&generatorConfig, &c.applicationSet, nil)
if c.expectedErr != nil { if c.expectedErr != nil {
require.EqualError(t, gotErr, c.expectedErr.Error()) assert.Equal(t, c.expectedErr.Error(), gotErr.Error())
} else { } else {
require.NoError(t, gotErr) require.NoError(t, gotErr)
} }
@@ -275,8 +233,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
} }
func TestAllowedSCMProviderPullRequest(t *testing.T) { func TestAllowedSCMProviderPullRequest(t *testing.T) {
t.Parallel()
cases := []struct { cases := []struct {
name string name string
providerConfig *argoprojiov1alpha1.PullRequestGenerator providerConfig *argoprojiov1alpha1.PullRequestGenerator
@@ -327,7 +283,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
"gitea.myorg.com", "gitea.myorg.com",
"bitbucket.myorg.com", "bitbucket.myorg.com",
"azuredevops.myorg.com", "azuredevops.myorg.com",
}, true, true, nil, true)) }, true, nil, true))
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@@ -350,7 +306,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
} }
func TestSCMProviderDisabled_PRGenerator(t *testing.T) { func TestSCMProviderDisabled_PRGenerator(t *testing.T) {
generator := NewPullRequestGenerator(nil, NewSCMConfig("", []string{}, false, true, nil, true)) generator := NewPullRequestGenerator(nil, NewSCMConfig("", []string{}, false, nil, true))
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"net/http"
"strings" "strings"
"time" "time"
@@ -12,18 +11,17 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v3/applicationset/services" "github.com/argoproj/argo-cd/v2/applicationset/services/github_app_auth"
"github.com/argoproj/argo-cd/v3/applicationset/services/github_app_auth" "github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider"
"github.com/argoproj/argo-cd/v3/applicationset/services/scm_provider" "github.com/argoproj/argo-cd/v2/applicationset/utils"
"github.com/argoproj/argo-cd/v3/applicationset/utils" "github.com/argoproj/argo-cd/v2/common"
"github.com/argoproj/argo-cd/v3/common" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
) )
var _ Generator = (*SCMProviderGenerator)(nil) var _ Generator = (*SCMProviderGenerator)(nil)
const ( const (
DefaultSCMProviderRequeueAfter = 30 * time.Minute DefaultSCMProviderRequeueAfterSeconds = 30 * time.Minute
) )
type SCMProviderGenerator struct { type SCMProviderGenerator struct {
@@ -33,22 +31,20 @@ type SCMProviderGenerator struct {
SCMConfig SCMConfig
} }
type SCMConfig struct { type SCMConfig struct {
scmRootCAPath string scmRootCAPath string
allowedSCMProviders []string allowedSCMProviders []string
enableSCMProviders bool enableSCMProviders bool
enableGitHubAPIMetrics bool GitHubApps github_app_auth.Credentials
GitHubApps github_app_auth.Credentials tokenRefStrictMode bool
tokenRefStrictMode bool
} }
func NewSCMConfig(scmRootCAPath string, allowedSCMProviders []string, enableSCMProviders bool, enableGitHubAPIMetrics bool, gitHubApps github_app_auth.Credentials, tokenRefStrictMode bool) SCMConfig { func NewSCMConfig(scmRootCAPath string, allowedSCMProviders []string, enableSCMProviders bool, gitHubApps github_app_auth.Credentials, tokenRefStrictMode bool) SCMConfig {
return SCMConfig{ return SCMConfig{
scmRootCAPath: scmRootCAPath, scmRootCAPath: scmRootCAPath,
allowedSCMProviders: allowedSCMProviders, allowedSCMProviders: allowedSCMProviders,
enableSCMProviders: enableSCMProviders, enableSCMProviders: enableSCMProviders,
enableGitHubAPIMetrics: enableGitHubAPIMetrics, GitHubApps: gitHubApps,
GitHubApps: gitHubApps, tokenRefStrictMode: tokenRefStrictMode,
tokenRefStrictMode: tokenRefStrictMode,
} }
} }
@@ -73,7 +69,7 @@ func (g *SCMProviderGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alph
return time.Duration(*appSetGenerator.SCMProvider.RequeueAfterSeconds) * time.Second return time.Duration(*appSetGenerator.SCMProvider.RequeueAfterSeconds) * time.Second
} }
return DefaultSCMProviderRequeueAfter return DefaultSCMProviderRequeueAfterSeconds
} }
func (g *SCMProviderGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate { func (g *SCMProviderGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
@@ -120,13 +116,13 @@ func ScmProviderAllowed(applicationSetInfo *argoprojiov1alpha1.ApplicationSet, g
return NewErrDisallowedSCMProvider(url, allowedScmProviders) return NewErrDisallowedSCMProvider(url, allowedScmProviders)
} }
func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) { func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
if appSetGenerator == nil { if appSetGenerator == nil {
return nil, ErrEmptyAppSetGenerator return nil, EmptyAppSetGeneratorError
} }
if appSetGenerator.SCMProvider == nil { if appSetGenerator.SCMProvider == nil {
return nil, ErrEmptyAppSetGenerator return nil, EmptyAppSetGeneratorError
} }
if !g.enableSCMProviders { if !g.enableSCMProviders {
@@ -142,16 +138,15 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
ctx := context.Background() ctx := context.Background()
var provider scm_provider.SCMProviderService var provider scm_provider.SCMProviderService
switch { if g.overrideProvider != nil {
case g.overrideProvider != nil:
provider = g.overrideProvider provider = g.overrideProvider
case providerConfig.Github != nil: } else if providerConfig.Github != nil {
var err error var err error
provider, err = g.githubProvider(ctx, providerConfig.Github, applicationSetInfo) provider, err = g.githubProvider(ctx, providerConfig.Github, applicationSetInfo)
if err != nil { if err != nil {
return nil, fmt.Errorf("scm provider: %w", err) return nil, fmt.Errorf("scm provider: %w", err)
} }
case providerConfig.Gitlab != nil: } else if providerConfig.Gitlab != nil {
providerConfig := providerConfig.Gitlab providerConfig := providerConfig.Gitlab
var caCerts []byte var caCerts []byte
var scmError error var scmError error
@@ -165,20 +160,20 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Gitlab token: %w", err) return nil, fmt.Errorf("error fetching Gitlab token: %w", err)
} }
provider, err = scm_provider.NewGitlabProvider(providerConfig.Group, token, providerConfig.API, providerConfig.AllBranches, providerConfig.IncludeSubgroups, providerConfig.WillIncludeSharedProjects(), providerConfig.Insecure, g.scmRootCAPath, providerConfig.Topic, caCerts) provider, err = scm_provider.NewGitlabProvider(ctx, providerConfig.Group, token, providerConfig.API, providerConfig.AllBranches, providerConfig.IncludeSubgroups, providerConfig.WillIncludeSharedProjects(), providerConfig.Insecure, g.scmRootCAPath, providerConfig.Topic, caCerts)
if err != nil { if err != nil {
return nil, fmt.Errorf("error initializing Gitlab service: %w", err) return nil, fmt.Errorf("error initializing Gitlab service: %w", err)
} }
case providerConfig.Gitea != nil: } else if providerConfig.Gitea != nil {
token, err := utils.GetSecretRef(ctx, g.client, providerConfig.Gitea.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode) token, err := utils.GetSecretRef(ctx, g.client, providerConfig.Gitea.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Gitea token: %w", err) return nil, fmt.Errorf("error fetching Gitea token: %w", err)
} }
provider, err = scm_provider.NewGiteaProvider(providerConfig.Gitea.Owner, token, providerConfig.Gitea.API, providerConfig.Gitea.AllBranches, providerConfig.Gitea.Insecure) provider, err = scm_provider.NewGiteaProvider(ctx, providerConfig.Gitea.Owner, token, providerConfig.Gitea.API, providerConfig.Gitea.AllBranches, providerConfig.Gitea.Insecure)
if err != nil { if err != nil {
return nil, fmt.Errorf("error initializing Gitea service: %w", err) return nil, fmt.Errorf("error initializing Gitea service: %w", err)
} }
case providerConfig.BitbucketServer != nil: } else if providerConfig.BitbucketServer != nil {
providerConfig := providerConfig.BitbucketServer providerConfig := providerConfig.BitbucketServer
var caCerts []byte var caCerts []byte
var scmError error var scmError error
@@ -188,51 +183,50 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
return nil, fmt.Errorf("error fetching CA certificates from ConfigMap: %w", scmError) return nil, fmt.Errorf("error fetching CA certificates from ConfigMap: %w", scmError)
} }
} }
switch { if providerConfig.BearerToken != nil {
case providerConfig.BearerToken != nil:
appToken, err := utils.GetSecretRef(ctx, g.client, providerConfig.BearerToken.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode) appToken, err := utils.GetSecretRef(ctx, g.client, providerConfig.BearerToken.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Secret Bearer token: %w", err) return nil, fmt.Errorf("error fetching Secret Bearer token: %w", err)
} }
provider, scmError = scm_provider.NewBitbucketServerProviderBearerToken(ctx, appToken, providerConfig.API, providerConfig.Project, providerConfig.AllBranches, g.scmRootCAPath, providerConfig.Insecure, caCerts) provider, scmError = scm_provider.NewBitbucketServerProviderBearerToken(ctx, appToken, providerConfig.API, providerConfig.Project, providerConfig.AllBranches, g.scmRootCAPath, providerConfig.Insecure, caCerts)
case providerConfig.BasicAuth != nil: } else if providerConfig.BasicAuth != nil {
password, err := utils.GetSecretRef(ctx, g.client, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace, g.tokenRefStrictMode) password, err := utils.GetSecretRef(ctx, g.client, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err) return nil, fmt.Errorf("error fetching Secret token: %w", err)
} }
provider, scmError = scm_provider.NewBitbucketServerProviderBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.AllBranches, g.scmRootCAPath, providerConfig.Insecure, caCerts) provider, scmError = scm_provider.NewBitbucketServerProviderBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.AllBranches, g.scmRootCAPath, providerConfig.Insecure, caCerts)
default: } else {
provider, scmError = scm_provider.NewBitbucketServerProviderNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.AllBranches, g.scmRootCAPath, providerConfig.Insecure, caCerts) provider, scmError = scm_provider.NewBitbucketServerProviderNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.AllBranches, g.scmRootCAPath, providerConfig.Insecure, caCerts)
} }
if scmError != nil { if scmError != nil {
return nil, fmt.Errorf("error initializing Bitbucket Server service: %w", scmError) return nil, fmt.Errorf("error initializing Bitbucket Server service: %w", scmError)
} }
case providerConfig.AzureDevOps != nil: } else if providerConfig.AzureDevOps != nil {
token, err := utils.GetSecretRef(ctx, g.client, providerConfig.AzureDevOps.AccessTokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode) token, err := utils.GetSecretRef(ctx, g.client, providerConfig.AzureDevOps.AccessTokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Azure Devops access token: %w", err) return nil, fmt.Errorf("error fetching Azure Devops access token: %w", err)
} }
provider, err = scm_provider.NewAzureDevOpsProvider(token, providerConfig.AzureDevOps.Organization, providerConfig.AzureDevOps.API, providerConfig.AzureDevOps.TeamProject, providerConfig.AzureDevOps.AllBranches) provider, err = scm_provider.NewAzureDevOpsProvider(ctx, token, providerConfig.AzureDevOps.Organization, providerConfig.AzureDevOps.API, providerConfig.AzureDevOps.TeamProject, providerConfig.AzureDevOps.AllBranches)
if err != nil { if err != nil {
return nil, fmt.Errorf("error initializing Azure Devops service: %w", err) return nil, fmt.Errorf("error initializing Azure Devops service: %w", err)
} }
case providerConfig.Bitbucket != nil: } else if providerConfig.Bitbucket != nil {
appPassword, err := utils.GetSecretRef(ctx, g.client, providerConfig.Bitbucket.AppPasswordRef, applicationSetInfo.Namespace, g.tokenRefStrictMode) appPassword, err := utils.GetSecretRef(ctx, g.client, providerConfig.Bitbucket.AppPasswordRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Bitbucket cloud appPassword: %w", err) return nil, fmt.Errorf("error fetching Bitbucket cloud appPassword: %w", err)
} }
provider, err = scm_provider.NewBitBucketCloudProvider(providerConfig.Bitbucket.Owner, providerConfig.Bitbucket.User, appPassword, providerConfig.Bitbucket.AllBranches) provider, err = scm_provider.NewBitBucketCloudProvider(ctx, providerConfig.Bitbucket.Owner, providerConfig.Bitbucket.User, appPassword, providerConfig.Bitbucket.AllBranches)
if err != nil { if err != nil {
return nil, fmt.Errorf("error initializing Bitbucket cloud service: %w", err) return nil, fmt.Errorf("error initializing Bitbucket cloud service: %w", err)
} }
case providerConfig.AWSCodeCommit != nil: } else if providerConfig.AWSCodeCommit != nil {
var awsErr error var awsErr error
provider, awsErr = scm_provider.NewAWSCodeCommitProvider(ctx, providerConfig.AWSCodeCommit.TagFilters, providerConfig.AWSCodeCommit.Role, providerConfig.AWSCodeCommit.Region, providerConfig.AWSCodeCommit.AllBranches) provider, awsErr = scm_provider.NewAWSCodeCommitProvider(ctx, providerConfig.AWSCodeCommit.TagFilters, providerConfig.AWSCodeCommit.Role, providerConfig.AWSCodeCommit.Region, providerConfig.AWSCodeCommit.AllBranches)
if awsErr != nil { if awsErr != nil {
return nil, fmt.Errorf("error initializing AWS codecommit service: %w", awsErr) return nil, fmt.Errorf("error initializing AWS codecommit service: %w", awsErr)
} }
default: } else {
return nil, errors.New("no SCM provider implementation configured") return nil, fmt.Errorf("no SCM provider implementation configured")
} }
// Find all the available repos. // Find all the available repos.
@@ -240,7 +234,7 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
if err != nil { if err != nil {
return nil, fmt.Errorf("error listing repos: %w", err) return nil, fmt.Errorf("error listing repos: %w", err)
} }
paramsArray := make([]map[string]any, 0, len(repos)) paramsArray := make([]map[string]interface{}, 0, len(repos))
var shortSHALength int var shortSHALength int
var shortSHALength7 int var shortSHALength7 int
for _, repo := range repos { for _, repo := range repos {
@@ -254,10 +248,9 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
shortSHALength7 = len(repo.SHA) shortSHALength7 = len(repo.SHA)
} }
params := map[string]any{ params := map[string]interface{}{
"organization": repo.Organization, "organization": repo.Organization,
"repository": repo.Repository, "repository": repo.Repository,
"repository_id": repo.RepositoryId,
"url": repo.URL, "url": repo.URL,
"branch": repo.Branch, "branch": repo.Branch,
"sha": repo.SHA, "sha": repo.SHA,
@@ -278,36 +271,23 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
} }
func (g *SCMProviderGenerator) githubProvider(ctx context.Context, github *argoprojiov1alpha1.SCMProviderGeneratorGithub, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (scm_provider.SCMProviderService, error) { func (g *SCMProviderGenerator) githubProvider(ctx context.Context, github *argoprojiov1alpha1.SCMProviderGeneratorGithub, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (scm_provider.SCMProviderService, error) {
var metricsCtx *services.MetricsContext
var httpClient *http.Client
if g.enableGitHubAPIMetrics {
metricsCtx = &services.MetricsContext{
AppSetNamespace: applicationSetInfo.Namespace,
AppSetName: applicationSetInfo.Name,
}
httpClient = services.NewGitHubMetricsClient(metricsCtx)
}
if github.AppSecretName != "" { if github.AppSecretName != "" {
auth, err := g.GitHubApps.GetAuthSecret(ctx, github.AppSecretName) auth, err := g.GitHubApps.GetAuthSecret(ctx, github.AppSecretName)
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Github app secret: %w", err) return nil, fmt.Errorf("error fetching Github app secret: %w", err)
} }
if g.enableGitHubAPIMetrics { return scm_provider.NewGithubAppProviderFor(
return scm_provider.NewGithubAppProviderFor(*auth, github.Organization, github.API, github.AllBranches, httpClient) *auth,
} github.Organization,
return scm_provider.NewGithubAppProviderFor(*auth, github.Organization, github.API, github.AllBranches) github.API,
github.AllBranches,
)
} }
token, err := utils.GetSecretRef(ctx, g.client, github.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode) token, err := utils.GetSecretRef(ctx, g.client, github.TokenRef, applicationSetInfo.Namespace, g.tokenRefStrictMode)
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Github token: %w", err) return nil, fmt.Errorf("error fetching Github token: %w", err)
} }
return scm_provider.NewGithubProvider(ctx, github.Organization, token, github.API, github.AllBranches)
if g.enableGitHubAPIMetrics {
return scm_provider.NewGithubProvider(github.Organization, token, github.API, github.AllBranches, httpClient)
}
return scm_provider.NewGithubProvider(github.Organization, token, github.API, github.AllBranches)
} }

View File

@@ -7,18 +7,16 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/argoproj/argo-cd/v3/applicationset/services/scm_provider" "github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
func TestSCMProviderGenerateParams(t *testing.T) { func TestSCMProviderGenerateParams(t *testing.T) {
t.Parallel()
cases := []struct { cases := []struct {
name string name string
repos []*scm_provider.Repository repos []*scm_provider.Repository
values map[string]string values map[string]string
expected []map[string]any expected []map[string]interface{}
expectedError error expectedError error
}{ }{
{ {
@@ -27,7 +25,6 @@ func TestSCMProviderGenerateParams(t *testing.T) {
{ {
Organization: "myorg", Organization: "myorg",
Repository: "repo1", Repository: "repo1",
RepositoryId: 190320251,
URL: "git@github.com:myorg/repo1.git", URL: "git@github.com:myorg/repo1.git",
Branch: "main", Branch: "main",
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b", SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
@@ -36,17 +33,15 @@ func TestSCMProviderGenerateParams(t *testing.T) {
{ {
Organization: "myorg", Organization: "myorg",
Repository: "repo2", Repository: "repo2",
RepositoryId: 190320252,
URL: "git@github.com:myorg/repo2.git", URL: "git@github.com:myorg/repo2.git",
Branch: "main", Branch: "main",
SHA: "59d0", SHA: "59d0",
}, },
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"organization": "myorg", "organization": "myorg",
"repository": "repo1", "repository": "repo1",
"repository_id": 190320251,
"url": "git@github.com:myorg/repo1.git", "url": "git@github.com:myorg/repo1.git",
"branch": "main", "branch": "main",
"branchNormalized": "main", "branchNormalized": "main",
@@ -58,7 +53,6 @@ func TestSCMProviderGenerateParams(t *testing.T) {
{ {
"organization": "myorg", "organization": "myorg",
"repository": "repo2", "repository": "repo2",
"repository_id": 190320252,
"url": "git@github.com:myorg/repo2.git", "url": "git@github.com:myorg/repo2.git",
"branch": "main", "branch": "main",
"branchNormalized": "main", "branchNormalized": "main",
@@ -75,7 +69,6 @@ func TestSCMProviderGenerateParams(t *testing.T) {
{ {
Organization: "myorg", Organization: "myorg",
Repository: "repo3", Repository: "repo3",
RepositoryId: 190320253,
URL: "git@github.com:myorg/repo3.git", URL: "git@github.com:myorg/repo3.git",
Branch: "main", Branch: "main",
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b", SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
@@ -86,11 +79,10 @@ func TestSCMProviderGenerateParams(t *testing.T) {
"foo": "bar", "foo": "bar",
"should_i_force_push_to": "{{ branch }}?", "should_i_force_push_to": "{{ branch }}?",
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"organization": "myorg", "organization": "myorg",
"repository": "repo3", "repository": "repo3",
"repository_id": 190320253,
"url": "git@github.com:myorg/repo3.git", "url": "git@github.com:myorg/repo3.git",
"branch": "main", "branch": "main",
"branchNormalized": "main", "branchNormalized": "main",
@@ -103,52 +95,6 @@ func TestSCMProviderGenerateParams(t *testing.T) {
}, },
}, },
}, },
{
name: "Repos with and without id",
repos: []*scm_provider.Repository{
{
Organization: "myorg",
Repository: "repo4",
RepositoryId: "idaz09",
URL: "git@github.com:myorg/repo4.git",
Branch: "main",
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
},
{
Organization: "myorg",
Repository: "repo5",
URL: "git@github.com:myorg/repo5.git",
Branch: "main",
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
},
},
expected: []map[string]any{
{
"organization": "myorg",
"repository": "repo4",
"repository_id": "idaz09",
"url": "git@github.com:myorg/repo4.git",
"branch": "main",
"branchNormalized": "main",
"sha": "0bc57212c3cbbec69d20b34c507284bd300def5b",
"short_sha": "0bc57212",
"short_sha_7": "0bc5721",
"labels": "",
},
{
"organization": "myorg",
"repository": "repo5",
"repository_id": nil,
"url": "git@github.com:myorg/repo5.git",
"branch": "main",
"branchNormalized": "main",
"sha": "0bc57212c3cbbec69d20b34c507284bd300def5b",
"short_sha": "0bc57212",
"short_sha_7": "0bc5721",
"labels": "",
},
},
},
} }
for _, testCase := range cases { for _, testCase := range cases {
@@ -187,8 +133,6 @@ func TestSCMProviderGenerateParams(t *testing.T) {
} }
func TestAllowedSCMProvider(t *testing.T) { func TestAllowedSCMProvider(t *testing.T) {
t.Parallel()
cases := []struct { cases := []struct {
name string name string
providerConfig *argoprojiov1alpha1.SCMProviderGenerator providerConfig *argoprojiov1alpha1.SCMProviderGenerator

View File

@@ -1,5 +1,5 @@
package generators package generators
type SCMGeneratorWithCustomApiUrl interface { //nolint:revive //FIXME(var-naming) type SCMGeneratorWithCustomApiUrl interface {
CustomApiUrl() string CustomApiUrl() string
} }

View File

@@ -7,18 +7,18 @@ import (
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v3/applicationset/services" "github.com/argoproj/argo-cd/v2/applicationset/services"
) )
func GetGenerators(ctx context.Context, c client.Client, k8sClient kubernetes.Interface, namespace string, argoCDService services.Repos, dynamicClient dynamic.Interface, scmConfig SCMConfig) map[string]Generator { func GetGenerators(ctx context.Context, c client.Client, k8sClient kubernetes.Interface, namespace string, argoCDService services.Repos, dynamicClient dynamic.Interface, scmConfig SCMConfig) map[string]Generator {
terminalGenerators := map[string]Generator{ terminalGenerators := map[string]Generator{
"List": NewListGenerator(), "List": NewListGenerator(),
"Clusters": NewClusterGenerator(ctx, c, k8sClient, namespace), "Clusters": NewClusterGenerator(c, ctx, k8sClient, namespace),
"Git": NewGitGenerator(argoCDService, namespace), "Git": NewGitGenerator(argoCDService, namespace),
"SCMProvider": NewSCMProviderGenerator(c, scmConfig), "SCMProvider": NewSCMProviderGenerator(c, scmConfig),
"ClusterDecisionResource": NewDuckTypeGenerator(ctx, dynamicClient, k8sClient, namespace), "ClusterDecisionResource": NewDuckTypeGenerator(ctx, dynamicClient, k8sClient, namespace),
"PullRequest": NewPullRequestGenerator(c, scmConfig), "PullRequest": NewPullRequestGenerator(c, scmConfig),
"Plugin": NewPluginGenerator(c, namespace), "Plugin": NewPluginGenerator(c, ctx, k8sClient, namespace),
} }
nestedGenerators := map[string]Generator{ nestedGenerators := map[string]Generator{

View File

@@ -4,11 +4,11 @@ import (
"fmt" "fmt"
) )
func appendTemplatedValues(values map[string]string, params map[string]any, useGoTemplate bool, goTemplateOptions []string) error { func appendTemplatedValues(values map[string]string, params map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) error {
// We create a local map to ensure that we do not fall victim to a billion-laughs attack. We iterate through the // We create a local map to ensure that we do not fall victim to a billion-laughs attack. We iterate through the
// cluster values map and only replace values in said map if it has already been allowlisted in the params map. // cluster values map and only replace values in said map if it has already been allowlisted in the params map.
// Once we iterate through all the cluster values we can then safely merge the `tmp` map into the main params map. // Once we iterate through all the cluster values we can then safely merge the `tmp` map into the main params map.
tmp := map[string]any{} tmp := map[string]interface{}{}
for key, value := range values { for key, value := range values {
result, err := replaceTemplatedString(value, params, useGoTemplate, goTemplateOptions) result, err := replaceTemplatedString(value, params, useGoTemplate, goTemplateOptions)
@@ -22,7 +22,7 @@ func appendTemplatedValues(values map[string]string, params map[string]any, useG
} }
tmp["values"].(map[string]string)[key] = result tmp["values"].(map[string]string)[key] = result
} else { } else {
tmp["values."+key] = result tmp[fmt.Sprintf("values.%s", key)] = result
} }
} }
@@ -33,7 +33,7 @@ func appendTemplatedValues(values map[string]string, params map[string]any, useG
return nil return nil
} }
func replaceTemplatedString(value string, params map[string]any, useGoTemplate bool, goTemplateOptions []string) (string, error) { func replaceTemplatedString(value string, params map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (string, error) {
replacedTmplStr, err := render.Replace(value, params, useGoTemplate, goTemplateOptions) replacedTmplStr, err := render.Replace(value, params, useGoTemplate, goTemplateOptions)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to replace templated string with rendered values: %w", err) return "", fmt.Errorf("failed to replace templated string with rendered values: %w", err)

View File

@@ -11,18 +11,18 @@ func TestValueInterpolation(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
values map[string]string values map[string]string
params map[string]any params map[string]interface{}
expected map[string]any expected map[string]interface{}
}{ }{
{ {
name: "Simple interpolation", name: "Simple interpolation",
values: map[string]string{ values: map[string]string{
"hello": "{{ world }}", "hello": "{{ world }}",
}, },
params: map[string]any{ params: map[string]interface{}{
"world": "world!", "world": "world!",
}, },
expected: map[string]any{ expected: map[string]interface{}{
"world": "world!", "world": "world!",
"values.hello": "world!", "values.hello": "world!",
}, },
@@ -32,8 +32,8 @@ func TestValueInterpolation(t *testing.T) {
values: map[string]string{ values: map[string]string{
"non-existent": "{{ non-existent }}", "non-existent": "{{ non-existent }}",
}, },
params: map[string]any{}, params: map[string]interface{}{},
expected: map[string]any{ expected: map[string]interface{}{
"values.non-existent": "{{ non-existent }}", "values.non-existent": "{{ non-existent }}",
}, },
}, },
@@ -44,8 +44,8 @@ func TestValueInterpolation(t *testing.T) {
"lol2": "{{values.lol1}}{{values.lol1}}", "lol2": "{{values.lol1}}{{values.lol1}}",
"lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}",
}, },
params: map[string]any{}, params: map[string]interface{}{},
expected: map[string]any{ expected: map[string]interface{}{
"values.lol1": "lol", "values.lol1": "lol",
"values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol2": "{{values.lol1}}{{values.lol1}}",
"values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}",
@@ -57,7 +57,7 @@ func TestValueInterpolation(t *testing.T) {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
err := appendTemplatedValues(testCase.values, testCase.params, false, nil) err := appendTemplatedValues(testCase.values, testCase.params, false, nil)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, testCase.expected, testCase.params) assert.EqualValues(t, testCase.expected, testCase.params)
}) })
} }
} }
@@ -66,18 +66,18 @@ func TestValueInterpolationWithGoTemplating(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
values map[string]string values map[string]string
params map[string]any params map[string]interface{}
expected map[string]any expected map[string]interface{}
}{ }{
{ {
name: "Simple interpolation", name: "Simple interpolation",
values: map[string]string{ values: map[string]string{
"hello": "{{ .world }}", "hello": "{{ .world }}",
}, },
params: map[string]any{ params: map[string]interface{}{
"world": "world!", "world": "world!",
}, },
expected: map[string]any{ expected: map[string]interface{}{
"world": "world!", "world": "world!",
"values": map[string]string{ "values": map[string]string{
"hello": "world!", "hello": "world!",
@@ -89,8 +89,8 @@ func TestValueInterpolationWithGoTemplating(t *testing.T) {
values: map[string]string{ values: map[string]string{
"non_existent": "{{ default \"bar\" .non_existent }}", "non_existent": "{{ default \"bar\" .non_existent }}",
}, },
params: map[string]any{}, params: map[string]interface{}{},
expected: map[string]any{ expected: map[string]interface{}{
"values": map[string]string{ "values": map[string]string{
"non_existent": "bar", "non_existent": "bar",
}, },
@@ -103,8 +103,8 @@ func TestValueInterpolationWithGoTemplating(t *testing.T) {
"lol2": "{{.values.lol1}}{{.values.lol1}}", "lol2": "{{.values.lol1}}{{.values.lol1}}",
"lol3": "{{.values.lol2}}{{.values.lol2}}{{.values.lol2}}", "lol3": "{{.values.lol2}}{{.values.lol2}}{{.values.lol2}}",
}, },
params: map[string]any{}, params: map[string]interface{}{},
expected: map[string]any{ expected: map[string]interface{}{
"values": map[string]string{ "values": map[string]string{
"lol1": "lol", "lol1": "lol",
"lol2": "<no value><no value>", "lol2": "<no value><no value>",
@@ -118,7 +118,7 @@ func TestValueInterpolationWithGoTemplating(t *testing.T) {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
err := appendTemplatedValues(testCase.values, testCase.params, true, nil) err := appendTemplatedValues(testCase.values, testCase.params, true, nil)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, testCase.expected, testCase.params) assert.EqualValues(t, testCase.expected, testCase.params)
}) })
} }
} }

View File

@@ -2,10 +2,11 @@ package metrics
import ( import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
) )
// Fake implementation for testing // Fake implementation for testing
func NewFakeAppsetMetrics() *ApplicationsetMetrics { func NewFakeAppsetMetrics(client ctrlclient.WithWatch) *ApplicationsetMetrics {
reconcileHistogram := prometheus.NewHistogramVec( reconcileHistogram := prometheus.NewHistogramVec(
prometheus.HistogramOpts{ prometheus.HistogramOpts{
Name: "argocd_appset_reconcile", Name: "argocd_appset_reconcile",

View File

@@ -7,10 +7,9 @@ import (
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/controller-runtime/pkg/metrics" "sigs.k8s.io/controller-runtime/pkg/metrics"
argoappv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
applisters "github.com/argoproj/argo-cd/v3/pkg/client/listers/application/v1alpha1" applisters "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1"
metricsutil "github.com/argoproj/argo-cd/v3/util/metrics" metricsutil "github.com/argoproj/argo-cd/v2/util/metrics"
"github.com/argoproj/argo-cd/v3/util/metrics/kubectl"
) )
var ( var (
@@ -58,9 +57,6 @@ func NewApplicationsetMetrics(appsetLister applisters.ApplicationSetLister, apps
metrics.Registry.MustRegister(reconcileHistogram) metrics.Registry.MustRegister(reconcileHistogram)
metrics.Registry.MustRegister(appsetCollector) metrics.Registry.MustRegister(appsetCollector)
kubectl.RegisterWithClientGo()
kubectl.RegisterWithPrometheus(metrics.Registry)
return ApplicationsetMetrics{ return ApplicationsetMetrics{
reconcileHistogram: reconcileHistogram, reconcileHistogram: reconcileHistogram,
} }

View File

@@ -7,19 +7,23 @@ import (
"testing" "testing"
"time" "time"
"github.com/prometheus/client_golang/prometheus" "github.com/argoproj/argo-cd/v2/applicationset/utils"
argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake" fake "sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/metrics"
"sigs.k8s.io/yaml"
"github.com/argoproj/argo-cd/v3/applicationset/utils" prometheus "github.com/prometheus/client_golang/prometheus"
argoappv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
metricsutil "github.com/argoproj/argo-cd/v3/util/metrics" metricsutil "github.com/argoproj/argo-cd/v2/util/metrics"
"sigs.k8s.io/controller-runtime/pkg/metrics"
"sigs.k8s.io/yaml"
) )
var ( var (
@@ -174,7 +178,7 @@ func TestApplicationsetCollector(t *testing.T) {
appsetCollector := newAppsetCollector(utils.NewAppsetLister(client), collectedLabels, filter) appsetCollector := newAppsetCollector(utils.NewAppsetLister(client), collectedLabels, filter)
metrics.Registry.MustRegister(appsetCollector) metrics.Registry.MustRegister(appsetCollector)
req, err := http.NewRequest(http.MethodGet, "/metrics", http.NoBody) req, err := http.NewRequest(http.MethodGet, "/metrics", nil)
require.NoError(t, err) require.NoError(t, err)
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
handler := promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{}) handler := promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{})
@@ -216,7 +220,7 @@ func TestObserveReconcile(t *testing.T) {
appsetMetrics := NewApplicationsetMetrics(utils.NewAppsetLister(client), collectedLabels, filter) appsetMetrics := NewApplicationsetMetrics(utils.NewAppsetLister(client), collectedLabels, filter)
req, err := http.NewRequest(http.MethodGet, "/metrics", http.NoBody) req, err := http.NewRequest(http.MethodGet, "/metrics", nil)
require.NoError(t, err) require.NoError(t, err)
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
handler := promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{}) handler := promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{})

View File

@@ -1,241 +0,0 @@
package services
import (
"net/http"
"strconv"
"time"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"sigs.k8s.io/controller-runtime/pkg/metrics"
)
// Doc for the GitHub API rate limit headers:
// https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#checking-the-status-of-your-rate-limit
// Metric names as constants
const (
githubAPIRequestTotalMetricName = "argocd_github_api_requests_total"
githubAPIRequestDurationMetricName = "argocd_github_api_request_duration_seconds"
githubAPIRateLimitRemainingMetricName = "argocd_github_api_rate_limit_remaining"
githubAPIRateLimitLimitMetricName = "argocd_github_api_rate_limit_limit"
githubAPIRateLimitResetMetricName = "argocd_github_api_rate_limit_reset_seconds"
githubAPIRateLimitUsedMetricName = "argocd_github_api_rate_limit_used"
)
// GitHubMetrics groups all metric vectors for easier injection and registration
type GitHubMetrics struct {
RequestTotal *prometheus.CounterVec
RequestDuration *prometheus.HistogramVec
RateLimitRemaining *prometheus.GaugeVec
RateLimitLimit *prometheus.GaugeVec
RateLimitReset *prometheus.GaugeVec
RateLimitUsed *prometheus.GaugeVec
}
// Factory for a new set of GitHub metrics (for tests or custom registries)
func NewGitHubMetrics() *GitHubMetrics {
return &GitHubMetrics{
RequestTotal: NewGitHubAPIRequestTotal(),
RequestDuration: NewGitHubAPIRequestDuration(),
RateLimitRemaining: NewGitHubAPIRateLimitRemaining(),
RateLimitLimit: NewGitHubAPIRateLimitLimit(),
RateLimitReset: NewGitHubAPIRateLimitReset(),
RateLimitUsed: NewGitHubAPIRateLimitUsed(),
}
}
// Factory functions for each metric vector
func NewGitHubAPIRequestTotal() *prometheus.CounterVec {
return prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: githubAPIRequestTotalMetricName,
Help: "Total number of GitHub API requests",
},
[]string{"method", "endpoint", "status", "appset_namespace", "appset_name"},
)
}
func NewGitHubAPIRequestDuration() *prometheus.HistogramVec {
return prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: githubAPIRequestDurationMetricName,
Help: "GitHub API request duration in seconds",
Buckets: prometheus.DefBuckets,
},
[]string{"method", "endpoint", "appset_namespace", "appset_name"},
)
}
func NewGitHubAPIRateLimitRemaining() *prometheus.GaugeVec {
return prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: githubAPIRateLimitRemainingMetricName,
Help: "The number of requests remaining in the current rate limit window",
},
[]string{"endpoint", "appset_namespace", "appset_name", "resource"},
)
}
func NewGitHubAPIRateLimitLimit() *prometheus.GaugeVec {
return prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: githubAPIRateLimitLimitMetricName,
Help: "The maximum number of requests that you can make per hour",
},
[]string{"endpoint", "appset_namespace", "appset_name", "resource"},
)
}
func NewGitHubAPIRateLimitReset() *prometheus.GaugeVec {
return prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: githubAPIRateLimitResetMetricName,
Help: "The time left till the current rate limit window resets, in seconds",
},
[]string{"endpoint", "appset_namespace", "appset_name", "resource"},
)
}
func NewGitHubAPIRateLimitUsed() *prometheus.GaugeVec {
return prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: githubAPIRateLimitUsedMetricName,
Help: "The number of requests used in the current rate limit window",
},
[]string{"endpoint", "appset_namespace", "appset_name", "resource"},
)
}
// Global metrics (registered with the default registry)
var globalGitHubMetrics = NewGitHubMetrics()
func init() {
log.Debug("Registering GitHub API AppSet metrics")
metrics.Registry.MustRegister(globalGitHubMetrics.RequestTotal)
metrics.Registry.MustRegister(globalGitHubMetrics.RequestDuration)
metrics.Registry.MustRegister(globalGitHubMetrics.RateLimitRemaining)
metrics.Registry.MustRegister(globalGitHubMetrics.RateLimitLimit)
metrics.Registry.MustRegister(globalGitHubMetrics.RateLimitReset)
metrics.Registry.MustRegister(globalGitHubMetrics.RateLimitUsed)
}
type MetricsContext struct {
AppSetNamespace string
AppSetName string
}
// GitHubMetricsTransport is a custom http.RoundTripper that collects GitHub API metrics
type GitHubMetricsTransport struct {
transport http.RoundTripper
metricsContext *MetricsContext
metrics *GitHubMetrics
}
// RoundTrip implements http.RoundTripper interface and collects metrics along with debug logging
func (t *GitHubMetricsTransport) RoundTrip(req *http.Request) (*http.Response, error) {
endpoint := req.URL.Path
method := req.Method
appsetNamespace := "unknown"
appsetName := "unknown"
if t.metricsContext != nil {
appsetNamespace = t.metricsContext.AppSetNamespace
appsetName = t.metricsContext.AppSetName
}
log.WithFields(log.Fields{
"method": method,
"endpoint": endpoint,
"applicationset": map[string]string{"name": appsetName, "namespace": appsetNamespace},
}).Debugf("Invoking GitHub API")
startTime := time.Now()
resp, err := t.transport.RoundTrip(req)
duration := time.Since(startTime)
// Record metrics
t.metrics.RequestDuration.WithLabelValues(method, endpoint, appsetNamespace, appsetName).Observe(duration.Seconds())
status := "0"
if resp != nil {
status = strconv.Itoa(resp.StatusCode)
}
t.metrics.RequestTotal.WithLabelValues(method, endpoint, status, appsetNamespace, appsetName).Inc()
if resp != nil {
resetHumanReadableTime := ""
remainingInt := 0
limitInt := 0
usedInt := 0
resource := resp.Header.Get("X-RateLimit-Resource")
// Record rate limit metrics if available
if resetTime := resp.Header.Get("X-RateLimit-Reset"); resetTime != "" {
if resetUnix, err := strconv.ParseInt(resetTime, 10, 64); err == nil {
// Calculate seconds until reset (reset timestamp - current time)
secondsUntilReset := resetUnix - time.Now().Unix()
t.metrics.RateLimitReset.WithLabelValues(endpoint, appsetNamespace, appsetName, resource).Set(float64(secondsUntilReset))
resetHumanReadableTime = time.Unix(resetUnix, 0).Local().Format("2006-01-02 15:04:05 MST")
}
}
if remaining := resp.Header.Get("X-RateLimit-Remaining"); remaining != "" {
if remainingInt, err = strconv.Atoi(remaining); err == nil {
t.metrics.RateLimitRemaining.WithLabelValues(endpoint, appsetNamespace, appsetName, resource).Set(float64(remainingInt))
}
}
if limit := resp.Header.Get("X-RateLimit-Limit"); limit != "" {
if limitInt, err = strconv.Atoi(limit); err == nil {
t.metrics.RateLimitLimit.WithLabelValues(endpoint, appsetNamespace, appsetName, resource).Set(float64(limitInt))
}
}
if used := resp.Header.Get("X-RateLimit-Used"); used != "" {
if usedInt, err = strconv.Atoi(used); err == nil {
t.metrics.RateLimitUsed.WithLabelValues(endpoint, appsetNamespace, appsetName, resource).Set(float64(usedInt))
}
}
log.WithFields(log.Fields{
"endpoint": endpoint,
"reset": resetHumanReadableTime,
"remaining": remainingInt,
"limit": limitInt,
"used": usedInt,
"resource": resource,
"applicationset": map[string]string{"name": appsetName, "namespace": appsetNamespace},
}).Debugf("GitHub API rate limit info")
}
return resp, err
}
// Full constructor (for tests and advanced use)
func NewGitHubMetricsTransport(
transport http.RoundTripper,
metricsContext *MetricsContext,
metrics *GitHubMetrics,
) *GitHubMetricsTransport {
return &GitHubMetricsTransport{
transport: transport,
metricsContext: metricsContext,
metrics: metrics,
}
}
// Default constructor
func NewDefaultGitHubMetricsTransport(transport http.RoundTripper, metricsContext *MetricsContext) *GitHubMetricsTransport {
return NewGitHubMetricsTransport(
transport,
metricsContext,
globalGitHubMetrics,
)
}
// NewGitHubMetricsClient wraps an http.Client with metrics middleware
func NewGitHubMetricsClient(metricsContext *MetricsContext) *http.Client {
log.Debug("Creating new GitHub metrics client")
return &http.Client{
Transport: NewDefaultGitHubMetricsTransport(http.DefaultTransport, metricsContext),
}
}

View File

@@ -1,221 +0,0 @@
package services
import (
"io"
"net/http"
"net/http/httptest"
"sort"
"strconv"
"strings"
"testing"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/stretchr/testify/assert"
)
type Metric struct {
name string
labels []string
value string
}
var (
endpointLabel = "endpoint=\"/api/test\""
URL = "/api/test"
appsetNamespaceLabel = "appset_namespace=\"test-ns\""
appsetNamespace = "test-ns"
appsetName = "test-appset"
appsetNameLabel = "appset_name=\"test-appset\""
resourceLabel = "resource=\"core\""
rateLimitMetrics = []Metric{
{
name: githubAPIRateLimitRemainingMetricName,
labels: []string{endpointLabel, appsetNamespaceLabel, appsetNameLabel, resourceLabel},
value: "42",
},
{
name: githubAPIRateLimitLimitMetricName,
labels: []string{endpointLabel, appsetNamespaceLabel, appsetNameLabel, resourceLabel},
value: "100",
},
{
name: githubAPIRateLimitUsedMetricName,
labels: []string{endpointLabel, appsetNamespaceLabel, appsetNameLabel, resourceLabel},
value: "58",
},
{
name: githubAPIRateLimitResetMetricName,
labels: []string{endpointLabel, appsetNamespaceLabel, appsetNameLabel, resourceLabel},
value: "1",
},
}
successRequestMetrics = Metric{
name: githubAPIRequestTotalMetricName,
labels: []string{"method=\"GET\"", endpointLabel, "status=\"201\"", appsetNamespaceLabel, appsetNameLabel},
value: "1",
}
failureRequestMetrics = Metric{
name: githubAPIRequestTotalMetricName,
labels: []string{"method=\"GET\"", endpointLabel, "status=\"0\"", appsetNamespaceLabel, appsetNameLabel},
value: "1",
}
)
func TestGitHubMetrics_CollectorApproach_Success(t *testing.T) {
metrics := NewGitHubMetrics()
reg := prometheus.NewRegistry()
reg.MustRegister(
metrics.RequestTotal,
metrics.RequestDuration,
metrics.RateLimitRemaining,
metrics.RateLimitLimit,
metrics.RateLimitReset,
metrics.RateLimitUsed,
)
// Setup a fake HTTP server
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("X-RateLimit-Reset", strconv.FormatInt(time.Now().Unix()+1, 10))
w.Header().Set("X-RateLimit-Remaining", "42")
w.Header().Set("X-RateLimit-Limit", "100")
w.Header().Set("X-RateLimit-Used", "58")
w.Header().Set("X-RateLimit-Resource", "core")
w.WriteHeader(http.StatusCreated)
_, _ = w.Write([]byte("ok"))
}))
defer ts.Close()
metricsCtx := &MetricsContext{AppSetNamespace: appsetNamespace, AppSetName: appsetName}
client := &http.Client{
Transport: NewGitHubMetricsTransport(
http.DefaultTransport,
metricsCtx,
metrics,
),
}
req, _ := http.NewRequest(http.MethodGet, ts.URL+URL, http.NoBody)
resp, err := client.Do(req)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
resp.Body.Close()
// Expose and scrape metrics
handler := promhttp.HandlerFor(reg, promhttp.HandlerOpts{})
server := httptest.NewServer(handler)
defer server.Close()
resp, err = http.Get(server.URL)
if err != nil {
t.Fatalf("failed to scrape metrics: %v", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
metricsOutput := string(body)
sort.Strings(successRequestMetrics.labels)
assert.Contains(t, metricsOutput, successRequestMetrics.name+"{"+strings.Join(successRequestMetrics.labels, ",")+"} "+successRequestMetrics.value)
for _, metric := range rateLimitMetrics {
sort.Strings(metric.labels)
assert.Contains(t, metricsOutput, metric.name+"{"+strings.Join(metric.labels, ",")+"} "+metric.value)
}
}
type RoundTripperFunc func(*http.Request) (*http.Response, error)
func (f RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
func TestGitHubMetrics_CollectorApproach_NoRateLimitMetricsOnNilResponse(t *testing.T) {
metrics := NewGitHubMetrics()
reg := prometheus.NewRegistry()
reg.MustRegister(
metrics.RequestTotal,
metrics.RequestDuration,
metrics.RateLimitRemaining,
metrics.RateLimitLimit,
metrics.RateLimitReset,
metrics.RateLimitUsed,
)
client := &http.Client{
Transport: &GitHubMetricsTransport{
transport: RoundTripperFunc(func(*http.Request) (*http.Response, error) {
return nil, http.ErrServerClosed
}),
metricsContext: &MetricsContext{AppSetNamespace: appsetNamespace, AppSetName: appsetName},
metrics: metrics,
},
}
req, _ := http.NewRequest(http.MethodGet, URL, http.NoBody)
_, _ = client.Do(req)
handler := promhttp.HandlerFor(reg, promhttp.HandlerOpts{})
server := httptest.NewServer(handler)
defer server.Close()
resp, err := http.Get(server.URL)
if err != nil {
t.Fatalf("failed to scrape metrics: %v", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
metricsOutput := string(body)
// Verify request metric exists with status "0"
sort.Strings(failureRequestMetrics.labels)
assert.Contains(t, metricsOutput, failureRequestMetrics.name+"{"+strings.Join(failureRequestMetrics.labels, ",")+"} "+failureRequestMetrics.value)
// Verify rate limit metrics don't exist
for _, metric := range rateLimitMetrics {
sort.Strings(metric.labels)
assert.NotContains(t, metricsOutput, metric.name+"{"+strings.Join(metric.labels, ",")+"} "+metric.value)
}
}
func TestNewGitHubMetricsClient(t *testing.T) {
// Test cases
testCases := []struct {
name string
metricsCtx *MetricsContext
}{
{
name: "with metrics context",
metricsCtx: &MetricsContext{
AppSetNamespace: appsetNamespace,
AppSetName: appsetName,
},
},
{
name: "with nil metrics context",
metricsCtx: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Create client
client := NewGitHubMetricsClient(tc.metricsCtx)
// Assert client is not nil
assert.NotNil(t, client)
// Assert transport is properly configured
transport, ok := client.Transport.(*GitHubMetricsTransport)
assert.True(t, ok, "Transport should be GitHubMetricsTransport")
// Verify transport configuration
assert.Equal(t, tc.metricsCtx, transport.metricsContext)
assert.NotNil(t, transport.metrics, "Metrics should not be nil")
assert.Equal(t, http.DefaultTransport, transport.transport, "Base transport should be http.DefaultTransport")
// Verify metrics are global metrics
assert.Equal(t, globalGitHubMetrics, transport.metrics, "Should use global metrics")
})
}
}

View File

@@ -5,27 +5,14 @@ import (
"net/http" "net/http"
"github.com/bradleyfalzon/ghinstallation/v2" "github.com/bradleyfalzon/ghinstallation/v2"
"github.com/google/go-github/v69/github" "github.com/google/go-github/v66/github"
"github.com/argoproj/argo-cd/v3/applicationset/services/github_app_auth" "github.com/argoproj/argo-cd/v2/applicationset/services/github_app_auth"
appsetutils "github.com/argoproj/argo-cd/v3/applicationset/utils"
) )
func getOptionalHTTPClientAndTransport(optionalHTTPClient ...*http.Client) (*http.Client, http.RoundTripper) {
httpClient := appsetutils.GetOptionalHTTPClient(optionalHTTPClient...)
if len(optionalHTTPClient) > 0 && optionalHTTPClient[0] != nil && optionalHTTPClient[0].Transport != nil {
// will either use the provided custom httpClient and it's transport
return httpClient, optionalHTTPClient[0].Transport
}
// or the default httpClient and transport
return httpClient, http.DefaultTransport
}
// Client builds a github client for the given app authentication. // Client builds a github client for the given app authentication.
func Client(g github_app_auth.Authentication, url string, optionalHTTPClient ...*http.Client) (*github.Client, error) { func Client(g github_app_auth.Authentication, url string) (*github.Client, error) {
httpClient, transport := getOptionalHTTPClientAndTransport(optionalHTTPClient...) rt, err := ghinstallation.New(http.DefaultTransport, g.Id, g.InstallationId, []byte(g.PrivateKey))
rt, err := ghinstallation.New(transport, g.Id, g.InstallationId, []byte(g.PrivateKey))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create github app install: %w", err) return nil, fmt.Errorf("failed to create github app install: %w", err)
} }
@@ -33,12 +20,13 @@ func Client(g github_app_auth.Authentication, url string, optionalHTTPClient ...
url = g.EnterpriseBaseURL url = g.EnterpriseBaseURL
} }
var client *github.Client var client *github.Client
httpClient.Transport = rt
if url == "" { if url == "" {
client = github.NewClient(httpClient) httpClient := http.Client{Transport: rt}
client = github.NewClient(&httpClient)
} else { } else {
rt.BaseURL = url rt.BaseURL = url
client, err = github.NewClient(httpClient).WithEnterpriseURLs(url, url) httpClient := http.Client{Transport: rt}
client, err = github.NewClient(&httpClient).WithEnterpriseURLs(url, url)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create github enterprise client: %w", err) return nil, fmt.Errorf("failed to create github enterprise client: %w", err)
} }

View File

@@ -65,7 +65,7 @@ func newClient(baseURL string, options ...ClientOptionFunc) (*Client, error) {
return c, nil return c, nil
} }
func (c *Client) NewRequestWithContext(ctx context.Context, method, path string, body any) (*http.Request, error) { func (c *Client) NewRequest(method, path string, body interface{}, options []ClientOptionFunc) (*http.Request, error) {
// Make sure the given URL end with a slash // Make sure the given URL end with a slash
if !strings.HasSuffix(c.baseURL, "/") { if !strings.HasSuffix(c.baseURL, "/") {
c.baseURL += "/" c.baseURL += "/"
@@ -82,7 +82,7 @@ func (c *Client) NewRequestWithContext(ctx context.Context, method, path string,
} }
} }
req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, buf) req, err := http.NewRequest(method, c.baseURL+path, buf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -91,7 +91,7 @@ func (c *Client) NewRequestWithContext(ctx context.Context, method, path string,
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
} }
if c.token != "" { if len(c.token) != 0 {
req.Header.Set("Authorization", "Bearer "+c.token) req.Header.Set("Authorization", "Bearer "+c.token)
} }
@@ -102,7 +102,7 @@ func (c *Client) NewRequestWithContext(ctx context.Context, method, path string,
return req, nil return req, nil
} }
func (c *Client) Do(req *http.Request, v any) (*http.Response, error) { func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*http.Response, error) {
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -143,7 +143,7 @@ func CheckResponse(resp *http.Response) error {
return fmt.Errorf("API error with status code %d: %w", resp.StatusCode, err) return fmt.Errorf("API error with status code %d: %w", resp.StatusCode, err)
} }
var raw map[string]any var raw map[string]interface{}
if err := json.Unmarshal(data, &raw); err != nil { if err := json.Unmarshal(data, &raw); err != nil {
return fmt.Errorf("API error with status code %d: %s", resp.StatusCode, string(data)) return fmt.Errorf("API error with status code %d: %s", resp.StatusCode, string(data))
} }

View File

@@ -2,7 +2,7 @@ package http
import ( import (
"bytes" "bytes"
"errors" "context"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@@ -14,7 +14,7 @@ import (
) )
func TestClient(t *testing.T) { func TestClient(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte("Hello, World!")) _, err := w.Write([]byte("Hello, World!"))
if err != nil { if err != nil {
@@ -29,13 +29,15 @@ func TestClient(t *testing.T) {
} }
func TestClientDo(t *testing.T) { func TestClientDo(t *testing.T) {
ctx := context.Background()
for _, c := range []struct { for _, c := range []struct {
name string name string
params map[string]string params map[string]string
content []byte content []byte
fakeServer *httptest.Server fakeServer *httptest.Server
clientOptionFns []ClientOptionFunc clientOptionFns []ClientOptionFunc
expected []map[string]any expected []map[string]interface{}
expectedCode int expectedCode int
expectedError error expectedError error
}{ }{
@@ -45,7 +47,7 @@ func TestClientDo(t *testing.T) {
"pkey1": "val1", "pkey1": "val1",
"pkey2": "val2", "pkey2": "val2",
}, },
fakeServer: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { fakeServer: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte(`[{ _, err := w.Write([]byte(`[{
"key1": "val1", "key1": "val1",
@@ -62,12 +64,12 @@ func TestClientDo(t *testing.T) {
} }
})), })),
clientOptionFns: nil, clientOptionFns: nil,
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"key1": "val1", "key1": "val1",
"key2": map[string]any{ "key2": map[string]interface{}{
"key2_1": "val2_1", "key2_1": "val2_1",
"key2_2": map[string]any{ "key2_2": map[string]interface{}{
"key2_2_1": "val2_2_1", "key2_2_1": "val2_2_1",
}, },
}, },
@@ -105,9 +107,9 @@ func TestClientDo(t *testing.T) {
} }
})), })),
clientOptionFns: nil, clientOptionFns: nil,
expected: []map[string]any(nil), expected: []map[string]interface{}(nil),
expectedCode: http.StatusUnauthorized, expectedCode: http.StatusUnauthorized,
expectedError: errors.New("API error with status code 401: "), expectedError: fmt.Errorf("API error with status code 401: "),
}, },
} { } {
cc := c cc := c
@@ -117,12 +119,12 @@ func TestClientDo(t *testing.T) {
client, err := NewClient(cc.fakeServer.URL, cc.clientOptionFns...) client, err := NewClient(cc.fakeServer.URL, cc.clientOptionFns...)
require.NoError(t, err, "NewClient returned unexpected error") require.NoError(t, err, "NewClient returned unexpected error")
req, err := client.NewRequestWithContext(t.Context(), http.MethodPost, "", cc.params) req, err := client.NewRequest("POST", "", cc.params, nil)
require.NoError(t, err, "NewRequest returned unexpected error") require.NoError(t, err, "NewRequest returned unexpected error")
var data []map[string]any var data []map[string]interface{}
resp, err := client.Do(req, &data) resp, err := client.Do(ctx, req, &data)
if cc.expectedError != nil { if cc.expectedError != nil {
assert.EqualError(t, err, cc.expectedError.Error()) assert.EqualError(t, err, cc.expectedError.Error())

View File

@@ -1,15 +1,78 @@
// Code generated by mockery; DO NOT EDIT. // Code generated by mockery v2.43.2. DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
package mocks package mocks
import ( import (
"context" context "context"
mock "github.com/stretchr/testify/mock" mock "github.com/stretchr/testify/mock"
) )
// Repos is an autogenerated mock type for the Repos type
type Repos struct {
mock.Mock
}
// GetDirectories provides a mock function with given fields: ctx, repoURL, revision, noRevisionCache, verifyCommit
func (_m *Repos) GetDirectories(ctx context.Context, repoURL string, revision string, noRevisionCache bool, verifyCommit bool) ([]string, error) {
ret := _m.Called(ctx, repoURL, revision, noRevisionCache, verifyCommit)
if len(ret) == 0 {
panic("no return value specified for GetDirectories")
}
var r0 []string
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, bool, bool) ([]string, error)); ok {
return rf(ctx, repoURL, revision, noRevisionCache, verifyCommit)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, bool, bool) []string); ok {
r0 = rf(ctx, repoURL, revision, noRevisionCache, verifyCommit)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, bool, bool) error); ok {
r1 = rf(ctx, repoURL, revision, noRevisionCache, verifyCommit)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetFiles provides a mock function with given fields: ctx, repoURL, revision, pattern, noRevisionCache, verifyCommit
func (_m *Repos) GetFiles(ctx context.Context, repoURL string, revision string, pattern string, noRevisionCache bool, verifyCommit bool) (map[string][]byte, error) {
ret := _m.Called(ctx, repoURL, revision, pattern, noRevisionCache, verifyCommit)
if len(ret) == 0 {
panic("no return value specified for GetFiles")
}
var r0 map[string][]byte
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool, bool) (map[string][]byte, error)); ok {
return rf(ctx, repoURL, revision, pattern, noRevisionCache, verifyCommit)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool, bool) map[string][]byte); ok {
r0 = rf(ctx, repoURL, revision, pattern, noRevisionCache, verifyCommit)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string][]byte)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, bool, bool) error); ok {
r1 = rf(ctx, repoURL, revision, pattern, noRevisionCache, verifyCommit)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewRepos creates a new instance of Repos. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // NewRepos creates a new instance of Repos. 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. // The first argument is typically a *testing.T value.
func NewRepos(t interface { func NewRepos(t interface {
@@ -23,206 +86,3 @@ func NewRepos(t interface {
return mock return mock
} }
// Repos is an autogenerated mock type for the Repos type
type Repos struct {
mock.Mock
}
type Repos_Expecter struct {
mock *mock.Mock
}
func (_m *Repos) EXPECT() *Repos_Expecter {
return &Repos_Expecter{mock: &_m.Mock}
}
// GetDirectories provides a mock function for the type Repos
func (_mock *Repos) GetDirectories(ctx context.Context, repoURL string, revision string, project string, noRevisionCache bool, verifyCommit bool) ([]string, error) {
ret := _mock.Called(ctx, repoURL, revision, project, noRevisionCache, verifyCommit)
if len(ret) == 0 {
panic("no return value specified for GetDirectories")
}
var r0 []string
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string, bool, bool) ([]string, error)); ok {
return returnFunc(ctx, repoURL, revision, project, noRevisionCache, verifyCommit)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string, bool, bool) []string); ok {
r0 = returnFunc(ctx, repoURL, revision, project, noRevisionCache, verifyCommit)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, string, bool, bool) error); ok {
r1 = returnFunc(ctx, repoURL, revision, project, noRevisionCache, verifyCommit)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Repos_GetDirectories_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetDirectories'
type Repos_GetDirectories_Call struct {
*mock.Call
}
// GetDirectories is a helper method to define mock.On call
// - ctx context.Context
// - repoURL string
// - revision string
// - project string
// - noRevisionCache bool
// - verifyCommit bool
func (_e *Repos_Expecter) GetDirectories(ctx interface{}, repoURL interface{}, revision interface{}, project interface{}, noRevisionCache interface{}, verifyCommit interface{}) *Repos_GetDirectories_Call {
return &Repos_GetDirectories_Call{Call: _e.mock.On("GetDirectories", ctx, repoURL, revision, project, noRevisionCache, verifyCommit)}
}
func (_c *Repos_GetDirectories_Call) Run(run func(ctx context.Context, repoURL string, revision string, project string, noRevisionCache bool, verifyCommit bool)) *Repos_GetDirectories_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)
}
var arg3 string
if args[3] != nil {
arg3 = args[3].(string)
}
var arg4 bool
if args[4] != nil {
arg4 = args[4].(bool)
}
var arg5 bool
if args[5] != nil {
arg5 = args[5].(bool)
}
run(
arg0,
arg1,
arg2,
arg3,
arg4,
arg5,
)
})
return _c
}
func (_c *Repos_GetDirectories_Call) Return(strings []string, err error) *Repos_GetDirectories_Call {
_c.Call.Return(strings, err)
return _c
}
func (_c *Repos_GetDirectories_Call) RunAndReturn(run func(ctx context.Context, repoURL string, revision string, project string, noRevisionCache bool, verifyCommit bool) ([]string, error)) *Repos_GetDirectories_Call {
_c.Call.Return(run)
return _c
}
// GetFiles provides a mock function for the type Repos
func (_mock *Repos) GetFiles(ctx context.Context, repoURL string, revision string, project string, pattern string, noRevisionCache bool, verifyCommit bool) (map[string][]byte, error) {
ret := _mock.Called(ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit)
if len(ret) == 0 {
panic("no return value specified for GetFiles")
}
var r0 map[string][]byte
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string, string, bool, bool) (map[string][]byte, error)); ok {
return returnFunc(ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string, string, bool, bool) map[string][]byte); ok {
r0 = returnFunc(ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string][]byte)
}
}
if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, string, string, bool, bool) error); ok {
r1 = returnFunc(ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Repos_GetFiles_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFiles'
type Repos_GetFiles_Call struct {
*mock.Call
}
// GetFiles is a helper method to define mock.On call
// - ctx context.Context
// - repoURL string
// - revision string
// - project string
// - pattern string
// - noRevisionCache bool
// - verifyCommit bool
func (_e *Repos_Expecter) GetFiles(ctx interface{}, repoURL interface{}, revision interface{}, project interface{}, pattern interface{}, noRevisionCache interface{}, verifyCommit interface{}) *Repos_GetFiles_Call {
return &Repos_GetFiles_Call{Call: _e.mock.On("GetFiles", ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit)}
}
func (_c *Repos_GetFiles_Call) Run(run func(ctx context.Context, repoURL string, revision string, project string, pattern string, noRevisionCache bool, verifyCommit bool)) *Repos_GetFiles_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)
}
var arg3 string
if args[3] != nil {
arg3 = args[3].(string)
}
var arg4 string
if args[4] != nil {
arg4 = args[4].(string)
}
var arg5 bool
if args[5] != nil {
arg5 = args[5].(bool)
}
var arg6 bool
if args[6] != nil {
arg6 = args[6].(bool)
}
run(
arg0,
arg1,
arg2,
arg3,
arg4,
arg5,
arg6,
)
})
return _c
}
func (_c *Repos_GetFiles_Call) Return(stringToBytes map[string][]byte, err error) *Repos_GetFiles_Call {
_c.Call.Return(stringToBytes, err)
return _c
}
func (_c *Repos_GetFiles_Call) RunAndReturn(run func(ctx context.Context, repoURL string, revision string, project string, pattern string, noRevisionCache bool, verifyCommit bool) (map[string][]byte, error)) *Repos_GetFiles_Call {
_c.Call.Return(run)
return _c
}

View File

@@ -5,8 +5,8 @@ import (
"fmt" "fmt"
"net/http" "net/http"
internalhttp "github.com/argoproj/argo-cd/v3/applicationset/services/internal/http" internalhttp "github.com/argoproj/argo-cd/v2/applicationset/services/internal/http"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
// ServiceRequest is the request object sent to the plugin service. // ServiceRequest is the request object sent to the plugin service.
@@ -20,7 +20,7 @@ type ServiceRequest struct {
type Output struct { type Output struct {
// Parameters is the list of parameter sets returned by the plugin. // Parameters is the list of parameter sets returned by the plugin.
Parameters []map[string]any `json:"parameters"` Parameters []map[string]interface{} `json:"parameters"`
} }
// ServiceResponse is the response object returned by the plugin service. // ServiceResponse is the response object returned by the plugin service.
@@ -34,7 +34,7 @@ type Service struct {
appSetName string appSetName string
} }
func NewPluginService(appSetName string, baseURL string, token string, requestTimeout int) (*Service, error) { func NewPluginService(ctx context.Context, appSetName string, baseURL string, token string, requestTimeout int) (*Service, error) {
var clientOptionFns []internalhttp.ClientOptionFunc var clientOptionFns []internalhttp.ClientOptionFunc
clientOptionFns = append(clientOptionFns, internalhttp.WithToken(token)) clientOptionFns = append(clientOptionFns, internalhttp.WithToken(token))
@@ -55,14 +55,14 @@ func NewPluginService(appSetName string, baseURL string, token string, requestTi
} }
func (p *Service) List(ctx context.Context, parameters v1alpha1.PluginParameters) (*ServiceResponse, error) { func (p *Service) List(ctx context.Context, parameters v1alpha1.PluginParameters) (*ServiceResponse, error) {
req, err := p.client.NewRequestWithContext(ctx, http.MethodPost, "api/v1/getparams.execute", ServiceRequest{ApplicationSetName: p.appSetName, Input: v1alpha1.PluginInput{Parameters: parameters}}) req, err := p.client.NewRequest(http.MethodPost, "api/v1/getparams.execute", ServiceRequest{ApplicationSetName: p.appSetName, Input: v1alpha1.PluginInput{Parameters: parameters}}, nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("NewRequest returned unexpected error: %w", err) return nil, fmt.Errorf("NewRequest returned unexpected error: %w", err)
} }
var data ServiceResponse var data ServiceResponse
_, err = p.client.Do(req, &data) _, err = p.client.Do(ctx, req, &data)
if err != nil { if err != nil {
return nil, fmt.Errorf("error get api '%s': %w", p.appSetName, err) return nil, fmt.Errorf("error get api '%s': %w", p.appSetName, err)
} }

View File

@@ -1,6 +1,7 @@
package plugin package plugin
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
@@ -30,10 +31,10 @@ func TestPlugin(t *testing.T) {
ts := httptest.NewServer(handler) ts := httptest.NewServer(handler)
defer ts.Close() defer ts.Close()
client, err := NewPluginService("plugin-test", ts.URL, token, 0) client, err := NewPluginService(context.Background(), "plugin-test", ts.URL, token, 0)
require.NoError(t, err) require.NoError(t, err)
data, err := client.List(t.Context(), nil) data, err := client.List(context.Background(), nil)
require.NoError(t, err) require.NoError(t, err)
var expectedData ServiceResponse var expectedData ServiceResponse

View File

@@ -1,9 +1,10 @@
package plugin package plugin
import ( import (
"fmt"
"strings" "strings"
"github.com/argoproj/argo-cd/v3/common" "github.com/argoproj/argo-cd/v2/common"
) )
// ParseSecretKey retrieves secret appSetName if different from common ArgoCDSecretName. // ParseSecretKey retrieves secret appSetName if different from common ArgoCDSecretName.
@@ -11,7 +12,7 @@ func ParseSecretKey(key string) (secretName string, tokenKey string) {
if strings.Contains(key, ":") { if strings.Contains(key, ":") {
parts := strings.Split(key, ":") parts := strings.Split(key, ":")
secretName = parts[0][1:] secretName = parts[0][1:]
tokenKey = "$" + parts[1] tokenKey = fmt.Sprintf("$%s", parts[1])
} else { } else {
secretName = common.ArgoCDSecretName secretName = common.ArgoCDSecretName
tokenKey = key tokenKey = key

View File

@@ -5,9 +5,9 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7" "github.com/microsoft/azure-devops-go-api/azuredevops"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/core" core "github.com/microsoft/azure-devops-go-api/azuredevops/core"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/git" git "github.com/microsoft/azure-devops-go-api/azuredevops/git"
) )
const AZURE_DEVOPS_DEFAULT_URL = "https://dev.azure.com" const AZURE_DEVOPS_DEFAULT_URL = "https://dev.azure.com"
@@ -41,14 +41,14 @@ var (
_ AzureDevOpsClientFactory = &devopsFactoryImpl{} _ AzureDevOpsClientFactory = &devopsFactoryImpl{}
) )
func NewAzureDevOpsService(token, url, organization, project, repo string, labels []string) (PullRequestService, error) { func NewAzureDevOpsService(ctx context.Context, token, url, organization, project, repo string, labels []string) (PullRequestService, error) {
organizationURL := buildURL(url, organization) organizationUrl := buildURL(url, organization)
var connection *azuredevops.Connection var connection *azuredevops.Connection
if token == "" { if token == "" {
connection = azuredevops.NewAnonymousConnection(organizationURL) connection = azuredevops.NewAnonymousConnection(organizationUrl)
} else { } else {
connection = azuredevops.NewPatConnection(organizationURL, token) connection = azuredevops.NewPatConnection(organizationUrl, token)
} }
return &AzureDevOpsService{ return &AzureDevOpsService{

View File

@@ -4,14 +4,15 @@ import (
"context" "context"
"testing" "testing"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/core" "github.com/microsoft/azure-devops-go-api/azuredevops/webapi"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/git"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/webapi" "github.com/microsoft/azure-devops-go-api/azuredevops/core"
git "github.com/microsoft/azure-devops-go-api/azuredevops/git"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
azureMock "github.com/argoproj/argo-cd/v3/applicationset/services/scm_provider/azure_devops/git/mocks" azureMock "github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider/azure_devops/git/mocks"
) )
func createBoolPtr(x bool) *bool { func createBoolPtr(x bool) *bool {
@@ -60,20 +61,20 @@ func (m *AzureClientFactoryMock) GetClient(ctx context.Context) (git.Client, err
func TestListPullRequest(t *testing.T) { func TestListPullRequest(t *testing.T) {
teamProject := "myorg_project" teamProject := "myorg_project"
repoName := "myorg_project_repo" repoName := "myorg_project_repo"
prID := 123 pr_id := 123
prTitle := "feat(123)" pr_title := "feat(123)"
prHeadSha := "cd4973d9d14a08ffe6b641a89a68891d6aac8056" pr_head_sha := "cd4973d9d14a08ffe6b641a89a68891d6aac8056"
ctx := t.Context() ctx := context.Background()
uniqueName := "testName" uniqueName := "testName"
pullRequestMock := []git.GitPullRequest{ pullRequestMock := []git.GitPullRequest{
{ {
PullRequestId: createIntPtr(prID), PullRequestId: createIntPtr(pr_id),
Title: createStringPtr(prTitle), Title: createStringPtr(pr_title),
SourceRefName: createStringPtr("refs/heads/feature-branch"), SourceRefName: createStringPtr("refs/heads/feature-branch"),
TargetRefName: createStringPtr("refs/heads/main"), TargetRefName: createStringPtr("refs/heads/main"),
LastMergeSourceCommit: &git.GitCommitRef{ LastMergeSourceCommit: &git.GitCommitRef{
CommitId: createStringPtr(prHeadSha), CommitId: createStringPtr(pr_head_sha),
}, },
Labels: &[]core.WebApiTagDefinition{}, Labels: &[]core.WebApiTagDefinition{},
Repository: &git.GitRepository{ Repository: &git.GitRepository{
@@ -107,9 +108,9 @@ func TestListPullRequest(t *testing.T) {
assert.Len(t, list, 1) assert.Len(t, list, 1)
assert.Equal(t, "feature-branch", list[0].Branch) assert.Equal(t, "feature-branch", list[0].Branch)
assert.Equal(t, "main", list[0].TargetBranch) assert.Equal(t, "main", list[0].TargetBranch)
assert.Equal(t, prHeadSha, list[0].HeadSHA) assert.Equal(t, pr_head_sha, list[0].HeadSHA)
assert.Equal(t, "feat(123)", list[0].Title) assert.Equal(t, "feat(123)", list[0].Title)
assert.Equal(t, prID, list[0].Number) assert.Equal(t, pr_id, list[0].Number)
assert.Equal(t, uniqueName, list[0].Author) assert.Equal(t, uniqueName, list[0].Author)
} }

View File

@@ -3,7 +3,6 @@ package pull_request
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net/url" "net/url"
@@ -17,19 +16,10 @@ type BitbucketCloudService struct {
} }
type BitbucketCloudPullRequest struct { type BitbucketCloudPullRequest struct {
ID int `json:"id"` ID int `json:"id"`
Title string `json:"title"` Title string `json:"title"`
Source BitbucketCloudPullRequestSource `json:"source"` Source BitbucketCloudPullRequestSource `json:"source"`
Author BitbucketCloudPullRequestAuthor `json:"author"` Author BitbucketCloudPullRequestAuthor `json:"author"`
Destination BitbucketCloudPullRequestDestination `json:"destination"`
}
type BitbucketCloudPullRequestDestination struct {
Branch BitbucketCloudPullRequestDestinationBranch `json:"branch"`
}
type BitbucketCloudPullRequestDestinationBranch struct {
Name string `json:"name"`
} }
type BitbucketCloudPullRequestSource struct { type BitbucketCloudPullRequestSource struct {
@@ -61,7 +51,7 @@ type PullRequestResponse struct {
var _ PullRequestService = (*BitbucketCloudService)(nil) var _ PullRequestService = (*BitbucketCloudService)(nil)
func parseURL(uri string) (*url.URL, error) { func parseUrl(uri string) (*url.URL, error) {
if uri == "" { if uri == "" {
uri = "https://api.bitbucket.org/2.0" uri = "https://api.bitbucket.org/2.0"
} }
@@ -74,10 +64,10 @@ func parseURL(uri string) (*url.URL, error) {
return url, nil return url, nil
} }
func NewBitbucketCloudServiceBasicAuth(baseURL, username, password, owner, repositorySlug string) (PullRequestService, error) { func NewBitbucketCloudServiceBasicAuth(baseUrl, username, password, owner, repositorySlug string) (PullRequestService, error) {
url, err := parseURL(baseURL) url, err := parseUrl(baseUrl)
if err != nil { if err != nil {
return nil, fmt.Errorf("error parsing base url of %s for %s/%s: %w", baseURL, owner, repositorySlug, err) return nil, fmt.Errorf("error parsing base url of %s for %s/%s: %w", baseUrl, owner, repositorySlug, err)
} }
bitbucketClient := bitbucket.NewBasicAuth(username, password) bitbucketClient := bitbucket.NewBasicAuth(username, password)
@@ -90,10 +80,10 @@ func NewBitbucketCloudServiceBasicAuth(baseURL, username, password, owner, repos
}, nil }, nil
} }
func NewBitbucketCloudServiceBearerToken(baseURL, bearerToken, owner, repositorySlug string) (PullRequestService, error) { func NewBitbucketCloudServiceBearerToken(baseUrl, bearerToken, owner, repositorySlug string) (PullRequestService, error) {
url, err := parseURL(baseURL) url, err := parseUrl(baseUrl)
if err != nil { if err != nil {
return nil, fmt.Errorf("error parsing base url of %s for %s/%s: %w", baseURL, owner, repositorySlug, err) return nil, fmt.Errorf("error parsing base url of %s for %s/%s: %w", baseUrl, owner, repositorySlug, err)
} }
bitbucketClient := bitbucket.NewOAuthbearerToken(bearerToken) bitbucketClient := bitbucket.NewOAuthbearerToken(bearerToken)
@@ -106,9 +96,9 @@ func NewBitbucketCloudServiceBearerToken(baseURL, bearerToken, owner, repository
}, nil }, nil
} }
func NewBitbucketCloudServiceNoAuth(baseURL, owner, repositorySlug string) (PullRequestService, error) { func NewBitbucketCloudServiceNoAuth(baseUrl, owner, repositorySlug string) (PullRequestService, error) {
// There is currently no method to explicitly not require auth // There is currently no method to explicitly not require auth
return NewBitbucketCloudServiceBearerToken(baseURL, "", owner, repositorySlug) return NewBitbucketCloudServiceBearerToken(baseUrl, "", owner, repositorySlug)
} }
func (b *BitbucketCloudService) List(_ context.Context) ([]*PullRequest, error) { func (b *BitbucketCloudService) List(_ context.Context) ([]*PullRequest, error) {
@@ -122,14 +112,14 @@ func (b *BitbucketCloudService) List(_ context.Context) ([]*PullRequest, error)
return nil, fmt.Errorf("error listing pull requests for %s/%s: %w", b.owner, b.repositorySlug, err) return nil, fmt.Errorf("error listing pull requests for %s/%s: %w", b.owner, b.repositorySlug, err)
} }
resp, ok := response.(map[string]any) resp, ok := response.(map[string]interface{})
if !ok { if !ok {
return nil, errors.New("unknown type returned from bitbucket pull requests") return nil, fmt.Errorf("unknown type returned from bitbucket pull requests")
} }
repoArray, ok := resp["values"].([]any) repoArray, ok := resp["values"].([]interface{})
if !ok { if !ok {
return nil, errors.New("unknown type returned from response values") return nil, fmt.Errorf("unknown type returned from response values")
} }
jsonStr, err := json.Marshal(repoArray) jsonStr, err := json.Marshal(repoArray)
@@ -145,12 +135,11 @@ func (b *BitbucketCloudService) List(_ context.Context) ([]*PullRequest, error)
pullRequests := []*PullRequest{} pullRequests := []*PullRequest{}
for _, pull := range pulls { for _, pull := range pulls {
pullRequests = append(pullRequests, &PullRequest{ pullRequests = append(pullRequests, &PullRequest{
Number: pull.ID, Number: pull.ID,
Title: pull.Title, Title: pull.Title,
Branch: pull.Source.Branch.Name, Branch: pull.Source.Branch.Name,
TargetBranch: pull.Destination.Branch.Name, HeadSHA: pull.Source.Commit.Hash,
HeadSHA: pull.Source.Commit.Hash, Author: pull.Author.Nickname,
Author: pull.Author.Nickname,
}) })
} }

View File

@@ -1,6 +1,7 @@
package pull_request package pull_request
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@@ -10,7 +11,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
func defaultHandlerCloud(t *testing.T) func(http.ResponseWriter, *http.Request) { func defaultHandlerCloud(t *testing.T) func(http.ResponseWriter, *http.Request) {
@@ -53,11 +54,11 @@ func defaultHandlerCloud(t *testing.T) func(http.ResponseWriter, *http.Request)
} }
func TestParseUrlEmptyUrl(t *testing.T) { func TestParseUrlEmptyUrl(t *testing.T) {
url, err := parseURL("") url, err := parseUrl("")
bitbucketURL, _ := url.Parse("https://api.bitbucket.org/2.0") bitbucketUrl, _ := url.Parse("https://api.bitbucket.org/2.0")
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, bitbucketURL, url) assert.Equal(t, bitbucketUrl, url)
} }
func TestInvalidBaseUrlBasicAuthCloud(t *testing.T) { func TestInvalidBaseUrlBasicAuthCloud(t *testing.T) {
@@ -86,7 +87,7 @@ func TestListPullRequestBearerTokenCloud(t *testing.T) {
defer ts.Close() defer ts.Close()
svc, err := NewBitbucketCloudServiceBearerToken(ts.URL, "TOKEN", "OWNER", "REPO") svc, err := NewBitbucketCloudServiceBearerToken(ts.URL, "TOKEN", "OWNER", "REPO")
require.NoError(t, err) require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 1) assert.Len(t, pullRequests, 1)
assert.Equal(t, 101, pullRequests[0].Number) assert.Equal(t, 101, pullRequests[0].Number)
@@ -104,7 +105,7 @@ func TestListPullRequestNoAuthCloud(t *testing.T) {
defer ts.Close() defer ts.Close()
svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO") svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
require.NoError(t, err) require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 1) assert.Len(t, pullRequests, 1)
assert.Equal(t, 101, pullRequests[0].Number) assert.Equal(t, 101, pullRequests[0].Number)
@@ -122,7 +123,7 @@ func TestListPullRequestBasicAuthCloud(t *testing.T) {
defer ts.Close() defer ts.Close()
svc, err := NewBitbucketCloudServiceBasicAuth(ts.URL, "user", "password", "OWNER", "REPO") svc, err := NewBitbucketCloudServiceBasicAuth(ts.URL, "user", "password", "OWNER", "REPO")
require.NoError(t, err) require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 1) assert.Len(t, pullRequests, 1)
assert.Equal(t, 101, pullRequests[0].Number) assert.Equal(t, 101, pullRequests[0].Number)
@@ -138,7 +139,7 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
var err error var err error
switch r.RequestURI { switch r.RequestURI {
case "/repositories/OWNER/REPO/pullrequests/": case "/repositories/OWNER/REPO/pullrequests/":
_, err = fmt.Fprintf(w, `{ _, err = io.WriteString(w, fmt.Sprintf(`{
"size": 2, "size": 2,
"pagelen": 1, "pagelen": 1,
"page": 1, "page": 1,
@@ -177,9 +178,9 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
} }
} }
] ]
}`, r.Host) }`, r.Host))
case "/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=2": case "/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=2":
_, err = fmt.Fprintf(w, `{ _, err = io.WriteString(w, fmt.Sprintf(`{
"size": 2, "size": 2,
"pagelen": 1, "pagelen": 1,
"page": 2, "page": 2,
@@ -202,7 +203,7 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
} }
} }
] ]
}`, r.Host) }`, r.Host))
default: default:
t.Fail() t.Fail()
} }
@@ -213,7 +214,7 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
defer ts.Close() defer ts.Close()
svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO") svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
require.NoError(t, err) require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 3) assert.Len(t, pullRequests, 3)
assert.Equal(t, PullRequest{ assert.Equal(t, PullRequest{
@@ -240,12 +241,12 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
} }
func TestListResponseErrorCloud(t *testing.T) { func TestListResponseErrorCloud(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
})) }))
defer ts.Close() defer ts.Close()
svc, _ := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO") svc, _ := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
_, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) _, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.Error(t, err) require.Error(t, err)
} }
@@ -269,7 +270,7 @@ func TestListResponseMalformedCloud(t *testing.T) {
})) }))
defer ts.Close() defer ts.Close()
svc, _ := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO") svc, _ := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
_, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) _, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.Error(t, err) require.Error(t, err)
} }
@@ -293,7 +294,7 @@ func TestListResponseMalformedValuesCloud(t *testing.T) {
})) }))
defer ts.Close() defer ts.Close()
svc, _ := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO") svc, _ := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
_, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) _, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.Error(t, err) require.Error(t, err)
} }
@@ -318,7 +319,7 @@ func TestListResponseEmptyCloud(t *testing.T) {
defer ts.Close() defer ts.Close()
svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO") svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
require.NoError(t, err) require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err) require.NoError(t, err)
assert.Empty(t, pullRequests) assert.Empty(t, pullRequests)
} }
@@ -329,7 +330,7 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
var err error var err error
switch r.RequestURI { switch r.RequestURI {
case "/repositories/OWNER/REPO/pullrequests/": case "/repositories/OWNER/REPO/pullrequests/":
_, err = fmt.Fprintf(w, `{ _, err = io.WriteString(w, fmt.Sprintf(`{
"size": 2, "size": 2,
"pagelen": 1, "pagelen": 1,
"page": 1, "page": 1,
@@ -349,11 +350,6 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
}, },
"author": { "author": {
"nickname": "testName" "nickname": "testName"
},
"destination": {
"branch": {
"name": "master"
}
} }
}, },
{ {
@@ -370,17 +366,12 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
}, },
"author": { "author": {
"nickname": "testName" "nickname": "testName"
},
"destination": {
"branch": {
"name": "branch-200"
}
} }
} }
] ]
}`, r.Host) }`, r.Host))
case "/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=2": case "/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=2":
_, err = fmt.Fprintf(w, `{ _, err = io.WriteString(w, fmt.Sprintf(`{
"size": 2, "size": 2,
"pagelen": 1, "pagelen": 1,
"page": 2, "page": 2,
@@ -400,15 +391,10 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
}, },
"author": { "author": {
"nickname": "testName" "nickname": "testName"
},
"destination": {
"branch": {
"name": "master"
}
} }
} }
] ]
}`, r.Host) }`, r.Host))
default: default:
t.Fail() t.Fail()
} }
@@ -420,7 +406,7 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
regexp := `feature-1[\d]{2}` regexp := `feature-1[\d]{2}`
svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO") svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
require.NoError(t, err) require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{ pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{ {
BranchMatch: &regexp, BranchMatch: &regexp,
}, },
@@ -428,26 +414,24 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 2) assert.Len(t, pullRequests, 2)
assert.Equal(t, PullRequest{ assert.Equal(t, PullRequest{
Number: 101, Number: 101,
Title: "feat(101)", Title: "feat(101)",
Branch: "feature-101", Branch: "feature-101",
HeadSHA: "1a8dd249c04a", HeadSHA: "1a8dd249c04a",
Author: "testName", Author: "testName",
TargetBranch: "master",
}, *pullRequests[0]) }, *pullRequests[0])
assert.Equal(t, PullRequest{ assert.Equal(t, PullRequest{
Number: 102, Number: 102,
Title: "feat(102)", Title: "feat(102)",
Branch: "feature-102", Branch: "feature-102",
HeadSHA: "6344d9623e3b", HeadSHA: "6344d9623e3b",
Author: "testName", Author: "testName",
TargetBranch: "master",
}, *pullRequests[1]) }, *pullRequests[1])
regexp = `.*2$` regexp = `.*2$`
svc, err = NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO") svc, err = NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
require.NoError(t, err) require.NoError(t, err)
pullRequests, err = ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{ pullRequests, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{ {
BranchMatch: &regexp, BranchMatch: &regexp,
}, },
@@ -455,40 +439,20 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 1) assert.Len(t, pullRequests, 1)
assert.Equal(t, PullRequest{ assert.Equal(t, PullRequest{
Number: 102, Number: 102,
Title: "feat(102)", Title: "feat(102)",
Branch: "feature-102", Branch: "feature-102",
HeadSHA: "6344d9623e3b", HeadSHA: "6344d9623e3b",
Author: "testName", Author: "testName",
TargetBranch: "master",
}, *pullRequests[0]) }, *pullRequests[0])
regexp = `[\d{2}` regexp = `[\d{2}`
svc, err = NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO") svc, err = NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
require.NoError(t, err) require.NoError(t, err)
_, err = ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{ _, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{ {
BranchMatch: &regexp, BranchMatch: &regexp,
}, },
}) })
require.Error(t, err) require.Error(t, err)
regexp = `feature-2[\d]{2}`
targetRegexp := `branch.*`
pullRequests, err = ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{
{
BranchMatch: &regexp,
TargetBranchMatch: &targetRegexp,
},
})
require.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.Equal(t, PullRequest{
Number: 200,
Title: "feat(200)",
Branch: "feature-200",
HeadSHA: "4cf807e67a6d",
Author: "testName",
TargetBranch: "branch-200",
}, *pullRequests[0])
} }

View File

@@ -8,7 +8,7 @@ import (
bitbucketv1 "github.com/gfleury/go-bitbucket-v1" bitbucketv1 "github.com/gfleury/go-bitbucket-v1"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v3/applicationset/utils" "github.com/argoproj/argo-cd/v2/applicationset/utils"
) )
type BitbucketService struct { type BitbucketService struct {
@@ -64,7 +64,7 @@ func newBitbucketService(ctx context.Context, bitbucketConfig *bitbucketv1.Confi
} }
func (b *BitbucketService) List(_ context.Context) ([]*PullRequest, error) { func (b *BitbucketService) List(_ context.Context) ([]*PullRequest, error) {
paged := map[string]any{ paged := map[string]interface{}{
"limit": 100, "limit": 100,
} }

View File

@@ -1,6 +1,7 @@
package pull_request package pull_request
import ( import (
"context"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"io" "io"
@@ -11,7 +12,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
func defaultHandler(t *testing.T) func(http.ResponseWriter, *http.Request) { func defaultHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
@@ -63,9 +64,9 @@ func TestListPullRequestNoAuth(t *testing.T) {
defaultHandler(t)(w, r) defaultHandler(t)(w, r)
})) }))
defer ts.Close() defer ts.Close()
svc, err := NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil) svc, err := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO", "", false, nil)
require.NoError(t, err) require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 1) assert.Len(t, pullRequests, 1)
assert.Equal(t, 101, pullRequests[0].Number) assert.Equal(t, 101, pullRequests[0].Number)
@@ -164,9 +165,9 @@ func TestListPullRequestPagination(t *testing.T) {
} }
})) }))
defer ts.Close() defer ts.Close()
svc, err := NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil) svc, err := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO", "", false, nil)
require.NoError(t, err) require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 3) assert.Len(t, pullRequests, 3)
assert.Equal(t, PullRequest{ assert.Equal(t, PullRequest{
@@ -206,9 +207,9 @@ func TestListPullRequestBasicAuth(t *testing.T) {
defaultHandler(t)(w, r) defaultHandler(t)(w, r)
})) }))
defer ts.Close() defer ts.Close()
svc, err := NewBitbucketServiceBasicAuth(t.Context(), "user", "password", ts.URL, "PROJECT", "REPO", "", false, nil) svc, err := NewBitbucketServiceBasicAuth(context.Background(), "user", "password", ts.URL, "PROJECT", "REPO", "", false, nil)
require.NoError(t, err) require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 1) assert.Len(t, pullRequests, 1)
assert.Equal(t, 101, pullRequests[0].Number) assert.Equal(t, 101, pullRequests[0].Number)
@@ -223,9 +224,9 @@ func TestListPullRequestBearerAuth(t *testing.T) {
defaultHandler(t)(w, r) defaultHandler(t)(w, r)
})) }))
defer ts.Close() defer ts.Close()
svc, err := NewBitbucketServiceBearerToken(t.Context(), "tolkien", ts.URL, "PROJECT", "REPO", "", false, nil) svc, err := NewBitbucketServiceBearerToken(context.Background(), "tolkien", ts.URL, "PROJECT", "REPO", "", false, nil)
require.NoError(t, err) require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 1) assert.Len(t, pullRequests, 1)
assert.Equal(t, 101, pullRequests[0].Number) assert.Equal(t, 101, pullRequests[0].Number)
@@ -276,7 +277,7 @@ func TestListPullRequestTLS(t *testing.T) {
defer ts.Close() defer ts.Close()
var certs []byte var certs []byte
if test.passCerts { if test.passCerts == true {
for _, cert := range ts.TLS.Certificates { for _, cert := range ts.TLS.Certificates {
for _, c := range cert.Certificate { for _, c := range cert.Certificate {
parsedCert, err := x509.ParseCertificate(c) parsedCert, err := x509.ParseCertificate(c)
@@ -289,9 +290,9 @@ func TestListPullRequestTLS(t *testing.T) {
} }
} }
svc, err := NewBitbucketServiceBasicAuth(t.Context(), "user", "password", ts.URL, "PROJECT", "REPO", "", test.tlsInsecure, certs) svc, err := NewBitbucketServiceBasicAuth(context.Background(), "user", "password", ts.URL, "PROJECT", "REPO", "", test.tlsInsecure, certs)
require.NoError(t, err) require.NoError(t, err)
_, err = ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) _, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
if test.requireErr { if test.requireErr {
require.Error(t, err) require.Error(t, err)
} else { } else {
@@ -302,12 +303,12 @@ func TestListPullRequestTLS(t *testing.T) {
} }
func TestListResponseError(t *testing.T) { func TestListResponseError(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
})) }))
defer ts.Close() defer ts.Close()
svc, _ := NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil) svc, _ := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO", "", false, nil)
_, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) _, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.Error(t, err) require.Error(t, err)
} }
@@ -331,8 +332,8 @@ func TestListResponseMalformed(t *testing.T) {
} }
})) }))
defer ts.Close() defer ts.Close()
svc, _ := NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil) svc, _ := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO", "", false, nil)
_, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) _, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.Error(t, err) require.Error(t, err)
} }
@@ -356,9 +357,9 @@ func TestListResponseEmpty(t *testing.T) {
} }
})) }))
defer ts.Close() defer ts.Close()
svc, err := NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil) svc, err := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO", "", false, nil)
require.NoError(t, err) require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err) require.NoError(t, err)
assert.Empty(t, pullRequests) assert.Empty(t, pullRequests)
} }
@@ -452,9 +453,9 @@ func TestListPullRequestBranchMatch(t *testing.T) {
})) }))
defer ts.Close() defer ts.Close()
regexp := `feature-1[\d]{2}` regexp := `feature-1[\d]{2}`
svc, err := NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil) svc, err := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO", "", false, nil)
require.NoError(t, err) require.NoError(t, err)
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{ pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{ {
BranchMatch: &regexp, BranchMatch: &regexp,
}, },
@@ -481,9 +482,9 @@ func TestListPullRequestBranchMatch(t *testing.T) {
}, *pullRequests[1]) }, *pullRequests[1])
regexp = `.*2$` regexp = `.*2$`
svc, err = NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil) svc, err = NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO", "", false, nil)
require.NoError(t, err) require.NoError(t, err)
pullRequests, err = ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{ pullRequests, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{ {
BranchMatch: &regexp, BranchMatch: &regexp,
}, },
@@ -501,9 +502,9 @@ func TestListPullRequestBranchMatch(t *testing.T) {
}, *pullRequests[0]) }, *pullRequests[0])
regexp = `[\d{2}` regexp = `[\d{2}`
svc, err = NewBitbucketServiceNoAuth(t.Context(), ts.URL, "PROJECT", "REPO", "", false, nil) svc, err = NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO", "", false, nil)
require.NoError(t, err) require.NoError(t, err)
_, err = ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{ _, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{ {
BranchMatch: &regexp, BranchMatch: &regexp,
}, },

View File

@@ -18,6 +18,6 @@ func NewFakeService(_ context.Context, listPullReuests []*PullRequest, listError
}, nil }, nil
} }
func (g *FakeService) List(_ context.Context) ([]*PullRequest, error) { func (g *FakeService) List(ctx context.Context) ([]*PullRequest, error) {
return g.listPullReuests, g.listError return g.listPullReuests, g.listError
} }

View File

@@ -14,12 +14,11 @@ type GiteaService struct {
client *gitea.Client client *gitea.Client
owner string owner string
repo string repo string
labels []string
} }
var _ PullRequestService = (*GiteaService)(nil) var _ PullRequestService = (*GiteaService)(nil)
func NewGiteaService(token, url, owner, repo string, labels []string, insecure bool) (PullRequestService, error) { func NewGiteaService(ctx context.Context, token, url, owner, repo string, insecure bool) (PullRequestService, error) {
if token == "" { if token == "" {
token = os.Getenv("GITEA_TOKEN") token = os.Getenv("GITEA_TOKEN")
} }
@@ -43,7 +42,6 @@ func NewGiteaService(token, url, owner, repo string, labels []string, insecure b
client: client, client: client,
owner: owner, owner: owner,
repo: repo, repo: repo,
labels: labels,
}, nil }, nil
} }
@@ -51,16 +49,12 @@ func (g *GiteaService) List(ctx context.Context) ([]*PullRequest, error) {
opts := gitea.ListPullRequestsOptions{ opts := gitea.ListPullRequestsOptions{
State: gitea.StateOpen, State: gitea.StateOpen,
} }
g.client.SetContext(ctx)
prs, _, err := g.client.ListRepoPullRequests(g.owner, g.repo, opts) prs, _, err := g.client.ListRepoPullRequests(g.owner, g.repo, opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
list := []*PullRequest{} list := []*PullRequest{}
for _, pr := range prs { for _, pr := range prs {
if !giteaContainLabels(g.labels, pr.Labels) {
continue
}
list = append(list, &PullRequest{ list = append(list, &PullRequest{
Number: int(pr.Index), Number: int(pr.Index),
Title: pr.Title, Title: pr.Title,
@@ -74,21 +68,6 @@ func (g *GiteaService) List(ctx context.Context) ([]*PullRequest, error) {
return list, nil return list, nil
} }
// containLabels returns true if gotLabels contains expectedLabels
func giteaContainLabels(expectedLabels []string, gotLabels []*gitea.Label) bool {
gotLabelNamesMap := make(map[string]bool)
for i := 0; i < len(gotLabels); i++ {
gotLabelNamesMap[gotLabels[i].Name] = true
}
for _, expected := range expectedLabels {
v, ok := gotLabelNamesMap[expected]
if !v || !ok {
return false
}
}
return true
}
// Get the Gitea pull request label names. // Get the Gitea pull request label names.
func getGiteaPRLabelNames(giteaLabels []*gitea.Label) []string { func getGiteaPRLabelNames(giteaLabels []*gitea.Label) []string {
var labelNames []string var labelNames []string

View File

@@ -1,6 +1,7 @@
package pull_request package pull_request
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@@ -52,7 +53,7 @@ func giteaMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
}, },
"title": "add an empty file", "title": "add an empty file",
"body": "", "body": "",
"labels": [{"id": 1, "name": "label1", "color": "00aabb", "description": "foo", "url": ""}], "labels": [],
"milestone": null, "milestone": null,
"assignee": null, "assignee": null,
"assignees": null, "assignees": null,
@@ -246,61 +247,13 @@ func giteaMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
} }
} }
func TestGiteaContainLabels(t *testing.T) {
cases := []struct {
Name string
Labels []string
PullLabels []*gitea.Label
Expect bool
}{
{
Name: "Match labels",
Labels: []string{"label1", "label2"},
PullLabels: []*gitea.Label{
{Name: "label1"},
{Name: "label2"},
{Name: "label3"},
},
Expect: true,
},
{
Name: "Not match labels",
Labels: []string{"label1", "label4"},
PullLabels: []*gitea.Label{
{Name: "label1"},
{Name: "label2"},
{Name: "label3"},
},
Expect: false,
},
{
Name: "No specify",
Labels: []string{},
PullLabels: []*gitea.Label{
{Name: "label1"},
{Name: "label2"},
{Name: "label3"},
},
Expect: true,
},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
if got := giteaContainLabels(c.Labels, c.PullLabels); got != c.Expect {
t.Errorf("expect: %v, got: %v", c.Expect, got)
}
})
}
}
func TestGiteaList(t *testing.T) { func TestGiteaList(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
giteaMockHandler(t)(w, r) giteaMockHandler(t)(w, r)
})) }))
host, err := NewGiteaService("", ts.URL, "test-argocd", "pr-test", []string{"label1"}, false) host, err := NewGiteaService(context.Background(), "", ts.URL, "test-argocd", "pr-test", false)
require.NoError(t, err) require.NoError(t, err)
prs, err := host.List(t.Context()) prs, err := host.List(context.Background())
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, prs, 1) assert.Len(t, prs, 1)
assert.Equal(t, 1, prs[0].Number) assert.Equal(t, 1, prs[0].Number)

View File

@@ -3,12 +3,10 @@ package pull_request
import ( import (
"context" "context"
"fmt" "fmt"
"net/http"
"os" "os"
"github.com/google/go-github/v69/github" "github.com/google/go-github/v66/github"
"golang.org/x/oauth2"
appsetutils "github.com/argoproj/argo-cd/v3/applicationset/utils"
) )
type GithubService struct { type GithubService struct {
@@ -20,21 +18,21 @@ type GithubService struct {
var _ PullRequestService = (*GithubService)(nil) var _ PullRequestService = (*GithubService)(nil)
func NewGithubService(token, url, owner, repo string, labels []string, optionalHTTPClient ...*http.Client) (PullRequestService, error) { func NewGithubService(ctx context.Context, token, url, owner, repo string, labels []string) (PullRequestService, error) {
var ts oauth2.TokenSource
// Undocumented environment variable to set a default token, to be used in testing to dodge anonymous rate limits. // Undocumented environment variable to set a default token, to be used in testing to dodge anonymous rate limits.
if token == "" { if token == "" {
token = os.Getenv("GITHUB_TOKEN") token = os.Getenv("GITHUB_TOKEN")
} }
if token != "" {
ts = oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
)
}
httpClient := oauth2.NewClient(ctx, ts)
var client *github.Client var client *github.Client
httpClient := appsetutils.GetOptionalHTTPClient(optionalHTTPClient...)
if url == "" { if url == "" {
if token == "" { client = github.NewClient(httpClient)
client = github.NewClient(httpClient)
} else {
client = github.NewClient(httpClient).WithAuthToken(token)
}
} else { } else {
var err error var err error
client, err = github.NewClient(httpClient).WithEnterpriseURLs(url, url) client, err = github.NewClient(httpClient).WithEnterpriseURLs(url, url)

View File

@@ -1,16 +1,12 @@
package pull_request package pull_request
import ( import (
"net/http" "github.com/argoproj/argo-cd/v2/applicationset/services/github_app_auth"
"github.com/argoproj/argo-cd/v2/applicationset/services/internal/github_app"
"github.com/argoproj/argo-cd/v3/applicationset/services/github_app_auth"
"github.com/argoproj/argo-cd/v3/applicationset/services/internal/github_app"
appsetutils "github.com/argoproj/argo-cd/v3/applicationset/utils"
) )
func NewGithubAppService(g github_app_auth.Authentication, url, owner, repo string, labels []string, optionalHTTPClient ...*http.Client) (PullRequestService, error) { func NewGithubAppService(g github_app_auth.Authentication, url, owner, repo string, labels []string) (PullRequestService, error) {
httpClient := appsetutils.GetOptionalHTTPClient(optionalHTTPClient...) client, err := github_app.Client(g, url)
client, err := github_app.Client(g, url, httpClient)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -3,7 +3,7 @@ package pull_request
import ( import (
"testing" "testing"
"github.com/google/go-github/v69/github" "github.com/google/go-github/v66/github"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )

View File

@@ -7,9 +7,9 @@ import (
"os" "os"
"github.com/hashicorp/go-retryablehttp" "github.com/hashicorp/go-retryablehttp"
gitlab "gitlab.com/gitlab-org/api/client-go" gitlab "github.com/xanzy/go-gitlab"
"github.com/argoproj/argo-cd/v3/applicationset/utils" "github.com/argoproj/argo-cd/v2/applicationset/utils"
) )
type GitLabService struct { type GitLabService struct {
@@ -21,7 +21,7 @@ type GitLabService struct {
var _ PullRequestService = (*GitLabService)(nil) var _ PullRequestService = (*GitLabService)(nil)
func NewGitLabService(token, url, project string, labels []string, pullRequestState string, scmRootCAPath string, insecure bool, caCerts []byte) (PullRequestService, error) { func NewGitLabService(ctx context.Context, token, url, project string, labels []string, pullRequestState string, scmRootCAPath string, insecure bool, caCerts []byte) (PullRequestService, error) {
var clientOptionFns []gitlab.ClientOptionFunc var clientOptionFns []gitlab.ClientOptionFunc
// Set a custom Gitlab base URL if one is provided // Set a custom Gitlab base URL if one is provided
@@ -74,7 +74,7 @@ func (g *GitLabService) List(ctx context.Context) ([]*PullRequest, error) {
pullRequests := []*PullRequest{} pullRequests := []*PullRequest{}
for { for {
mrs, resp, err := g.client.MergeRequests.ListProjectMergeRequests(g.project, opts, gitlab.WithContext(ctx)) mrs, resp, err := g.client.MergeRequests.ListProjectMergeRequests(g.project, opts)
if err != nil { if err != nil {
return nil, fmt.Errorf("error listing merge requests for project '%s': %w", g.project, err) return nil, fmt.Errorf("error listing merge requests for project '%s': %w", g.project, err)
} }

View File

@@ -1,6 +1,7 @@
package pull_request package pull_request
import ( import (
"context"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"io" "io"
@@ -34,10 +35,10 @@ func TestGitLabServiceCustomBaseURL(t *testing.T) {
writeMRListResponse(t, w) writeMRListResponse(t, w)
}) })
svc, err := NewGitLabService("", server.URL, "278964", nil, "", "", false, nil) svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", nil, "", "", false, nil)
require.NoError(t, err) require.NoError(t, err)
_, err = svc.List(t.Context()) _, err = svc.List(context.Background())
require.NoError(t, err) require.NoError(t, err)
} }
@@ -53,10 +54,10 @@ func TestGitLabServiceToken(t *testing.T) {
writeMRListResponse(t, w) writeMRListResponse(t, w)
}) })
svc, err := NewGitLabService("token-123", server.URL, "278964", nil, "", "", false, nil) svc, err := NewGitLabService(context.Background(), "token-123", server.URL, "278964", nil, "", "", false, nil)
require.NoError(t, err) require.NoError(t, err)
_, err = svc.List(t.Context()) _, err = svc.List(context.Background())
require.NoError(t, err) require.NoError(t, err)
} }
@@ -72,10 +73,10 @@ func TestList(t *testing.T) {
writeMRListResponse(t, w) writeMRListResponse(t, w)
}) })
svc, err := NewGitLabService("", server.URL, "278964", []string{}, "", "", false, nil) svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{}, "", "", false, nil)
require.NoError(t, err) require.NoError(t, err)
prs, err := svc.List(t.Context()) prs, err := svc.List(context.Background())
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, prs, 1) assert.Len(t, prs, 1)
assert.Equal(t, 15442, prs[0].Number) assert.Equal(t, 15442, prs[0].Number)
@@ -98,10 +99,10 @@ func TestListWithLabels(t *testing.T) {
writeMRListResponse(t, w) writeMRListResponse(t, w)
}) })
svc, err := NewGitLabService("", server.URL, "278964", []string{"feature", "ready"}, "", "", false, nil) svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{"feature", "ready"}, "", "", false, nil)
require.NoError(t, err) require.NoError(t, err)
_, err = svc.List(t.Context()) _, err = svc.List(context.Background())
require.NoError(t, err) require.NoError(t, err)
} }
@@ -117,10 +118,10 @@ func TestListWithState(t *testing.T) {
writeMRListResponse(t, w) writeMRListResponse(t, w)
}) })
svc, err := NewGitLabService("", server.URL, "278964", []string{}, "opened", "", false, nil) svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{}, "opened", "", false, nil)
require.NoError(t, err) require.NoError(t, err)
_, err = svc.List(t.Context()) _, err = svc.List(context.Background())
require.NoError(t, err) require.NoError(t, err)
} }
@@ -160,13 +161,13 @@ func TestListWithStateTLS(t *testing.T) {
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
writeMRListResponse(t, w) writeMRListResponse(t, w)
})) }))
defer ts.Close() defer ts.Close()
var certs []byte var certs []byte
if test.passCerts { if test.passCerts == true {
for _, cert := range ts.TLS.Certificates { for _, cert := range ts.TLS.Certificates {
for _, c := range cert.Certificate { for _, c := range cert.Certificate {
parsedCert, err := x509.ParseCertificate(c) parsedCert, err := x509.ParseCertificate(c)
@@ -179,10 +180,10 @@ func TestListWithStateTLS(t *testing.T) {
} }
} }
svc, err := NewGitLabService("", ts.URL, "278964", []string{}, "opened", "", test.tlsInsecure, certs) svc, err := NewGitLabService(context.Background(), "", ts.URL, "278964", []string{}, "opened", "", test.tlsInsecure, certs)
require.NoError(t, err) require.NoError(t, err)
_, err = svc.List(t.Context()) _, err = svc.List(context.Background())
if test.requireErr { if test.requireErr {
require.Error(t, err) require.Error(t, err)
} else { } else {

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