Compare commits

...

1 Commits

Author SHA1 Message Date
Justin Marquis
fcdc4f1743 sibling of 587c5ba1c6 2024-06-26 16:04:29 +00:00
1350 changed files with 48141 additions and 114452 deletions

View File

@@ -8,7 +8,6 @@ ignore:
- "pkg/client/.*"
- "vendor/.*"
- "test/.*"
- "**/mocks/*"
coverage:
status:
# we've found this not to be useful

View File

@@ -18,10 +18,8 @@ hack/
docs/
examples/
.github/
!test/container
!test/e2e/testdata
!test/fixture
!test/remote
!test/container
!hack/installers
!hack/gpg-wrapper.sh
!hack/git-verify-wrapper.sh

View File

@@ -9,6 +9,12 @@ assignees: ''
Target RC1 date: ___. __, ____
Target GA date: ___. __, ____
- [ ] Create new section in the [Release Planning doc](https://docs.google.com/document/d/1trJIomcgXcfvLw0aYnERrFWfPjQOfYMDJOCh1S8nMBc/edit?usp=sharing)
- [ ] Schedule a Release Planning meeting roughly two weeks before the scheduled Release freeze date by adding it to the community calendar (or delegate this task to someone with write access to the community calendar)
- [ ] Include Zoom link in the invite
- [ ] Post in #argo-cd and #argo-contributors one week before the meeting
- [ ] Post again one hour before the meeting
- [ ] At the meeting, remove issues/PRs from the project's column for that release which have not been “claimed” by at least one Approver (add it to the next column if Approver requests that)
- [ ] 1wk before feature freeze post in #argo-contributors that PRs must be merged by DD-MM-YYYY to be included in the release - ask approvers to drop items from milestone they cant merge
- [ ] At least two days before RC1 date, draft RC blog post and submit it for review (or delegate this task)
- [ ] Cut RC1 (or delegate this task to an Approver and coordinate timing)

View File

@@ -17,11 +17,6 @@ updates:
schedule:
interval: "daily"
- package-ecosystem: "npm"
directory: "/ui-test/"
schedule:
interval: "daily"
- package-ecosystem: "docker"
directory: "/"
schedule:

View File

@@ -13,12 +13,11 @@ Checklist:
* [ ] I've updated both the CLI and UI to expose my feature, or I plan to submit a second PR with them.
* [ ] Does this PR require documentation updates?
* [ ] I've updated documentation as required by this PR.
* [ ] Optional. My organization is added to USERS.md.
* [ ] I have signed off all my commits as required by [DCO](https://github.com/argoproj/argoproj/blob/master/community/CONTRIBUTING.md#legal)
* [ ] I have written unit and/or e2e tests for my change. PRs without these are unlikely to be merged.
* [ ] My build is green ([troubleshooting builds](https://argo-cd.readthedocs.io/en/latest/developer-guide/ci/)).
* [ ] My new feature complies with the [feature status](https://github.com/argoproj/argoproj/blob/master/community/feature-status.md) guidelines.
* [ ] I have added a brief description of why this PR is necessary and/or what this PR solves.
* [ ] Optional. My organization is added to USERS.md.
* [ ] Optional. For bug fixes, I've indicated what older releases this fix should be cherry-picked into (this may or may not happen depending on risk/complexity).
<!-- Please see [Contribution FAQs](https://argo-cd.readthedocs.io/en/latest/developer-guide/faq/) if you have questions about your pull-request. -->

View File

@@ -6,10 +6,9 @@
| codeql.yaml | CodeQL analysis |
| image-reuse.yaml | Build, push, and Sign container images |
| image.yaml | Build container image for PR's & publish for push events |
| init-release.yaml | Build manifests and version then create a PR for release branch|
| pr-title-check.yaml| Lint PR for semantic information |
| init-release.yaml | Build manifests and version then create a PR for release branch|
| release.yaml | Build images, cli-binaries, provenances, and post actions |
| scorecard.yaml | Generate scorecard for supply-chain security |
| update-snyk.yaml | Scheduled snyk reports |
# Reusable workflows

View File

@@ -1,5 +1,5 @@
name: Integration tests
on:
on:
push:
branches:
- 'master'
@@ -13,7 +13,7 @@ on:
env:
# Golang version to use across CI steps
GOLANG_VERSION: '1.22'
GOLANG_VERSION: '1.21'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -23,65 +23,36 @@ permissions:
contents: read
jobs:
changes:
runs-on: ubuntu-latest
outputs:
backend: ${{ steps.filter.outputs.backend_any_changed }}
frontend: ${{ steps.filter.outputs.frontend_any_changed }}
docs: ${{ steps.filter.outputs.docs_any_changed }}
steps:
- uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- uses: tj-actions/changed-files@cc3bbb0c526f8ee1d282f8c5f9f4e50745a5b457 # v44.5.4
id: filter
with:
# Any file which is not under docs/, ui/ or is not a markdown file is counted as a backend file
files_yaml: |
backend:
- '!ui/**'
- '!**.md'
- '!**/*.md'
- '!docs/**'
frontend:
- 'ui/**'
- Dockerfile
docs:
- 'docs/**'
check-go:
name: Ensure Go modules synchronicity
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-22.04
needs:
- changes
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup Golang
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Download all Go modules
run: |
go mod download
- name: Check for tidiness of go.mod and go.sum
- name: Check for tidyness of go.mod and go.sum
run: |
go mod tidy
git diff --exit-code -- .
build-go:
name: Build & cache Go code
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-22.04
needs:
- changes
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup Golang
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Restore go build cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -96,42 +67,37 @@ jobs:
contents: read # for actions/checkout to fetch code
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
name: Lint Go code
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-22.04
needs:
- changes
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup Golang
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Run golangci-lint
uses: golangci/golangci-lint-action@a4f60bb28d35aeee14e6880718e0c85ff1882e64 # v6.0.1
uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0
with:
version: v1.58.2
args: --verbose
version: v1.54.0
args: --enable gofmt --timeout 10m --exclude SA5011 --verbose --max-issues-per-linter 0 --max-same-issues 0
test-go:
name: Run unit tests for Go packages
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-22.04
needs:
- build-go
- changes
env:
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:
- name: Create checkout directory
run: mkdir -p ~/go/src/github.com/argoproj
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Create symlink in GOPATH
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
- name: Setup Golang
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Install required packages
@@ -151,7 +117,7 @@ jobs:
run: |
echo "/usr/local/bin" >> $GITHUB_PATH
- name: Restore go build cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -171,31 +137,34 @@ jobs:
go mod download
- name: Run all unit tests
run: make test-local
- name: Generate code coverage artifacts
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: code-coverage
path: coverage.out
- name: Generate test results artifacts
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: test-results
path: test-results
path: test-results/
test-go-race:
name: Run unit tests with -race for Go packages
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-22.04
needs:
- build-go
- changes
env:
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:
- name: Create checkout directory
run: mkdir -p ~/go/src/github.com/argoproj
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Create symlink in GOPATH
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
- name: Setup Golang
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Install required packages
@@ -215,7 +184,7 @@ jobs:
run: |
echo "/usr/local/bin" >> $GITHUB_PATH
- name: Restore go build cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -236,22 +205,19 @@ jobs:
- name: Run all unit tests
run: make test-race-local
- name: Generate test results artifacts
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: race-results
path: test-results/
codegen:
name: Check changes to generated code
if: ${{ needs.changes.outputs.backend == 'true' || needs.changes.outputs.docs == 'true'}}
runs-on: ubuntu-22.04
needs:
- changes
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup Golang
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Create symlink in GOPATH
@@ -294,21 +260,17 @@ jobs:
build-ui:
name: Build, test & lint UI code
# We run UI logic for backend changes so that we have a complete set of coverage documents to send to codecov.
if: ${{ needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true' }}
runs-on: ubuntu-22.04
needs:
- changes
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup NodeJS
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: '21.6.1'
node-version: '20.7.0'
- name: Restore node dependency cache
id: cache-dependencies
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
with:
path: ui/node_modules
key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }}
@@ -330,78 +292,78 @@ jobs:
analyze:
name: Process & analyze test artifacts
if: ${{ needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true' }}
runs-on: ubuntu-22.04
needs:
- test-go
- build-ui
- changes
- test-e2e
env:
sonar_secret: ${{ secrets.SONAR_TOKEN }}
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
with:
fetch-depth: 0
- name: Restore node dependency cache
id: cache-dependencies
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
with:
path: ui/node_modules
key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }}
- name: Remove other node_modules directory
run: |
rm -rf ui/node_modules/argo-ui/node_modules
- name: Get e2e code coverage
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
- name: Create test-results directory
run: |
mkdir -p test-results
- name: Get code coverage artifiact
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
with:
name: e2e-code-coverage
path: e2e-code-coverage
- name: Get unit test code coverage
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
name: code-coverage
- name: Get test result artifact
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
with:
name: test-results
path: test-results
- name: combine-go-coverage
# We generate coverage reports for all Argo CD components, but only the applicationset-controller report
# contains coverage data. The other components currently don't shut down gracefully, so no coverage data is
# produced. Once those components are fixed, we can add references to their coverage output directories.
run: |
go tool covdata percent -i=test-results,e2e-code-coverage/applicationset-controller -o test-results/full-coverage.out
- name: Upload code coverage information to codecov.io
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4
with:
file: test-results/full-coverage.out
fail_ci_if_error: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
file: coverage.out
- name: Perform static code analysis using SonarCloud
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
uses: SonarSource/sonarqube-scan-action@540792c588b5c2740ad2bb4667db5cd46ae678f2 # v2.2
SCANNER_VERSION: 4.2.0.1873
SCANNER_PATH: /tmp/cache/scanner
OS: linux
run: |
# We do not use the provided action, because it does contain an old
# version of the scanner, and also takes time to build.
set -e
mkdir -p ${SCANNER_PATH}
export SONAR_USER_HOME=${SCANNER_PATH}/.sonar
if [[ ! -x "${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner" ]]; then
curl -Ol https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${SCANNER_VERSION}-${OS}.zip
unzip -qq -o sonar-scanner-cli-${SCANNER_VERSION}-${OS}.zip -d ${SCANNER_PATH}
fi
chmod +x ${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner
chmod +x ${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/jre/bin/java
# Explicitly set NODE_MODULES
export NODE_MODULES=${PWD}/ui/node_modules
export NODE_PATH=${PWD}/ui/node_modules
${SCANNER_PATH}/sonar-scanner-${SCANNER_VERSION}-${OS}/bin/sonar-scanner
if: env.sonar_secret != ''
test-e2e:
name: Run end-to-end tests
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
k3s:
- version: v1.29.1
# We designate the latest version because we only collect code coverage for that version.
latest: true
- version: v1.28.6
latest: false
- version: v1.27.10
latest: false
- version: v1.26.13
latest: false
needs:
k3s-version: [v1.28.2, v1.27.6, v1.26.9, v1.25.14]
needs:
- build-go
- changes
env:
GOPATH: /home/runner/go
ARGOCD_FAKE_IN_CLUSTER: "true"
@@ -411,15 +373,15 @@ jobs:
ARGOCD_E2E_K3S: "true"
ARGOCD_IN_CI: "true"
ARGOCD_E2E_APISERVER_PORT: "8088"
ARGOCD_APPLICATION_NAMESPACES: "argocd-e2e-external,argocd-e2e-external-2"
ARGOCD_APPLICATION_NAMESPACES: "argocd-e2e-external"
ARGOCD_SERVER: "127.0.0.1:8088"
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:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Setup Golang
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: GH actions workaround - Kill XSP4 process
@@ -427,7 +389,7 @@ jobs:
sudo pkill mono || true
- name: Install K3S
env:
INSTALL_K3S_VERSION: ${{ matrix.k3s.version }}+k3s1
INSTALL_K3S_VERSION: ${{ matrix.k3s-version }}+k3s1
run: |
set -x
curl -sfL https://get.k3s.io | sh -
@@ -438,7 +400,7 @@ jobs:
sudo chmod go-r $HOME/.kube/config
kubectl version
- name: Restore go build cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -464,7 +426,7 @@ jobs:
git config --global user.email "john.doe@example.com"
- name: Pull Docker image required for tests
run: |
docker pull ghcr.io/dexidp/dex:v2.38.0
docker pull ghcr.io/dexidp/dex:v2.37.0
docker pull argoproj/argo-cd-ci-builder:v1.0.0
docker pull redis:7.0.15-alpine
- name: Create target directory for binaries in the build-process
@@ -479,7 +441,7 @@ jobs:
# port 8080 which is not visible in netstat -tulpen, but still there
# with a HTTP listener. We have API server listening on port 8088
# instead.
make start-e2e-local COVERAGE_ENABLED=true 2>&1 | sed -r "s/[[:cntrl:]]\[[0-9]{1,3}m//g" > /tmp/e2e-server.log &
make start-e2e-local 2>&1 | sed -r "s/[[:cntrl:]]\[[0-9]{1,3}m//g" > /tmp/e2e-server.log &
count=1
until curl -f http://127.0.0.1:8088/healthz; do
sleep 10;
@@ -493,40 +455,9 @@ jobs:
run: |
set -x
make test-e2e-local
goreman run stop-all || echo "goreman trouble"
sleep 30
- name: Upload e2e coverage report
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: e2e-code-coverage
path: /tmp/coverage
if: ${{ matrix.k3s.latest }}
- name: Upload e2e-server logs
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: e2e-server-k8s${{ matrix.k3s.version }}.log
name: e2e-server-k8s${{ matrix.k3s-version }}.log
path: /tmp/e2e-server.log
if: ${{ failure() }}
# workaround for status checks -- check this one job instead of each individual E2E job in the matrix
# this allows us to skip the entire matrix when it doesn't need to run while still having accurate status checks
# see:
# https://github.com/argoproj/argo-workflows/pull/12006
# https://github.com/orgs/community/discussions/9141#discussioncomment-2296809
# https://github.com/orgs/community/discussions/26822#discussioncomment-3305794
test-e2e-composite-result:
name: E2E Tests - Composite result
if: ${{ always() }}
needs:
- test-e2e
- changes
runs-on: ubuntu-22.04
steps:
- run: |
result="${{ needs.test-e2e.result }}"
# mark as successful even if skipped
if [[ $result == "success" || $result == "skipped" ]]; then
exit 0
else
exit 1
fi

View File

@@ -27,19 +27,14 @@ jobs:
# CodeQL runs on ubuntu-latest and windows-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
# Use correct go version. https://github.com/github/codeql-action/issues/1842#issuecomment-1704398087
- name: Setup Golang
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with:
go-version-file: go.mod
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@8fcfedf57053e09257688fce7a0beeb18b1b9ae3 # v2.17.2
uses: github/codeql-action/init@8aff97f12c99086bdb92ff62ae06dbbcdf07941b # v2.1.33
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
@@ -47,7 +42,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@8fcfedf57053e09257688fce7a0beeb18b1b9ae3 # v2.17.2
uses: github/codeql-action/autobuild@8aff97f12c99086bdb92ff62ae06dbbcdf07941b # v2.1.33
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -61,4 +56,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@8fcfedf57053e09257688fce7a0beeb18b1b9ae3 # v2.17.2
uses: github/codeql-action/analyze@8aff97f12c99086bdb92ff62ae06dbbcdf07941b # v2.1.33

View File

@@ -58,25 +58,25 @@ jobs:
image-digest: ${{ steps.image.outputs.digest }}
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
if: ${{ github.ref_type == 'tag'}}
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
if: ${{ github.ref_type != 'tag'}}
- name: Setup Golang
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
go-version: ${{ inputs.go-version }}
- name: Install cosign
uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0
uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 # v3.4.0
- uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
- uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
- uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
- name: Setup tags for container image as a CSV type
@@ -104,7 +104,7 @@ jobs:
echo 'EOF' >> $GITHUB_ENV
- name: Login to Quay.io
uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
with:
registry: quay.io
username: ${{ secrets.quay_username }}
@@ -112,7 +112,7 @@ jobs:
if: ${{ inputs.quay_image_name && inputs.push }}
- name: Login to GitHub Container Registry
uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
with:
registry: ghcr.io
username: ${{ secrets.ghcr_username }}
@@ -120,7 +120,7 @@ jobs:
if: ${{ inputs.ghcr_image_name && inputs.push }}
- name: Login to dockerhub Container Registry
uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
with:
username: ${{ secrets.docker_username }}
password: ${{ secrets.docker_password }}
@@ -134,7 +134,7 @@ jobs:
echo "GIT_TREE_STATE=$(if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)" >> $GITHUB_ENV
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
uses: jlumbroso/free-disk-space@4d9e71b726748f254fe64fa44d273194bd18ec91
with:
large-packages: false
docker-images: false
@@ -143,7 +143,7 @@ jobs:
- name: Build and push container image
id: image
uses: docker/build-push-action@31159d49c0d4756269a0940a750801a1ea5d7003 #v6.1.0
uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 #v4.1.1
with:
context: .
platforms: ${{ inputs.platforms }}

View File

@@ -25,7 +25,7 @@ jobs:
image-tag: ${{ steps.image.outputs.tag}}
platforms: ${{ steps.platforms.outputs.platforms }}
steps:
- uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Set image tag for ghcr
run: echo "tag=$(cat ./VERSION)-${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
@@ -52,7 +52,7 @@ jobs:
uses: ./.github/workflows/image-reuse.yaml
with:
# Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations)
go-version: 1.22
go-version: 1.21
platforms: ${{ needs.set-vars.outputs.platforms }}
push: false
@@ -68,7 +68,7 @@ jobs:
quay_image_name: quay.io/argoproj/argocd:latest
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)
go-version: 1.22
go-version: 1.21
platforms: ${{ needs.set-vars.outputs.platforms }}
push: true
secrets:
@@ -86,7 +86,7 @@ jobs:
packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.10.0
with:
image: ghcr.io/argoproj/argo-cd/argocd
digest: ${{ needs.build-and-publish.outputs.image-digest }}
@@ -104,7 +104,7 @@ jobs:
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- run: git clone "https://$TOKEN@github.com/argoproj/argoproj-deployments"
env:
TOKEN: ${{ secrets.TOKEN }}

View File

@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
@@ -64,7 +64,7 @@ jobs:
git stash pop
- name: Create pull request
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 # v5.0.2
with:
commit-message: "Bump version to ${{ inputs.TARGET_VERSION }}"
title: "Bump version to ${{ inputs.TARGET_VERSION }} on ${{ inputs.TARGET_BRANCH }} branch"

View File

@@ -23,7 +23,7 @@ jobs:
name: Validate PR Title
runs-on: ubuntu-latest
steps:
- uses: thehanimo/pr-title-checker@1d8cd483a2b73118406a187f54dca8a9415f1375 # v1.4.2
- uses: thehanimo/pr-title-checker@0cf5902181e78341bb97bb06646396e5bd354b3f # v1.4.0
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
configuration_path: ".github/pr-title-checker-config.json"

View File

@@ -10,7 +10,7 @@ on:
permissions: {}
env:
GOLANG_VERSION: '1.22' # Note: go-version must also be set in job argocd-image.with.go-version
GOLANG_VERSION: '1.21' # Note: go-version must also be set in job argocd-image.with.go-version
jobs:
argocd-image:
@@ -23,7 +23,7 @@ jobs:
with:
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)
go-version: 1.22
go-version: 1.21
platforms: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
push: true
secrets:
@@ -38,7 +38,7 @@ jobs:
packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
if: github.repository == 'argoproj/argo-cd'
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.10.0
with:
image: quay.io/argoproj/argocd
digest: ${{ needs.argocd-image.outputs.image-digest }}
@@ -59,7 +59,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
@@ -77,7 +77,7 @@ jobs:
fi
- name: Setup Golang
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
@@ -87,16 +87,8 @@ jobs:
echo "KUBECTL_VERSION=$(go list -m k8s.io/client-go | head -n 1 | rev | cut -d' ' -f1 | rev)" >> $GITHUB_ENV
echo "GIT_TREE_STATE=$(if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)" >> $GITHUB_ENV
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
with:
large-packages: false
docker-images: false
swap-storage: false
tool-cache: false
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0
id: run-goreleaser
with:
version: latest
@@ -128,7 +120,7 @@ jobs:
contents: write # Needed for release uploads
if: github.repository == 'argoproj/argo-cd'
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0
with:
base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
provenance-name: "argocd-cli.intoto.jsonl"
@@ -147,13 +139,13 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Golang
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
go-version: ${{ env.GOLANG_VERSION }}
@@ -197,7 +189,7 @@ jobs:
echo "hashes=$(sha256sum /tmp/sbom.tar.gz | base64 -w0)" >> "$GITHUB_OUTPUT"
- name: Upload SBOM
uses: softprops/action-gh-release@a74c6b72af54cfa997e81df42d94703d6313a2d0 # v2.0.6
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -212,7 +204,7 @@ jobs:
contents: write # Needed for release uploads
if: github.repository == 'argoproj/argo-cd'
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0
with:
base64-subjects: "${{ needs.generate-sbom.outputs.hashes }}"
provenance-name: "argocd-sbom.intoto.jsonl"
@@ -230,7 +222,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
@@ -273,13 +265,11 @@ jobs:
set -xue
SOURCE_TAG=${{ github.ref_name }}
VERSION_REF="${SOURCE_TAG#*v}"
COMMIT_HASH=$(git rev-parse HEAD)
if echo "$VERSION_REF" | grep -E -- '^[0-9]+\.[0-9]+\.0-rc1';then
VERSION=$(awk 'BEGIN {FS=OFS="."} {$2++; print}' <<< "${VERSION_REF%-rc1}")
echo "Updating VERSION to: $VERSION"
echo "UPDATE_VERSION=true" >> $GITHUB_ENV
echo "NEW_VERSION=$VERSION" >> $GITHUB_ENV
echo "COMMIT_HASH=$COMMIT_HASH" >> $GITHUB_ENV
else
echo "Not updating VERSION"
echo "UPDATE_VERSION=false" >> $GITHUB_ENV
@@ -288,14 +278,10 @@ jobs:
- name: Update VERSION on master branch
run: |
echo ${{ env.NEW_VERSION }} > VERSION
# Replace the 'project-release: vX.X.X-rcX' line in SECURITY-INSIGHTS.yml
sed -i "s/project-release: v.*$/project-release: v${{ env.NEW_VERSION }}/" SECURITY-INSIGHTS.yml
# Update the 'commit-hash: XXXXXXX' line in SECURITY-INSIGHTS.yml
sed -i "s/commit-hash: .*/commit-hash: ${{ env.COMMIT_HASH }}/" SECURITY-INSIGHTS.yml
if: ${{ env.UPDATE_VERSION == 'true' }}
- name: Create PR to update VERSION on master branch
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 # v5.0.2
with:
commit-message: Bump version in master
title: "chore: Bump version in master"

View File

@@ -30,12 +30,12 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031 # v2.2.0
with:
results_file: results.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
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: SARIF file
path: results.sarif
@@ -62,6 +62,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@8fcfedf57053e09257688fce7a0beeb18b1b9ae3 # v2.17.2
uses: github/codeql-action/upload-sarif@3ebbd71c74ef574dbc558c82f70e52732c8b44fe # v2.2.1
with:
sarif_file: results.sarif

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Build reports

1
.gitignore vendored
View File

@@ -19,7 +19,6 @@ node_modules/
./test/cmp/*.sock
.envrc.remote
.*.swp
rerunreport.txt
# ignore built binaries
cmd/argocd/argocd

2
.gitpod.Dockerfile vendored
View File

@@ -1,4 +1,4 @@
FROM gitpod/workspace-full@sha256:8dd34e72ae5b9e6f60d267dd6287befc2cf5ad1a11c64e9d93daa60c952a2154
FROM gitpod/workspace-full@sha256:511cecde4dc129ca9eb4cc4c479d61f95e5485ebe320a07f5b902f11899956a3
USER root

View File

@@ -1,41 +0,0 @@
issues:
exclude:
- SA1019
- SA5011
max-issues-per-linter: 0
max-same-issues: 0
linters:
enable:
- errcheck
- errorlint
- gocritic
- gofumpt
- goimports
- gosimple
- govet
- ineffassign
- misspell
- staticcheck
- testifylint
- unused
- whitespace
linters-settings:
gocritic:
disabled-checks:
- appendAssign
- assignOp # Keep it disabled for readability
- badCond
- commentFormatting
- exitAfterDefer
- ifElseChain
- mapKey
- singleCaseSwitch
- typeSwitchVar
goimports:
local-prefixes: github.com/argoproj/argo-cd/v2
testifylint:
enable-all: true
disable:
- go-require
run:
timeout: 50m

View File

@@ -1,5 +1,3 @@
version: 2
project_name: argocd
before:

View File

@@ -2,13 +2,8 @@
** @argoproj/argocd-approvers
# Docs
/docs/** @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
/USERS.md @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
/README.md @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
/mkdocs.yml @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
/docs/** @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
# CI
/.codecov.yml @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
/.github/** @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
/.goreleaser.yaml @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
/sonar-project.properties @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
/.github/** @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
/.goreleaser.yaml @argoproj/argocd-approvers @argoproj/argocd-approvers-ci

View File

@@ -1 +0,0 @@
Please refer to [the Contribution Guide](https://argo-cd.readthedocs.io/en/latest/developer-guide/code-contributions/)

View File

@@ -1,10 +1,10 @@
ARG BASE_IMAGE=docker.io/library/ubuntu:24.04@sha256:3f85b7caad41a95462cf5b787d8a04604c8262cdcdf9a472b8c52ef83375fe15
ARG BASE_IMAGE=docker.io/library/ubuntu:22.04@sha256:0bced47fffa3361afa981854fcabcd4577cd43cebbb808cea2b1f33a3dd7f508
####################################################################################################
# Builder 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
####################################################################################################
FROM docker.io/library/golang:1.22.4@sha256:c2010b9c2342431a24a2e64e33d9eb2e484af49e72c820e200d332d214d5e61f AS builder
FROM docker.io/library/golang:1.21.10@sha256:16438a8e66c0c984f732e815ee5b7d715b8e33e81bac6d6a3750b1067744e7ca AS builder
RUN echo 'deb http://archive.debian.org/debian buster-backports main' >> /etc/apt/sources.list
@@ -28,7 +28,7 @@ WORKDIR /tmp
COPY hack/install.sh hack/tool-versions.sh ./
COPY hack/installers installers
RUN ./install.sh helm && \
RUN ./install.sh helm-linux && \
INSTALL_PATH=/usr/local/bin ./install.sh kustomize
####################################################################################################
@@ -51,7 +51,7 @@ RUN groupadd -g $ARGOCD_USER_ID argocd && \
apt-get update && \
apt-get dist-upgrade -y && \
apt-get install -y \
git git-lfs tini gpg tzdata connect-proxy && \
git git-lfs tini gpg tzdata && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
@@ -83,7 +83,7 @@ WORKDIR /home/argocd
####################################################################################################
# Argo CD UI stage
####################################################################################################
FROM --platform=$BUILDPLATFORM docker.io/library/node:22.3.0@sha256:5e4044ff6001d06e7748e35bfa4f80c73cf5f5a7360a1b782995e038a01b0585 AS argocd-ui
FROM --platform=$BUILDPLATFORM docker.io/library/node:20.6.1@sha256:14bd39208dbc0eb171cbfb26ccb9ac09fa1b2eba04ccd528ab5d12983fd9ee24 AS argocd-ui
WORKDIR /src
COPY ["ui/package.json", "ui/yarn.lock", "./"]
@@ -101,7 +101,7 @@ RUN HOST_ARCH=$TARGETARCH NODE_ENV='production' NODE_ONLINE_ENV='online' NODE_OP
####################################################################################################
# Argo CD Build stage which performs the actual build of Argo CD binaries
####################################################################################################
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.22.4@sha256:c2010b9c2342431a24a2e64e33d9eb2e484af49e72c820e200d332d214d5e61f AS argocd-build
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.21.10@sha256:16438a8e66c0c984f732e815ee5b7d715b8e33e81bac6d6a3750b1067744e7ca AS argocd-build
WORKDIR /go/src/github.com/argoproj/argo-cd

138
Makefile
View File

@@ -3,7 +3,6 @@ CURRENT_DIR=$(shell pwd)
DIST_DIR=${CURRENT_DIR}/dist
CLI_NAME=argocd
BIN_NAME=argocd
CGO_FLAG=0
GEN_RESOURCES_CLI_NAME=argocd-resources-gen
@@ -23,21 +22,14 @@ KUBECTL_VERSION=$(shell go list -m k8s.io/client-go | head -n 1 | rev | cut -d'
GOPATH?=$(shell if test -x `which go`; then go env GOPATH; else echo "$(HOME)/go"; fi)
GOCACHE?=$(HOME)/.cache/go-build
# Docker command to use
DOCKER?=docker
ifeq ($(DOCKER),podman)
PODMAN_ARGS=--userns keep-id
else
PODMAN_ARGS=
endif
DOCKER_SRCDIR?=$(GOPATH)/src
DOCKER_WORKDIR?=/go/src/github.com/argoproj/argo-cd
ARGOCD_PROCFILE?=Procfile
# pointing to python 3.7 to match https://github.com/argoproj/argo-cd/blob/master/.readthedocs.yml
MKDOCS_DOCKER_IMAGE?=python:3.7-alpine
# Strict mode has been disabled in latest versions of mkdocs-material.
# Thus pointing to the older image of mkdocs-material matching the version used by argo-cd.
MKDOCS_DOCKER_IMAGE?=squidfunk/mkdocs-material:4.1.1
MKDOCS_RUN_ARGS?=
# Configuration for building argocd-test-tools image
@@ -57,7 +49,7 @@ ARGOCD_E2E_DEX_PORT?=5556
ARGOCD_E2E_YARN_HOST?=localhost
ARGOCD_E2E_DISABLE_AUTH?=
ARGOCD_E2E_TEST_TIMEOUT?=90m
ARGOCD_E2E_TEST_TIMEOUT?=45m
ARGOCD_IN_CI?=false
ARGOCD_TEST_E2E?=true
@@ -84,7 +76,7 @@ SUDO?=
# Runs any command in the argocd-test-utils container in server mode
# Server mode container will start with uid 0 and drop privileges during runtime
define run-in-test-server
$(SUDO) $(DOCKER) run --rm -it \
$(SUDO) docker run --rm -it \
--name argocd-test-server \
-u $(CONTAINER_UID):$(CONTAINER_GID) \
-e USER_ID=$(CONTAINER_UID) \
@@ -109,14 +101,13 @@ define run-in-test-server
-p ${ARGOCD_E2E_APISERVER_PORT}:8080 \
-p 4000:4000 \
-p 5000:5000 \
$(PODMAN_ARGS) \
$(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG) \
bash -c "$(1)"
endef
# Runs any command in the argocd-test-utils container in client mode
define run-in-test-client
$(SUDO) $(DOCKER) run --rm -it \
$(SUDO) docker run --rm -it \
--name argocd-test-client \
-u $(CONTAINER_UID):$(CONTAINER_GID) \
-e HOME=/home/user \
@@ -131,14 +122,13 @@ define run-in-test-client
-v ${HOME}/.kube:/home/user/.kube${VOLUME_MOUNT} \
-v /tmp:/tmp${VOLUME_MOUNT} \
-w ${DOCKER_WORKDIR} \
$(PODMAN_ARGS) \
$(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG) \
bash -c "$(1)"
endef
#
define exec-in-test-server
$(SUDO) $(DOCKER) exec -it -u $(CONTAINER_UID):$(CONTAINER_GID) -e ARGOCD_E2E_RECORD=$(ARGOCD_E2E_RECORD) -e ARGOCD_E2E_K3S=$(ARGOCD_E2E_K3S) argocd-test-server $(1)
$(SUDO) docker exec -it -u $(CONTAINER_UID):$(CONTAINER_GID) -e ARGOCD_E2E_RECORD=$(ARGOCD_E2E_RECORD) -e ARGOCD_E2E_K3S=$(ARGOCD_E2E_K3S) argocd-test-server $(1)
endef
PATH:=$(PATH):$(PWD)/hack
@@ -153,13 +143,6 @@ DEV_IMAGE?=false
ARGOCD_GPG_ENABLED?=true
ARGOCD_E2E_APISERVER_PORT?=8080
ifeq (${COVERAGE_ENABLED}, true)
# We use this in the cli-local target to enable code coverage for e2e tests.
COVERAGE_FLAG=-cover
else
COVERAGE_FLAG=
endif
override LDFLAGS += \
-X ${PACKAGE}.version=${VERSION} \
-X ${PACKAGE}.buildDate=${BUILD_DATE} \
@@ -192,21 +175,29 @@ endif
.PHONY: all
all: cli image
# We have some legacy requirements for being checked out within $GOPATH.
# The ensure-gopath target can be used as dependency to ensure we are running
# within these boundaries.
.PHONY: ensure-gopath
ensure-gopath:
ifneq ("$(PWD)","$(LEGACY_PATH)")
@echo "Due to legacy requirements for codegen, repository needs to be checked out within \$$GOPATH"
@echo "Location of this repo should be '$(LEGACY_PATH)' but is '$(PWD)'"
@exit 1
endif
.PHONY: gogen
gogen:
gogen: ensure-gopath
export GO111MODULE=off
go generate ./...
go generate ./util/argo/...
.PHONY: protogen
protogen: mod-vendor-local protogen-fast
.PHONY: protogen-fast
protogen-fast:
protogen: ensure-gopath mod-vendor-local
export GO111MODULE=off
./hack/generate-proto.sh
.PHONY: openapigen
openapigen:
openapigen: ensure-gopath
export GO111MODULE=off
./hack/update-openapi.sh
@@ -221,22 +212,19 @@ notification-docs:
.PHONY: clientgen
clientgen:
clientgen: ensure-gopath
export GO111MODULE=off
./hack/update-codegen.sh
.PHONY: clidocsgen
clidocsgen:
clidocsgen: ensure-gopath
go run tools/cmd-docs/main.go
.PHONY: codegen-local
codegen-local: mod-vendor-local gogen protogen clientgen openapigen clidocsgen manifests-local notification-docs notification-catalog
codegen-local: ensure-gopath mod-vendor-local gogen protogen clientgen openapigen clidocsgen manifests-local notification-docs notification-catalog
rm -rf vendor/
.PHONY: codegen-local-fast
codegen-local-fast: gogen protogen-fast clientgen openapigen clidocsgen manifests-local notification-docs notification-catalog
.PHONY: codegen
codegen: test-tools-image
$(call run-in-test-client,make codegen-local)
@@ -247,11 +235,11 @@ cli: test-tools-image
.PHONY: cli-local
cli-local: clean-debug
CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build $(COVERAGE_FLAG) -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${CLI_NAME} ./cmd
CGO_ENABLED=0 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${CLI_NAME} ./cmd
.PHONY: gen-resources-cli-local
gen-resources-cli-local: clean-debug
CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${GEN_RESOURCES_CLI_NAME} ./hack/gen-resources/cmd
CGO_ENABLED=0 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${GEN_RESOURCES_CLI_NAME} ./hack/gen-resources/cmd
.PHONY: release-cli
release-cli: clean-debug build-ui
@@ -266,8 +254,8 @@ release-cli: clean-debug build-ui
.PHONY: test-tools-image
test-tools-image:
ifndef SKIP_TEST_TOOLS_IMAGE
$(SUDO) $(DOCKER) build --build-arg UID=$(CONTAINER_UID) -t $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE) -f test/container/Dockerfile .
$(SUDO) $(DOCKER) tag $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE) $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG)
$(SUDO) docker build --build-arg UID=$(CONTAINER_UID) -t $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE) -f test/container/Dockerfile .
$(SUDO) docker tag $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE) $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG)
endif
.PHONY: manifests-local
@@ -281,25 +269,25 @@ manifests: test-tools-image
# consolidated binary for cli, util, server, repo-server, controller
.PHONY: argocd-all
argocd-all: clean-debug
CGO_ENABLED=${CGO_FLAG} GOOS=${GOOS} GOARCH=${GOARCH} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${BIN_NAME} ./cmd
CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${BIN_NAME} ./cmd
.PHONY: server
server: clean-debug
CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-server ./cmd
CGO_ENABLED=0 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-server ./cmd
.PHONY: repo-server
repo-server:
CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-repo-server ./cmd
CGO_ENABLED=0 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-repo-server ./cmd
.PHONY: controller
controller:
CGO_ENABLED=${CGO_FLAG} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-application-controller ./cmd
CGO_ENABLED=0 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-application-controller ./cmd
.PHONY: build-ui
build-ui:
DOCKER_BUILDKIT=1 $(DOCKER) build -t argocd-ui --platform=$(TARGET_ARCH) --target argocd-ui .
DOCKER_BUILDKIT=1 docker build -t argocd-ui --platform=$(TARGET_ARCH) --target argocd-ui .
find ./ui/dist -type f -not -name gitkeep -delete
$(DOCKER) run -v ${CURRENT_DIR}/ui/dist/app:/tmp/app --rm -t argocd-ui sh -c 'cp -r ./dist/app/* /tmp/app/'
docker run -v ${CURRENT_DIR}/ui/dist/app:/tmp/app --rm -t argocd-ui sh -c 'cp -r ./dist/app/* /tmp/app/'
.PHONY: image
ifeq ($(DEV_IMAGE), true)
@@ -308,29 +296,29 @@ ifeq ($(DEV_IMAGE), true)
# the dist directory is under .dockerignore.
IMAGE_TAG="dev-$(shell git describe --always --dirty)"
image: build-ui
DOCKER_BUILDKIT=1 $(DOCKER) build --platform=$(TARGET_ARCH) -t argocd-base --target argocd-base .
CGO_ENABLED=${CGO_FLAG} GOOS=linux GOARCH=amd64 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd ./cmd
DOCKER_BUILDKIT=1 docker build --platform=$(TARGET_ARCH) -t argocd-base --target argocd-base .
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd ./cmd
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-server
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-application-controller
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-repo-server
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-cmp-server
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-dex
cp Dockerfile.dev dist
DOCKER_BUILDKIT=1 $(DOCKER) build --platform=$(TARGET_ARCH) -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) -f dist/Dockerfile.dev dist
DOCKER_BUILDKIT=1 docker build --platform=$(TARGET_ARCH) -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) -f dist/Dockerfile.dev dist
else
image:
DOCKER_BUILDKIT=1 $(DOCKER) build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) --platform=$(TARGET_ARCH) .
DOCKER_BUILDKIT=1 docker build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) --platform=$(TARGET_ARCH) .
endif
@if [ "$(DOCKER_PUSH)" = "true" ] ; then $(DOCKER) push $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) ; fi
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) ; fi
.PHONY: armimage
armimage:
$(DOCKER) build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG)-arm .
docker build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG)-arm .
.PHONY: builder-image
builder-image:
$(DOCKER) build -t $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) --target builder .
@if [ "$(DOCKER_PUSH)" = "true" ] ; then $(DOCKER) push $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) ; fi
docker build -t $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) --target builder .
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) ; fi
.PHONY: mod-download
mod-download: test-tools-image
@@ -364,7 +352,7 @@ lint-local:
golangci-lint --version
# NOTE: If you get a "Killed" OOM message, try reducing the value of GOGC
# See https://github.com/golangci/golangci-lint#memory-usage-of-golangci-lint
GOGC=$(ARGOCD_LINT_GOGC) GOMAXPROCS=2 golangci-lint run --fix --verbose
GOGC=$(ARGOCD_LINT_GOGC) GOMAXPROCS=2 golangci-lint run --enable gofmt --fix --verbose --timeout 3000s --max-issues-per-linter 0 --max-same-issues 0
.PHONY: lint-ui
lint-ui: test-tools-image
@@ -398,9 +386,9 @@ test: test-tools-image
.PHONY: test-local
test-local:
if test "$(TEST_MODULE)" = ""; then \
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES=`go list ./... | grep -v 'test/e2e'` ./hack/test.sh -args -test.gocoverdir="$(PWD)/test-results"; \
./hack/test.sh -coverprofile=coverage.out `go list ./... | grep -v 'test/e2e'`; \
else \
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES="$(TEST_MODULE)" ./hack/test.sh -args -test.gocoverdir="$(PWD)/test-results" "$(TEST_MODULE)"; \
./hack/test.sh -coverprofile=coverage.out "$(TEST_MODULE)"; \
fi
.PHONY: test-race
@@ -412,9 +400,9 @@ test-race: test-tools-image
.PHONY: test-race-local
test-race-local:
if test "$(TEST_MODULE)" = ""; then \
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES=`go list ./... | grep -v 'test/e2e'` ./hack/test.sh -race -args -test.gocoverdir="$(PWD)/test-results"; \
./hack/test.sh -race -coverprofile=coverage.out `go list ./... | grep -v 'test/e2e'`; \
else \
DIST_DIR=${DIST_DIR} RERUN_FAILS=0 PACKAGES="$(TEST_MODULE)" ./hack/test.sh -race -args -test.gocoverdir="$(PWD)/test-results"; \
./hack/test.sh -race -coverprofile=coverage.out "$(TEST_MODULE)"; \
fi
# Run the E2E test suite. E2E test servers (see start-e2e target) must be
@@ -428,7 +416,7 @@ test-e2e:
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
export GO111MODULE=off
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"
ARGOCD_E2E_RECORD=${ARGOCD_E2E_RECORD} ARGOCD_GPG_ENABLED=true NO_PROXY=* ./hack/test.sh -timeout $(ARGOCD_E2E_TEST_TIMEOUT) -v ./test/e2e
# Spawns a shell in the test server container for debugging purposes
debug-test-server: test-tools-image
@@ -441,7 +429,7 @@ debug-test-client: test-tools-image
# Starts e2e server in a container
.PHONY: start-e2e
start-e2e: test-tools-image
$(DOCKER) version
docker version
mkdir -p ${GOCACHE}
$(call run-in-test-server,make ARGOCD_PROCFILE=test/container/Procfile start-e2e-local)
@@ -450,7 +438,6 @@ start-e2e: test-tools-image
start-e2e-local: mod-vendor-local dep-ui-local cli-local
kubectl create ns argocd-e2e || true
kubectl create ns argocd-e2e-external || true
kubectl create ns argocd-e2e-external-2 || true
kubectl config set-context --current --namespace=argocd-e2e
kustomize build test/manifests/base | kubectl apply -f -
kubectl apply -f https://raw.githubusercontent.com/open-cluster-management/api/a6845f2ebcb186ec26b832f60c988537a58f3859/cluster/v1alpha1/0000_04_clusters.open-cluster-management.io_placementdecisions.crd.yaml
@@ -459,12 +446,6 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
mkdir -p /tmp/argo-e2e/app/config/gpg/keys && chmod 0700 /tmp/argo-e2e/app/config/gpg/keys
mkdir -p /tmp/argo-e2e/app/config/gpg/source && chmod 0700 /tmp/argo-e2e/app/config/gpg/source
mkdir -p /tmp/argo-e2e/app/config/plugin && chmod 0700 /tmp/argo-e2e/app/config/plugin
# create folders to hold go coverage results for each component
mkdir -p /tmp/coverage/app-controller
mkdir -p /tmp/coverage/api-server
mkdir -p /tmp/coverage/repo-server
mkdir -p /tmp/coverage/applicationset-controller
mkdir -p /tmp/coverage/notification
# set paths for locally managed ssh known hosts and tls certs data
ARGOCD_SSH_DATA_PATH=/tmp/argo-e2e/app/config/ssh \
ARGOCD_TLS_DATA_PATH=/tmp/argo-e2e/app/config/tls \
@@ -477,12 +458,11 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
ARGOCD_ZJWT_FEATURE_FLAG=always \
ARGOCD_IN_CI=$(ARGOCD_IN_CI) \
BIN_MODE=$(ARGOCD_BIN_MODE) \
ARGOCD_APPLICATION_NAMESPACES=argocd-e2e-external,argocd-e2e-external-2 \
ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACES=argocd-e2e-external,argocd-e2e-external-2 \
ARGOCD_APPLICATION_NAMESPACES=argocd-e2e-external \
ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACES=argocd-e2e-external \
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 \
goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START}
ls -lrt /tmp/coverage
# Cleans VSCode debug.test files from sub-dirs to prevent them from being included in by golang embed
.PHONY: clean-debug
@@ -495,7 +475,7 @@ clean: clean-debug
.PHONY: start
start: test-tools-image
$(DOCKER) version
docker version
$(call run-in-test-server,make ARGOCD_PROCFILE=test/container/Procfile start-local ARGOCD_START=${ARGOCD_START})
# Starts a local instance of ArgoCD
@@ -508,11 +488,9 @@ start-local: mod-vendor-local dep-ui-local cli-local
mkdir -p /tmp/argocd-local
mkdir -p /tmp/argocd-local/gpg/keys && chmod 0700 /tmp/argocd-local/gpg/keys
mkdir -p /tmp/argocd-local/gpg/source
REDIS_PASSWORD=$(shell kubectl get secret argocd-redis -o jsonpath='{.data.auth}' | base64 -d) \
ARGOCD_ZJWT_FEATURE_FLAG=always \
ARGOCD_IN_CI=false \
ARGOCD_GPG_ENABLED=$(ARGOCD_GPG_ENABLED) \
BIN_MODE=$(ARGOCD_BIN_MODE) \
ARGOCD_E2E_TEST=false \
ARGOCD_APPLICATION_NAMESPACES=$(ARGOCD_APPLICATION_NAMESPACES) \
goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START}
@@ -546,7 +524,7 @@ build-docs-local:
.PHONY: build-docs
build-docs:
$(DOCKER) run ${MKDOCS_RUN_ARGS} --rm -it -v ${CURRENT_DIR}:/docs -w /docs --entrypoint "" ${MKDOCS_DOCKER_IMAGE} sh -c 'pip install -r docs/requirements.txt; mkdocs build'
docker run ${MKDOCS_RUN_ARGS} --rm -it -v ${CURRENT_DIR}:/docs --entrypoint "" ${MKDOCS_DOCKER_IMAGE} sh -c 'pip install -r docs/requirements.txt; mkdocs build'
.PHONY: serve-docs-local
serve-docs-local:
@@ -554,7 +532,8 @@ serve-docs-local:
.PHONY: serve-docs
serve-docs:
$(DOCKER) run ${MKDOCS_RUN_ARGS} --rm -it -p 8000:8000 -v ${CURRENT_DIR}:/docs -w /docs --entrypoint "" ${MKDOCS_DOCKER_IMAGE} sh -c 'pip install -r docs/requirements.txt; mkdocs serve -a $$(ip route get 1 | awk '\''{print $$7}'\''):8000'
docker run ${MKDOCS_RUN_ARGS} --rm -it -p 8000:8000 -v ${CURRENT_DIR}/site:/site -w /site --entrypoint "" ${MKDOCS_DOCKER_IMAGE} python3 -m http.server --bind 0.0.0.0 8000
# Verify that kubectl can connect to your K8s cluster from Docker
.PHONY: verify-kube-connect
@@ -577,8 +556,7 @@ install-tools-local: install-test-tools-local install-codegen-tools-local instal
.PHONY: install-test-tools-local
install-test-tools-local:
./hack/install.sh kustomize
./hack/install.sh helm
./hack/install.sh gotestsum
./hack/install.sh helm-linux
# Installs all tools required for running codegen (Linux packages)
.PHONY: install-codegen-tools-local
@@ -606,7 +584,7 @@ list:
.PHONY: applicationset-controller
applicationset-controller:
GODEBUG="tarinsecurepath=0,zipinsecurepath=0" CGO_ENABLED=${CGO_FLAG} go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-applicationset-controller ./cmd
GODEBUG="tarinsecurepath=0,zipinsecurepath=0" CGO_ENABLED=0 go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argocd-applicationset-controller ./cmd
.PHONY: checksums
checksums:

2
OWNERS
View File

@@ -5,7 +5,6 @@ owners:
approvers:
- alexec
- alexmt
- gdsoumya
- jannfis
- jessesuen
- jgwest
@@ -31,3 +30,4 @@ reviewers:
- zachaller
- 34fathombelow
- alexef
- gdsoumya

View File

@@ -1,12 +1,12 @@
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:-''}"
controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "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:-''}"
api-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "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/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: bash -c "if [ \"$ARGOCD_REDIS_LOCAL\" = 'true' ]; then redis-server --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; else docker run --rm --name argocd-redis -i -p ${ARGOCD_E2E_REDIS_PORT:-6379}:${ARGOCD_E2E_REDIS_PORT:-6379} docker.io/library/redis:$(grep "image: redis" manifests/base/redis/argocd-redis-deployment.yaml | cut -d':' -f3) --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; fi"
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 "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}"
ui: sh -c 'cd ui && ${ARGOCD_E2E_YARN_CMD:-yarn} start'
git-server: test/fixture/testrepos/start-git.sh
helm-registry: test/fixture/testrepos/start-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}
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'}"
applicationset-controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "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 "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"

View File

@@ -13,7 +13,6 @@
**Social:**
[![Twitter Follow](https://img.shields.io/twitter/follow/argoproj?style=social)](https://twitter.com/argoproj)
[![Slack](https://img.shields.io/badge/slack-argoproj-brightgreen.svg?logo=slack)](https://argoproj.github.io/community/join-slack)
[![LinkedIn](https://img.shields.io/badge/LinkedIn-argoproj-blue.svg?logo=linkedin)](https://www.linkedin.com/company/argoproj/)
# Argo CD - Declarative Continuous Delivery for Kubernetes
@@ -86,5 +85,4 @@ Participation in the Argo CD project is governed by the [CNCF Code of Conduct](h
1. [Getting Started with ArgoCD for GitOps Deployments](https://youtu.be/AvLuplh1skA)
1. [Using Argo CD & Datree for Stable Kubernetes CI/CD Deployments](https://youtu.be/17894DTru2Y)
1. [How to create Argo CD Applications Automatically using ApplicationSet? "Automation of GitOps"](https://amralaayassen.medium.com/how-to-create-argocd-applications-automatically-using-applicationset-automation-of-the-gitops-59455eaf4f72)
1. [Progressive Delivery with Service Mesh Argo Rollouts with Istio](https://www.cncf.io/blog/2022/12/16/progressive-delivery-with-service-mesh-argo-rollouts-with-istio/)

View File

@@ -1,128 +0,0 @@
header:
schema-version: 1.0.0
expiration-date: '2024-10-31T00:00:00.000Z' # One year from initial release.
last-updated: '2023-10-27'
last-reviewed: '2023-10-27'
commit-hash: fe606708859574b9b6102a505e260fac5d3fb14e
project-url: https://github.com/argoproj/argo-cd
project-release: v2.13.0
changelog: https://github.com/argoproj/argo-cd/releases
license: https://github.com/argoproj/argo-cd/blob/master/LICENSE
project-lifecycle:
status: active
roadmap: https://github.com/orgs/argoproj/projects/25
bug-fixes-only: false
core-maintainers:
- https://github.com/argoproj/argoproj/blob/master/MAINTAINERS.md
release-cycle: https://argo-cd.readthedocs.io/en/stable/developer-guide/release-process-and-cadence/
release-process: https://argo-cd.readthedocs.io/en/stable/developer-guide/release-process-and-cadence/#release-process
contribution-policy:
accepts-pull-requests: true
accepts-automated-pull-requests: true
automated-tools-list:
- automated-tool: dependabot
action: allowed
path:
- /
- automated-tool: snyk-report
action: allowed
path:
- docs/snyk
comment: |
This tool runs Snyk and generates a report of vulnerabilities in the project's dependencies. The report is
placed in the project's documentation. The workflow is defined here:
https://github.com/argoproj/argo-cd/blob/master/.github/workflows/update-snyk.yaml
contributing-policy: https://argo-cd.readthedocs.io/en/stable/developer-guide/code-contributions/
code-of-conduct: https://github.com/cncf/foundation/blob/master/code-of-conduct.md
documentation:
- https://argo-cd.readthedocs.io/
distribution-points:
- https://github.com/argoproj/argo-cd/releases
- https://quay.io/repository/argoproj/argocd
security-artifacts:
threat-model:
threat-model-created: true
evidence-url:
- https://github.com/argoproj/argoproj/blob/master/docs/argo_threat_model.pdf
- https://github.com/argoproj/argoproj/blob/master/docs/end_user_threat_model.pdf
self-assessment:
self-assessment-created: false
comment: |
An extensive self-assessment was performed for CNCF graduation. Because the self-assessment process was evolving
at the time, no standardized document has been published.
security-testing:
- tool-type: sca
tool-name: Dependabot
tool-version: "2"
tool-url: https://github.com/dependabot
integration:
ad-hoc: false
ci: false
before-release: false
tool-rulesets:
- https://github.com/argoproj/argo-cd/blob/master/.github/dependabot.yml
- tool-type: sca
tool-name: Snyk
tool-version: latest
tool-url: https://snyk.io/
integration:
ad-hoc: true
ci: true
before-release: false
- tool-type: sast
tool-name: CodeQL
tool-version: latest
tool-url: https://codeql.github.com/
integration:
ad-hoc: false
ci: true
before-release: false
comment: |
We use the default configuration with the latest version.
security-assessments:
- auditor-name: Trail of Bits
auditor-url: https://trailofbits.com
auditor-report: https://github.com/argoproj/argoproj/blob/master/docs/argo_security_final_report.pdf
report-year: 2021
- auditor-name: Ada Logics
auditor-url: https://adalogics.com
auditor-report: https://github.com/argoproj/argoproj/blob/master/docs/argo_security_audit_2022.pdf
report-year: 2022
- auditor-name: Ada Logics
auditor-url: https://adalogics.com
auditor-report: https://github.com/argoproj/argoproj/blob/master/docs/audit_fuzzer_adalogics_2022.pdf
report-year: 2022
comment: |
Part of the audit was performed by Ada Logics, focussed on fuzzing.
- auditor-name: Chainguard
auditor-url: https://chainguard.dev
auditor-report: https://github.com/argoproj/argoproj/blob/master/docs/software_supply_chain_slsa_assessment_chainguard_2023.pdf
report-year: 2023
comment: |
Confirmed the project's release process as achieving SLSA (v0.1) level 3.
security-contacts:
- type: email
value: cncf-argo-security@lists.cncf.io
primary: true
vulnerability-reporting:
accepts-vulnerability-reports: true
email-contact: cncf-argo-security@lists.cncf.io
security-policy: https://github.com/argoproj/argo-cd/security/policy
bug-bounty-available: true
bug-bounty-url: https://hackerone.com/ibb/policy_scopes
out-scope:
- vulnerable and outdated components # See https://github.com/argoproj/argo-cd/blob/master/SECURITY.md#a-word-about-security-scanners
- security logging and monitoring failures
dependencies:
third-party-packages: true
dependencies-lists:
- https://github.com/argoproj/argo-cd/blob/master/go.mod
- https://github.com/argoproj/argo-cd/blob/master/Dockerfile
- https://github.com/argoproj/argo-cd/blob/master/ui/package.json
sbom:
- sbom-file: https://github.com/argoproj/argo-cd/releases # Every release's assets include SBOMs.
sbom-format: SPDX
dependencies-lifecycle:
policy-url: https://argo-cd.readthedocs.io/en/stable/developer-guide/release-process-and-cadence/#dependencies-lifecycle-policy
env-dependencies-policy:
policy-url: https://argo-cd.readthedocs.io/en/stable/developer-guide/release-process-and-cadence/#dependencies-lifecycle-policy

View File

@@ -18,58 +18,48 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Albert Heijn](https://ah.nl/)
1. [Alibaba Group](https://www.alibabagroup.com/)
1. [Allianz Direct](https://www.allianzdirect.de/)
1. [AlphaSense](https://www.alpha-sense.com/)
1. [Amadeus IT Group](https://amadeus.com/)
1. [Ambassador Labs](https://www.getambassador.io/)
1. [Ancestry](https://www.ancestry.com/)
1. [Andgo Systems](https://www.andgosystems.com/)
1. [ANSTO - Australian Synchrotron](https://www.synchrotron.org.au/)
1. [Ant Group](https://www.antgroup.com/)
1. [AppDirect](https://www.appdirect.com)
1. [Arctiq Inc.](https://www.arctiq.ca)
2. [Arturia](https://www.arturia.com)
1. [ARZ Allgemeines Rechenzentrum GmbH](https://www.arz.at/)
1. [Autodesk](https://www.autodesk.com)
1. [Axians ACSP](https://www.axians.fr)
2. [Autodesk](https://www.autodesk.com)
1. [Axual B.V.](https://axual.com)
1. [Back Market](https://www.backmarket.com)
1. [Baloise](https://www.baloise.com)
1. [BCDevExchange DevOps Platform](https://bcdevexchange.org/DevOpsPlatform)
1. [Beat](https://thebeat.co/en/)
1. [Beez Innovation Labs](https://www.beezlabs.com/)
1. [Bedag Informatik AG](https://www.bedag.ch/)
1. [Beleza Na Web](https://www.belezanaweb.com.br/)
1. [BigPanda](https://bigpanda.io)
1. [BioBox Analytics](https://biobox.io)
1. [BMW Group](https://www.bmwgroup.com/)
1. [Boozt](https://www.booztgroup.com/)
1. [Boticario](https://www.boticario.com.br/)
1. [Broker Consulting, a.s.](https://www.bcas.cz/en/)
1. [Bulder Bank](https://bulderbank.no)
1. [CAM](https://cam-inc.co.jp)
1. [Camptocamp](https://camptocamp.com)
1. [Candis](https://www.candis.io)
1. [Capital One](https://www.capitalone.com)
1. [CARFAX Europe](https://www.carfax.eu)
1. [CARFAX](https://www.carfax.com)
1. [CARFAX Europe](https://www.carfax.eu)
1. [Carrefour Group](https://www.carrefour.com)
1. [Casavo](https://casavo.com)
1. [Celonis](https://www.celonis.com/)
1. [CERN](https://home.cern/)
1. [Chainnodes](https://chainnodes.org)
1. [Chargetrip](https://chargetrip.com)
1. [Chainnodes](https://chainnodes.org)
1. [Chime](https://www.chime.com)
1. [Cisco ET&I](https://eti.cisco.com/)
1. [Cloud Posse](https://www.cloudposse.com/)
1. [Cloud Scale](https://cloudscaleinc.com/)
1. [CloudGeometry](https://www.cloudgeometry.io/)
1. [Cloudmate](https://cloudmt.co.kr/)
1. [Cloudogu](https://cloudogu.com/)
1. [Cobalt](https://www.cobalt.io/)
1. [Codefresh](https://www.codefresh.io/)
1. [Codility](https://www.codility.com/)
1. [Commonbond](https://commonbond.co/)
1. [Contlo](https://contlo.com/)
1. [Coralogix](https://coralogix.com/)
1. [Crédit Agricole CIB](https://www.ca-cib.com)
1. [CROZ d.o.o.](https://croz.net/)
@@ -102,9 +92,7 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Fave](https://myfave.com)
1. [Flexport](https://www.flexport.com/)
1. [Flip](https://flip.id)
1. [Fly Security](https://www.flysecurity.com.br/)
1. [Fonoa](https://www.fonoa.com/)
1. [Fortra](https://www.fortra.com)
1. [freee](https://corp.freee.co.jp/en/company/)
1. [Freshop, Inc](https://www.freshop.com/)
1. [Future PLC](https://www.futureplc.com/)
@@ -121,8 +109,8 @@ Currently, the following organizations are **officially** using Argo CD:
1. [GlueOps](https://glueops.dev)
1. [GMETRI](https://gmetri.com/)
1. [Gojek](https://www.gojek.io/)
1. [GoTo Financial](https://gotofinancial.com/)
1. [GoTo](https://www.goto.com/)
1. [GoTo Financial](https://gotofinancial.com/)
1. [Greenpass](https://www.greenpass.com.br/)
1. [Gridfuse](https://gridfuse.com/)
1. [Groww](https://groww.in)
@@ -135,12 +123,9 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Hiya](https://hiya.com)
1. [Honestbank](https://honestbank.com)
1. [Hostinger](https://www.hostinger.com)
1. [IABAI](https://www.iab.ai)
1. [IBM](https://www.ibm.com/)
1. [Ibotta](https://home.ibotta.com)
1. [IFS](https://www.ifs.com)
1. [IITS-Consulting](https://iits-consulting.de)
1. [IllumiDesk](https://www.illumidesk.com)
1. [imaware](https://imaware.health)
1. [Indeed](https://indeed.com)
1. [Index Exchange](https://www.indexexchange.com/)
@@ -155,19 +140,16 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Karrot](https://www.daangn.com/)
1. [KarrotPay](https://www.daangnpay.com/)
1. [Kasa](https://kasa.co.kr/)
1. [Kave Home](https://kavehome.com)
1. [Keeeb](https://www.keeeb.com/)
1. [KelkooGroup](https://www.kelkoogroup.com)
1. [Keptn](https://keptn.sh)
1. [Kinguin](https://www.kinguin.net/)
1. [KintoHub](https://www.kintohub.com/)
1. [KompiTech GmbH](https://www.kompitech.com/)
1. [Kong Inc.](https://konghq.com/)
1. [KPMG](https://kpmg.com/uk)
1. [KubeSphere](https://github.com/kubesphere)
1. [Kurly](https://www.kurly.com/)
1. [Kvist](https://kvistsolutions.com)
1. [Kyriba](https://www.kyriba.com/)
1. [LexisNexis](https://www.lexisnexis.com/)
1. [Lian Chu Securities](https://lczq.com)
1. [Liatrio](https://www.liatrio.com)
@@ -187,8 +169,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Meican](https://meican.com/)
1. [Meilleurs Agents](https://www.meilleursagents.com/)
1. [Mercedes-Benz Tech Innovation](https://www.mercedes-benz-techinnovation.com/)
1. [Mercedes-Benz.io](https://www.mercedes-benz.io/)
1. [Metacore Games](https://metacoregames.com/)
1. [Metanet](http://www.metanet.co.kr/en/)
1. [MindSpore](https://mindspore.cn)
1. [Mirantis](https://mirantis.com/)
@@ -201,7 +181,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Natura &Co](https://naturaeco.com/)
1. [Nethopper](https://nethopper.io)
1. [New Relic](https://newrelic.com/)
1. [Nextbasket](https://nextbasket.com)
1. [Nextdoor](https://nextdoor.com/)
1. [Nikkei](https://www.nikkei.co.jp/nikkeiinfo/en/)
1. [Nitro](https://gonitro.com)
@@ -209,11 +188,9 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Objective](https://www.objective.com.br/)
1. [OCCMundial](https://occ.com.mx)
1. [Octadesk](https://octadesk.com)
1. [Octopus Deploy](https://octopus.com)
1. [Olfeo](https://www.olfeo.com/)
1. [omegaUp](https://omegaUp.com)
1. [Omni](https://omni.se/)
1. [Oncourse Home Solutions](https://oncoursehome.com/)
1. [openEuler](https://openeuler.org)
1. [openGauss](https://opengauss.org/)
1. [OpenGov](https://opengov.com)
@@ -230,17 +207,12 @@ Currently, the following organizations are **officially** using Argo CD:
1. [PagerDuty](https://www.pagerduty.com/)
1. [Pandosearch](https://www.pandosearch.com/en/home)
1. [Patreon](https://www.patreon.com/)
1. [PayIt](https://payitgov.com/)
1. [PayPay](https://paypay.ne.jp/)
1. [Peloton Interactive](https://www.onepeloton.com/)
1. [Percona](https://percona.com/)
1. [PGS](https://www.pgs.com)
1. [Pigment](https://www.gopigment.com/)
1. [Pipedrive](https://www.pipedrive.com/)
1. [Pipefy](https://www.pipefy.com/)
1. [Pipekit](https://pipekit.io/)
1. [Pismo](https://pismo.io/)
1. [PITS Globale Datenrettungsdienste](https://www.pitsdatenrettung.de/)
1. [Platform9 Systems](https://platform9.com/)
1. [Polarpoint.io](https://polarpoint.io)
1. [PostFinance](https://github.com/postfinance)
@@ -256,29 +228,22 @@ Currently, the following organizations are **officially** using Argo CD:
1. [QuintoAndar](https://quintoandar.com.br)
1. [Quipper](https://www.quipper.com/)
1. [RapidAPI](https://www.rapidapi.com/)
1. [rebuy](https://www.rebuy.de/)
1. [Recreation.gov](https://www.recreation.gov/)
1. [Red Hat](https://www.redhat.com/)
1. [Redpill Linpro](https://www.redpill-linpro.com/)
1. [Reenigne Cloud](https://reenigne.ca)
1. [reev.com](https://www.reev.com/)
1. [RightRev](https://rightrev.com/)
1. [Rijkswaterstaat](https://www.rijkswaterstaat.nl/en)
1. [Rise](https://www.risecard.eu/)
1. [Riskified](https://www.riskified.com/)
1. [Robotinfra](https://www.robotinfra.com)
1. [Rocket.Chat](https://rocket.chat)
1. [Rogo](https://rogodata.com)
1. [Rubin Observatory](https://www.lsst.org)
1. [Saildrone](https://www.saildrone.com/)
1. [Salad Technologies](https://salad.com/)
1. [Saloodo! GmbH](https://www.saloodo.com)
1. [Sap Labs](http://sap.com)
1. [Sauce Labs](https://saucelabs.com/)
1. [Schwarz IT](https://jobs.schwarz/it-mission)
1. [SCRM Lidl International Hub](https://scrm.lidl)
1. [SEEK](https://seek.com.au)
1. [Semgrep](https://semgrep.com)
1. [Shield](https://shield.com)
1. [SI Analytics](https://si-analytics.ai)
1. [Skit](https://skit.ai/)
1. [Skyscanner](https://www.skyscanner.net/)
@@ -293,8 +258,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Spendesk](https://spendesk.com/)
1. [Splunk](https://splunk.com/)
1. [Spores Labs](https://spores.app)
1. [Statsig](https://statsig.com)
1. [SternumIOT](https://sternumiot.com)
1. [StreamNative](https://streamnative.io)
1. [Stuart](https://stuart.com/)
1. [Sumo Logic](https://sumologic.com/)
@@ -303,13 +266,11 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Swisscom](https://www.swisscom.ch)
1. [Swissquote](https://github.com/swissquote)
1. [Syncier](https://syncier.com/)
1. [Syself](https://syself.com)
1. [TableCheck](https://tablecheck.com/)
1. [Tailor Brands](https://www.tailorbrands.com)
1. [Tamkeen Technologies](https://tamkeentech.sa/)
1. [Techcombank](https://www.techcombank.com.vn/trang-chu)
1. [Technacy](https://www.technacy.it/)
1. [Telavita](https://www.telavita.com.br/)
1. [Tesla](https://tesla.com/)
1. [The Scale Factory](https://www.scalefactory.com/)
1. [ThousandEyes](https://www.thousandeyes.com/)
@@ -333,7 +294,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Urbantz](https://urbantz.com/)
1. [Vectra](https://www.vectra.ai)
1. [Veepee](https://www.veepee.com)
1. [Verkada](https://www.verkada.com)
1. [Viaduct](https://www.viaduct.ai/)
1. [VietMoney](https://vietmoney.vn/)
1. [Vinted](https://vinted.com/)

View File

@@ -1 +1 @@
2.13.0
2.9.17

View File

@@ -18,11 +18,8 @@ import (
"context"
"fmt"
"reflect"
"strings"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
log "github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
@@ -42,10 +39,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/source"
"github.com/argoproj/argo-cd/v2/applicationset/controllers/template"
"github.com/argoproj/argo-cd/v2/applicationset/generators"
"github.com/argoproj/argo-cd/v2/applicationset/status"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
"github.com/argoproj/argo-cd/v2/common"
"github.com/argoproj/argo-cd/v2/util/db"
@@ -67,10 +63,12 @@ const (
ReconcileRequeueOnValidationError = time.Minute * 3
)
var defaultPreservedAnnotations = []string{
NotifiedAnnotationKey,
argov1alpha1.AnnotationKeyRefresh,
}
var (
defaultPreservedAnnotations = []string{
NotifiedAnnotationKey,
argov1alpha1.AnnotationKeyRefresh,
}
)
// ApplicationSetReconciler reconciles a ApplicationSet object
type ApplicationSetReconciler struct {
@@ -111,40 +109,24 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
// Do not attempt to further reconcile the ApplicationSet if it is being deleted.
if applicationSetInfo.ObjectMeta.DeletionTimestamp != nil {
appsetName := applicationSetInfo.ObjectMeta.Name
logCtx.Debugf("DeletionTimestamp is set on %s", appsetName)
deleteAllowed := utils.DefaultPolicy(applicationSetInfo.Spec.SyncPolicy, r.Policy, r.EnablePolicyOverride).AllowDelete()
if !deleteAllowed {
logCtx.Debugf("ApplicationSet policy does not allow to delete")
if err := r.removeOwnerReferencesOnDeleteAppSet(ctx, applicationSetInfo); err != nil {
return ctrl.Result{}, err
}
logCtx.Debugf("ownerReferences referring %s is deleted from generated applications", appsetName)
}
controllerutil.RemoveFinalizer(&applicationSetInfo, argov1alpha1.ResourcesFinalizerName)
if err := r.Update(ctx, &applicationSetInfo); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
// Log a warning if there are unrecognized generators
_ = utils.CheckInvalidGenerators(&applicationSetInfo)
// desiredApplications is the main list of all expected Applications from all generators in this appset.
desiredApplications, applicationSetReason, generatorsErr := template.GenerateApplications(logCtx, applicationSetInfo, r.Generators, r.Renderer, r.Client)
if generatorsErr != nil {
desiredApplications, applicationSetReason, err := r.generateApplications(applicationSetInfo)
if err != nil {
_ = r.setApplicationSetStatusCondition(ctx,
&applicationSetInfo,
argov1alpha1.ApplicationSetCondition{
Type: argov1alpha1.ApplicationSetConditionErrorOccurred,
Message: generatorsErr.Error(),
Message: err.Error(),
Reason: string(applicationSetReason),
Status: argov1alpha1.ApplicationSetConditionStatusTrue,
}, parametersGenerated,
)
if len(desiredApplications) < 1 {
return ctrl.Result{}, generatorsErr
}
return ctrl.Result{}, err
}
parametersGenerated = true
@@ -172,16 +154,6 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
return ctrl.Result{RequeueAfter: ReconcileRequeueOnValidationError}, nil
}
currentApplications, err := r.getCurrentApplications(ctx, applicationSetInfo)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to get current applications for application set: %w", err)
}
err = r.updateResourcesStatus(ctx, logCtx, &applicationSetInfo, currentApplications)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to get update resources status for application set: %w", err)
}
// appMap is a name->app collection of Applications in this ApplicationSet.
appMap := map[string]argov1alpha1.Application{}
// appSyncMap tracks which apps will be synced during this reconciliation.
@@ -190,19 +162,24 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
if r.EnableProgressiveSyncs {
if applicationSetInfo.Spec.Strategy == nil && len(applicationSetInfo.Status.ApplicationStatus) > 0 {
// If appset used progressive sync but stopped, clean up the progressive sync application statuses
logCtx.Infof("Removing %v unnecessary AppStatus entries from ApplicationSet %v", len(applicationSetInfo.Status.ApplicationStatus), applicationSetInfo.Name)
log.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, &applicationSetInfo, []argov1alpha1.ApplicationSetApplicationStatus{})
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to clear previous AppSet application statuses for %v: %w", applicationSetInfo.Name, err)
}
} else if applicationSetInfo.Spec.Strategy != nil {
// appset uses progressive sync
for _, app := range currentApplications {
applications, err := r.getCurrentApplications(ctx, applicationSetInfo)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to get current applications for application set: %w", err)
}
for _, app := range applications {
appMap[app.Name] = app
}
appSyncMap, err = r.performProgressiveSyncs(ctx, logCtx, applicationSetInfo, currentApplications, desiredApplications, appMap)
appSyncMap, err = r.performProgressiveSyncs(ctx, applicationSetInfo, applications, desiredApplications, appMap)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to perform progressive sync reconciliation for application set: %w", err)
}
@@ -240,7 +217,8 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
if r.EnableProgressiveSyncs {
// trigger appropriate application syncs if RollingSync strategy is enabled
if progressiveSyncsStrategyEnabled(&applicationSetInfo, "RollingSync") {
validApps, err = r.syncValidApplications(logCtx, &applicationSetInfo, appSyncMap, appMap, validApps)
validApps, err = r.syncValidApplications(ctx, &applicationSetInfo, appSyncMap, appMap, validApps)
if err != nil {
_ = r.setApplicationSetStatusCondition(ctx,
&applicationSetInfo,
@@ -257,7 +235,7 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
}
if utils.DefaultPolicy(applicationSetInfo.Spec.SyncPolicy, r.Policy, r.EnablePolicyOverride).AllowUpdate() {
err = r.createOrUpdateInCluster(ctx, logCtx, applicationSetInfo, validApps)
err = r.createOrUpdateInCluster(ctx, applicationSetInfo, validApps)
if err != nil {
_ = r.setApplicationSetStatusCondition(ctx,
&applicationSetInfo,
@@ -271,7 +249,7 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
return ctrl.Result{}, err
}
} else {
err = r.createInCluster(ctx, logCtx, applicationSetInfo, validApps)
err = r.createInCluster(ctx, applicationSetInfo, validApps)
if err != nil {
_ = r.setApplicationSetStatusCondition(ctx,
&applicationSetInfo,
@@ -287,7 +265,7 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
}
if utils.DefaultPolicy(applicationSetInfo.Spec.SyncPolicy, r.Policy, r.EnablePolicyOverride).AllowDelete() {
err = r.deleteInCluster(ctx, logCtx, applicationSetInfo, desiredApplications)
err = r.deleteInCluster(ctx, applicationSetInfo, desiredApplications)
if err != nil {
_ = r.setApplicationSetStatusCondition(ctx,
&applicationSetInfo,
@@ -322,7 +300,7 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
requeueAfter := r.getMinRequeueAfter(&applicationSetInfo)
if len(validateErrors) == 0 && generatorsErr == nil {
if len(validateErrors) == 0 {
if err := r.setApplicationSetStatusCondition(ctx,
&applicationSetInfo,
argov1alpha1.ApplicationSetCondition{
@@ -431,7 +409,7 @@ func (r *ApplicationSetReconciler) setApplicationSetStatusCondition(ctx context.
if client.IgnoreNotFound(err) != nil {
return nil
}
return fmt.Errorf("error fetching updated application set: %w", err)
return fmt.Errorf("error fetching updated application set: %v", err)
}
applicationSet.Status.SetConditions(
@@ -441,7 +419,7 @@ func (r *ApplicationSetReconciler) setApplicationSetStatusCondition(ctx context.
// Update the newly fetched object with new set of conditions
err := r.Client.Status().Update(ctx, applicationSet)
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: %v", err)
}
}
@@ -454,6 +432,7 @@ func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Con
errorsByIndex := map[int]error{}
namesSet := map[string]bool{}
for i, app := range desiredApplications {
if !namesSet[app.Name] {
namesSet[app.Name] = true
} else {
@@ -473,6 +452,7 @@ func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Con
errorsByIndex[i] = fmt.Errorf("application destination spec is invalid: %s", err.Error())
continue
}
}
return errorsByIndex, nil
@@ -481,6 +461,7 @@ func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Con
func (r *ApplicationSetReconciler) getMinRequeueAfter(applicationSetInfo *argov1alpha1.ApplicationSet) time.Duration {
var res time.Duration
for _, requestedGenerator := range applicationSetInfo.Spec.Generators {
relevantGenerators := generators.GetRelevantGenerators(&requestedGenerator, r.Generators)
for _, g := range relevantGenerators {
@@ -497,6 +478,62 @@ func (r *ApplicationSetReconciler) getMinRequeueAfter(applicationSetInfo *argov1
return res
}
func getTempApplication(applicationSetTemplate argov1alpha1.ApplicationSetTemplate) *argov1alpha1.Application {
var tmplApplication argov1alpha1.Application
tmplApplication.Annotations = applicationSetTemplate.Annotations
tmplApplication.Labels = applicationSetTemplate.Labels
tmplApplication.Namespace = applicationSetTemplate.Namespace
tmplApplication.Name = applicationSetTemplate.Name
tmplApplication.Spec = applicationSetTemplate.Spec
tmplApplication.Finalizers = applicationSetTemplate.Finalizers
return &tmplApplication
}
func (r *ApplicationSetReconciler) generateApplications(applicationSetInfo argov1alpha1.ApplicationSet) ([]argov1alpha1.Application, argov1alpha1.ApplicationSetReasonType, error) {
var res []argov1alpha1.Application
var firstError error
var applicationSetReason argov1alpha1.ApplicationSetReasonType
for _, requestedGenerator := range applicationSetInfo.Spec.Generators {
t, err := generators.Transform(requestedGenerator, r.Generators, applicationSetInfo.Spec.Template, &applicationSetInfo, map[string]interface{}{})
if err != nil {
log.WithError(err).WithField("generator", requestedGenerator).
Error("error generating application from params")
if firstError == nil {
firstError = err
applicationSetReason = argov1alpha1.ApplicationSetReasonApplicationParamsGenerationError
}
continue
}
for _, a := range t {
tmplApplication := getTempApplication(a.Template)
for _, p := range a.Params {
app, err := r.Renderer.RenderTemplateParams(tmplApplication, applicationSetInfo.Spec.SyncPolicy, p, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions)
if err != nil {
log.WithError(err).WithField("params", a.Params).WithField("generator", requestedGenerator).
Error("error generating application from params")
if firstError == nil {
firstError = err
applicationSetReason = argov1alpha1.ApplicationSetReasonRenderTemplateParamsError
}
continue
}
res = append(res, *app)
}
}
log.WithField("generator", requestedGenerator).Infof("generated %d applications", len(res))
log.WithField("generator", requestedGenerator).Debugf("apps from generator: %+v", res)
}
return res, applicationSetReason, firstError
}
func ignoreNotAllowedNamespaces(namespaces []string) predicate.Predicate {
return predicate.Funcs{
CreateFunc: func(e event.CreateEvent) bool {
@@ -505,24 +542,22 @@ func ignoreNotAllowedNamespaces(namespaces []string) predicate.Predicate {
}
}
func appControllerIndexer(rawObj client.Object) []string {
// grab the job object, extract the owner...
app := rawObj.(*argov1alpha1.Application)
owner := metav1.GetControllerOf(app)
if owner == nil {
return nil
}
// ...make sure it's a application set...
if owner.APIVersion != argov1alpha1.SchemeGroupVersion.String() || owner.Kind != "ApplicationSet" {
return nil
}
// ...and if so, return it
return []string{owner.Name}
}
func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager, enableProgressiveSyncs bool, maxConcurrentReconciliations int) error {
if err := mgr.GetFieldIndexer().IndexField(context.TODO(), &argov1alpha1.Application{}, ".metadata.controller", appControllerIndexer); err != nil {
if err := mgr.GetFieldIndexer().IndexField(context.TODO(), &argov1alpha1.Application{}, ".metadata.controller", func(rawObj client.Object) []string {
// grab the job object, extract the owner...
app := rawObj.(*argov1alpha1.Application)
owner := metav1.GetControllerOf(app)
if owner == nil {
return nil
}
// ...make sure it's a application set...
if owner.APIVersion != argov1alpha1.SchemeGroupVersion.String() || owner.Kind != "ApplicationSet" {
return nil
}
// ...and if so, return it
return []string{owner.Name}
}); err != nil {
return fmt.Errorf("error setting up with manager: %w", err)
}
@@ -534,7 +569,7 @@ func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager, enableProg
Owns(&argov1alpha1.Application{}, builder.WithPredicates(ownsHandler)).
WithEventFilter(ignoreNotAllowedNamespaces(r.ApplicationSetNamespaces)).
Watches(
&corev1.Secret{},
&source.Kind{Type: &corev1.Secret{}},
&clusterSecretEventHandler{
Client: mgr.GetClient(),
Log: log.WithField("type", "createSecretEventHandler"),
@@ -566,11 +601,14 @@ func (r *ApplicationSetReconciler) updateCache(ctx context.Context, obj client.O
// - For new applications, it will call create
// - For existing application, it will call update
// The function also adds owner reference to all applications, and uses it to delete them.
func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context, logCtx *log.Entry, applicationSet argov1alpha1.ApplicationSet, desiredApplications []argov1alpha1.Application) error {
func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context, applicationSet argov1alpha1.ApplicationSet, desiredApplications []argov1alpha1.Application) error {
var firstError error
// Creates or updates the application in appList
for _, generatedApp := range desiredApplications {
appLog := logCtx.WithFields(log.Fields{"app": generatedApp.QualifiedName()})
appLog := log.WithFields(log.Fields{"app": generatedApp.Name, "appSet": applicationSet.Name})
generatedApp.Namespace = applicationSet.Namespace
// Normalize to avoid fighting with the application controller.
generatedApp.Spec = *argoutil.NormalizeApplicationSpec(&generatedApp.Spec)
@@ -634,17 +672,6 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
}
}
// Preserve post-delete finalizers:
// https://github.com/argoproj/argo-cd/issues/17181
for _, finalizer := range found.ObjectMeta.Finalizers {
if strings.HasPrefix(finalizer, argov1alpha1.PostDeleteFinalizerName) {
if generatedApp.Finalizers == nil {
generatedApp.Finalizers = []string{}
}
generatedApp.Finalizers = append(generatedApp.Finalizers, finalizer)
}
}
found.ObjectMeta.Annotations = generatedApp.Annotations
found.ObjectMeta.Finalizers = generatedApp.Finalizers
@@ -652,6 +679,7 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
return controllerutil.SetControllerReference(&applicationSet, found, r.Scheme)
})
if err != nil {
appLog.WithError(err).WithField("action", action).Errorf("failed to %s Application", action)
if firstError == nil {
@@ -676,7 +704,8 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
// createInCluster will filter from the desiredApplications only the application that needs to be created
// Then it will call createOrUpdateInCluster to do the actual create
func (r *ApplicationSetReconciler) createInCluster(ctx context.Context, logCtx *log.Entry, applicationSet argov1alpha1.ApplicationSet, desiredApplications []argov1alpha1.Application) error {
func (r *ApplicationSetReconciler) createInCluster(ctx context.Context, applicationSet argov1alpha1.ApplicationSet, desiredApplications []argov1alpha1.Application) error {
var createApps []argov1alpha1.Application
current, err := r.getCurrentApplications(ctx, applicationSet)
if err != nil {
@@ -698,12 +727,14 @@ func (r *ApplicationSetReconciler) createInCluster(ctx context.Context, logCtx *
}
}
return r.createOrUpdateInCluster(ctx, logCtx, applicationSet, createApps)
return r.createOrUpdateInCluster(ctx, applicationSet, createApps)
}
func (r *ApplicationSetReconciler) getCurrentApplications(ctx context.Context, applicationSet argov1alpha1.ApplicationSet) ([]argov1alpha1.Application, error) {
func (r *ApplicationSetReconciler) getCurrentApplications(_ context.Context, applicationSet argov1alpha1.ApplicationSet) ([]argov1alpha1.Application, error) {
// TODO: Should this use the context param?
var current argov1alpha1.ApplicationList
err := r.Client.List(ctx, &current, client.MatchingFields{".metadata.controller": applicationSet.Name}, client.InNamespace(applicationSet.Namespace))
err := r.Client.List(context.Background(), &current, client.MatchingFields{".metadata.controller": applicationSet.Name})
if err != nil {
return nil, fmt.Errorf("error retrieving applications: %w", err)
}
@@ -713,7 +744,7 @@ func (r *ApplicationSetReconciler) getCurrentApplications(ctx context.Context, a
// 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
func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, logCtx *log.Entry, applicationSet argov1alpha1.ApplicationSet, desiredApplications []argov1alpha1.Application) error {
func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, 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)
@@ -737,14 +768,15 @@ func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, logCtx *
// Delete apps that are not in m[string]bool
var firstError error
for _, app := range current {
logCtx = logCtx.WithField("app", app.QualifiedName())
appLog := log.WithFields(log.Fields{"app": app.Name, "appSet": applicationSet.Name})
_, exists := m[app.Name]
if !exists {
// Removes the Argo CD resources finalizer if the application contains an invalid target (eg missing cluster)
err := r.removeFinalizerOnInvalidDestination(ctx, applicationSet, &app, clusterList, logCtx)
err := r.removeFinalizerOnInvalidDestination(ctx, applicationSet, &app, clusterList, appLog)
if err != nil {
logCtx.WithError(err).Error("failed to update Application")
appLog.WithError(err).Error("failed to update Application")
if firstError != nil {
firstError = err
}
@@ -753,14 +785,14 @@ func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, logCtx *
err = r.Client.Delete(ctx, &app)
if err != nil {
logCtx.WithError(err).Error("failed to delete Application")
appLog.WithError(err).Error("failed to delete Application")
if firstError != nil {
firstError = err
}
continue
}
r.Recorder.Eventf(&applicationSet, corev1.EventTypeNormal, "Deleted", "Deleted Application %q", app.Name)
logCtx.Log(log.InfoLevel, "Deleted application")
appLog.Log(log.InfoLevel, "Deleted application")
}
}
return firstError
@@ -768,6 +800,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)
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
if len(app.Finalizers) == 0 {
return nil
@@ -780,10 +813,12 @@ func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx conte
appLog.Warnf("The destination cluster for %s couldn't be found: %v", app.Name, err)
validDestination = false
} else {
// Detect if the destination's server field does not match an existing cluster
matchingCluster := false
for _, cluster := range clusterList.Items {
// Server fields must match. Note that ValidateDestination ensures that the server field is set, if applicable.
if app.Spec.Destination.Server != cluster.Server {
continue
@@ -807,6 +842,7 @@ func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx conte
// If the destination is invalid (for example the cluster is no longer defined), then remove
// the application finalizers to avoid triggering Argo CD bug #5817
if !validDestination {
// Filter out the Argo CD finalizer from the finalizer list
var newFinalizers []string
for _, existingFinalizer := range app.Finalizers {
@@ -838,37 +874,21 @@ func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx conte
return nil
}
func (r *ApplicationSetReconciler) removeOwnerReferencesOnDeleteAppSet(ctx context.Context, applicationSet argov1alpha1.ApplicationSet) error {
applications, err := r.getCurrentApplications(ctx, applicationSet)
if err != nil {
return err
}
func (r *ApplicationSetReconciler) performProgressiveSyncs(ctx context.Context, appset argov1alpha1.ApplicationSet, applications []argov1alpha1.Application, desiredApplications []argov1alpha1.Application, appMap map[string]argov1alpha1.Application) (map[string]bool, error) {
for _, app := range applications {
app.SetOwnerReferences([]metav1.OwnerReference{})
err := r.Client.Update(ctx, &app)
if err != nil {
return err
}
}
return nil
}
func (r *ApplicationSetReconciler) performProgressiveSyncs(ctx context.Context, logCtx *log.Entry, appset argov1alpha1.ApplicationSet, applications []argov1alpha1.Application, desiredApplications []argov1alpha1.Application, appMap map[string]argov1alpha1.Application) (map[string]bool, error) {
appDependencyList, appStepMap, err := r.buildAppDependencyList(logCtx, appset, desiredApplications)
appDependencyList, appStepMap, err := r.buildAppDependencyList(ctx, appset, desiredApplications)
if err != nil {
return nil, fmt.Errorf("failed to build app dependency list: %w", err)
}
_, err = r.updateApplicationSetApplicationStatus(ctx, logCtx, &appset, applications, appStepMap)
_, err = r.updateApplicationSetApplicationStatus(ctx, &appset, applications, appStepMap)
if err != nil {
return nil, fmt.Errorf("failed to update applicationset app status: %w", err)
}
logCtx.Infof("ApplicationSet %v step list:", appset.Name)
log.Infof("ApplicationSet %v step list:", appset.Name)
for i, step := range appDependencyList {
logCtx.Infof("step %v: %+v", i+1, step)
log.Infof("step %v: %+v", i+1, step)
}
appSyncMap, err := r.buildAppSyncMap(ctx, appset, appDependencyList, appMap)
@@ -876,9 +896,9 @@ func (r *ApplicationSetReconciler) performProgressiveSyncs(ctx context.Context,
return nil, fmt.Errorf("failed to build app sync map: %w", err)
}
logCtx.Infof("Application allowed to sync before maxUpdate?: %+v", appSyncMap)
log.Infof("Application allowed to sync before maxUpdate?: %+v", appSyncMap)
_, err = r.updateApplicationSetApplicationStatusProgress(ctx, logCtx, &appset, appSyncMap, appStepMap, appMap)
_, err = r.updateApplicationSetApplicationStatusProgress(ctx, &appset, appSyncMap, appStepMap, appMap)
if err != nil {
return nil, fmt.Errorf("failed to update applicationset application status progress: %w", err)
}
@@ -892,7 +912,8 @@ func (r *ApplicationSetReconciler) performProgressiveSyncs(ctx context.Context,
}
// this list tracks which Applications belong to each RollingUpdate step
func (r *ApplicationSetReconciler) buildAppDependencyList(logCtx *log.Entry, applicationSet argov1alpha1.ApplicationSet, applications []argov1alpha1.Application) ([][]string, map[string]int, error) {
func (r *ApplicationSetReconciler) buildAppDependencyList(ctx context.Context, applicationSet argov1alpha1.ApplicationSet, applications []argov1alpha1.Application) ([][]string, map[string]int, error) {
if applicationSet.Spec.Strategy == nil || applicationSet.Spec.Strategy.Type == "" || applicationSet.Spec.Strategy.Type == "AllAtOnce" {
return [][]string{}, map[string]int{}, nil
}
@@ -912,13 +933,15 @@ func (r *ApplicationSetReconciler) buildAppDependencyList(logCtx *log.Entry, app
// use applicationLabelSelectors to filter generated Applications into steps and status by name
for _, app := range applications {
for i, step := range steps {
selected := true // default to true, assuming the current Application is a match for the given step matchExpression
for _, matchExpression := range step.MatchExpressions {
if val, ok := app.Labels[matchExpression.Key]; ok {
valueMatched := labelMatchedExpression(logCtx, val, matchExpression)
if !valueMatched { // none of the matchExpression values was a match with the Application's labels
if val, ok := app.Labels[matchExpression.Key]; ok {
valueMatched := labelMatchedExpression(val, matchExpression)
if !valueMatched { // none of the matchExpression values was a match with the Application'ss labels
selected = false
break
}
@@ -931,7 +954,7 @@ func (r *ApplicationSetReconciler) buildAppDependencyList(logCtx *log.Entry, app
if selected {
appDependencyList[i] = append(appDependencyList[i], app.Name)
if val, ok := appStepMap[app.Name]; ok {
logCtx.Warnf("AppSet '%v' has a invalid matchExpression that selects Application '%v' label twice, in steps %v and %v", applicationSet.Name, app.Name, val+1, i+1)
log.Warnf("AppSet '%v' has a invalid matchExpression that selects Application '%v' label twice, in steps %v and %v", applicationSet.Name, app.Name, val+1, i+1)
} else {
appStepMap[app.Name] = i
}
@@ -942,9 +965,9 @@ func (r *ApplicationSetReconciler) buildAppDependencyList(logCtx *log.Entry, app
return appDependencyList, appStepMap, nil
}
func labelMatchedExpression(logCtx *log.Entry, val string, matchExpression argov1alpha1.ApplicationMatchExpression) bool {
func labelMatchedExpression(val string, matchExpression argov1alpha1.ApplicationMatchExpression) bool {
if matchExpression.Operator != "In" && matchExpression.Operator != "NotIn" {
logCtx.Errorf("skipping AppSet rollingUpdate step Application selection, invalid matchExpression operator provided: %q ", matchExpression.Operator)
log.Errorf("skipping AppSet rollingUpdate step Application selection, invalid matchExpression operator provided: %q ", matchExpression.Operator)
return false
}
@@ -978,6 +1001,7 @@ func (r *ApplicationSetReconciler) buildAppSyncMap(ctx context.Context, applicat
// detect if we need to halt before progressing to the next step
for _, appName := range appDependencyList[i] {
idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, appName)
if idx == -1 {
// no Application status found, likely because the Application is being newly created
@@ -988,6 +1012,7 @@ func (r *ApplicationSetReconciler) buildAppSyncMap(ctx context.Context, applicat
appStatus := applicationSet.Status.ApplicationStatus[idx]
if app, ok := appMap[appName]; ok {
syncEnabled = appSyncEnabledForNextStep(&applicationSet, app, appStatus)
if !syncEnabled {
break
@@ -1004,6 +1029,7 @@ func (r *ApplicationSetReconciler) buildAppSyncMap(ctx context.Context, applicat
}
func appSyncEnabledForNextStep(appset *argov1alpha1.ApplicationSet, app argov1alpha1.Application, appStatus argov1alpha1.ApplicationSetApplicationStatus) bool {
if progressiveSyncsStrategyEnabled(appset, "RollingSync") {
// we still need to complete the current step if the Application is not yet Healthy or there are still pending Application changes
return isApplicationHealthy(app) && appStatus.Status == "Healthy"
@@ -1045,11 +1071,13 @@ func statusStrings(app argov1alpha1.Application) (string, string, string) {
}
// check the status of each Application's status and promote Applications to the next status if needed
func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx context.Context, logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, applications []argov1alpha1.Application, appStepMap map[string]int) ([]argov1alpha1.ApplicationSetApplicationStatus, error) {
func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx context.Context, applicationSet *argov1alpha1.ApplicationSet, applications []argov1alpha1.Application, appStepMap map[string]int) ([]argov1alpha1.ApplicationSetApplicationStatus, error) {
now := metav1.Now()
appStatuses := make([]argov1alpha1.ApplicationSetApplicationStatus, 0, len(applications))
for _, app := range applications {
healthStatusString, syncStatusString, operationPhaseString := statusStrings(app)
idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, app.Name)
@@ -1064,7 +1092,6 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
Message: "No Application status found, defaulting status to Waiting.",
Status: "Waiting",
Step: fmt.Sprint(appStepMap[app.Name] + 1),
TargetRevisions: app.Status.GetRevisions(),
}
} else {
// we have an existing AppStatus
@@ -1077,32 +1104,27 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
}
if appOutdated && currentAppStatus.Status != "Waiting" && currentAppStatus.Status != "Pending" {
logCtx.Infof("Application %v is outdated, updating its ApplicationSet status to Waiting", app.Name)
log.Infof("Application %v is outdated, updating its ApplicationSet status to Waiting", app.Name)
currentAppStatus.LastTransitionTime = &now
currentAppStatus.Status = "Waiting"
currentAppStatus.Message = "Application has pending changes, setting status to Waiting."
currentAppStatus.Step = fmt.Sprint(appStepMap[currentAppStatus.Application] + 1)
currentAppStatus.TargetRevisions = app.Status.GetRevisions()
}
if currentAppStatus.Status == "Pending" {
if operationPhaseString == "Succeeded" {
revisions := []string{}
if len(app.Status.OperationState.SyncResult.Revisions) > 0 {
revisions = app.Status.OperationState.SyncResult.Revisions
} else if app.Status.OperationState.SyncResult.Revision != "" {
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 = fmt.Sprint(appStepMap[currentAppStatus.Application] + 1)
// check for successful syncs started less than 10s before the Application transitioned to Pending
// this covers race conditions where syncs initiated by RollingSync miraculously have a sync time before the transition to Pending state occurred (could be a few seconds)
if operationPhaseString == "Succeeded" && app.Status.OperationState.StartedAt.Add(time.Duration(10)*time.Second).After(currentAppStatus.LastTransitionTime.Time) {
if !app.Status.OperationState.StartedAt.After(currentAppStatus.LastTransitionTime.Time) {
log.Warnf("Application %v was synced less than 10s prior to entering Pending status, we'll assume the AppSet controller triggered this sync and update its status to Progressing", app.Name)
}
log.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 = fmt.Sprint(appStepMap[currentAppStatus.Application] + 1)
} else if operationPhaseString == "Running" || healthStatusString == "Progressing" {
logCtx.Infof("Application %v has entered Progressing status, updating its ApplicationSet status to Progressing", app.Name)
log.Infof("Application %v has entered Progressing status, updating its ApplicationSet status to Progressing", app.Name)
currentAppStatus.LastTransitionTime = &now
currentAppStatus.Status = "Progressing"
currentAppStatus.Message = "Application resource became Progressing, updating status from Pending to Progressing."
@@ -1111,7 +1133,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
}
if currentAppStatus.Status == "Waiting" && isApplicationHealthy(app) {
logCtx.Infof("Application %v is already synced and healthy, updating its ApplicationSet status to Healthy", app.Name)
log.Infof("Application %v is already synced and healthy, updating its ApplicationSet status to Healthy", app.Name)
currentAppStatus.LastTransitionTime = &now
currentAppStatus.Status = healthStatusString
currentAppStatus.Message = "Application resource is already Healthy, updating status from Waiting to Healthy."
@@ -1119,7 +1141,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
}
if currentAppStatus.Status == "Progressing" && isApplicationHealthy(app) {
logCtx.Infof("Application %v has completed Progressing status, updating its ApplicationSet status to Healthy", app.Name)
log.Infof("Application %v has completed Progressing status, updating its ApplicationSet status to Healthy", app.Name)
currentAppStatus.LastTransitionTime = &now
currentAppStatus.Status = healthStatusString
currentAppStatus.Message = "Application resource became Healthy, updating status from Progressing to Healthy."
@@ -1129,7 +1151,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
appStatuses = append(appStatuses, currentAppStatus)
}
err := r.setAppSetApplicationStatus(ctx, logCtx, applicationSet, appStatuses)
err := r.setAppSetApplicationStatus(ctx, applicationSet, appStatuses)
if err != nil {
return nil, fmt.Errorf("failed to set AppSet application statuses: %w", err)
}
@@ -1138,7 +1160,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
}
// check Applications that are in Waiting status and promote them to Pending if needed
func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress(ctx context.Context, logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appStepMap map[string]int, appMap map[string]argov1alpha1.Application) ([]argov1alpha1.ApplicationSetApplicationStatus, error) {
func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress(ctx context.Context, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appStepMap map[string]int, appMap map[string]argov1alpha1.Application) ([]argov1alpha1.ApplicationSetApplicationStatus, error) {
now := metav1.Now()
appStatuses := make([]argov1alpha1.ApplicationSetApplicationStatus, 0, len(applicationSet.Status.ApplicationStatus))
@@ -1169,6 +1191,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
}
for _, appStatus := range applicationSet.Status.ApplicationStatus {
maxUpdateAllowed := true
maxUpdate := &intstr.IntOrString{}
if progressiveSyncsStrategyEnabled(applicationSet, "RollingSync") {
@@ -1179,7 +1202,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
if maxUpdate != nil {
maxUpdateVal, err := intstr.GetScaledValueFromIntOrPercent(maxUpdate, totalCountMap[appStepMap[appStatus.Application]], false)
if err != nil {
logCtx.Warnf("AppSet '%v' has a invalid maxUpdate value '%+v', ignoring maxUpdate logic for this step: %v", applicationSet.Name, maxUpdate, err)
log.Warnf("AppSet '%v' has a invalid maxUpdate value '%+v', ignoring maxUpdate logic for this step: %v", applicationSet.Name, maxUpdate, err)
}
// ensure that percentage values greater than 0% always result in at least 1 Application being selected
@@ -1189,12 +1212,13 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
if updateCountMap[appStepMap[appStatus.Application]] >= maxUpdateVal {
maxUpdateAllowed = false
logCtx.Infof("Application %v is not allowed to update yet, %v/%v Applications already updating in step %v in AppSet %v", appStatus.Application, updateCountMap[appStepMap[appStatus.Application]], maxUpdateVal, appStepMap[appStatus.Application]+1, applicationSet.Name)
log.Infof("Application %v is not allowed to update yet, %v/%v Applications already updating in step %v in AppSet %v", appStatus.Application, updateCountMap[appStepMap[appStatus.Application]], maxUpdateVal, appStepMap[appStatus.Application]+1, applicationSet.Name)
}
}
if appStatus.Status == "Waiting" && appSyncMap[appStatus.Application] && maxUpdateAllowed {
logCtx.Infof("Application %v moved to Pending status, watching for the Application to start Progressing", appStatus.Application)
log.Infof("Application %v moved to Pending status, watching for the Application to start Progressing", appStatus.Application)
appStatus.LastTransitionTime = &now
appStatus.Status = "Pending"
appStatus.Message = "Application moved to Pending status, watching for the Application resource to start Progressing."
@@ -1207,7 +1231,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
}
}
err := r.setAppSetApplicationStatus(ctx, logCtx, applicationSet, appStatuses)
err := r.setAppSetApplicationStatus(ctx, applicationSet, appStatuses)
if err != nil {
return nil, fmt.Errorf("failed to set AppSet app status: %w", err)
}
@@ -1216,6 +1240,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
}
func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusConditions(ctx context.Context, applicationSet *argov1alpha1.ApplicationSet) ([]argov1alpha1.ApplicationSetCondition, error) {
appSetProgressing := false
for _, appStatus := range applicationSet.Status.ApplicationStatus {
if appStatus.Status != "Healthy" {
@@ -1266,36 +1291,9 @@ func findApplicationStatusIndex(appStatuses []argov1alpha1.ApplicationSetApplica
return -1
}
func (r *ApplicationSetReconciler) updateResourcesStatus(ctx context.Context, logCtx *log.Entry, appset *argov1alpha1.ApplicationSet, apps []argov1alpha1.Application) error {
statusMap := status.GetResourceStatusMap(appset)
statusMap = status.BuildResourceStatus(statusMap, apps)
statuses := []argov1alpha1.ResourceStatus{}
for _, status := range statusMap {
statuses = append(statuses, status)
}
appset.Status.Resources = statuses
namespacedName := types.NamespacedName{Namespace: appset.Namespace, Name: appset.Name}
err := r.Client.Status().Update(ctx, appset)
if err != nil {
logCtx.Errorf("unable to set application set status: %v", err)
return fmt.Errorf("unable to set application set status: %w", err)
}
if err := r.Get(ctx, namespacedName, appset); err != nil {
if client.IgnoreNotFound(err) != nil {
return nil
}
return fmt.Errorf("error fetching updated application set: %w", err)
}
return nil
}
// setApplicationSetApplicationStatus updates the ApplicationSet's status field
// setApplicationSetApplicationStatus updates the ApplicatonSet's status field
// with any new/changed Application statuses.
func (r *ApplicationSetReconciler) setAppSetApplicationStatus(ctx context.Context, logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, applicationStatuses []argov1alpha1.ApplicationSetApplicationStatus) error {
func (r *ApplicationSetReconciler) setAppSetApplicationStatus(ctx context.Context, applicationSet *argov1alpha1.ApplicationSet, applicationStatuses []argov1alpha1.ApplicationSetApplicationStatus) error {
needToUpdateStatus := false
if len(applicationStatuses) != len(applicationSet.Status.ApplicationStatus) {
@@ -1328,22 +1326,23 @@ func (r *ApplicationSetReconciler) setAppSetApplicationStatus(ctx context.Contex
// Update the newly fetched object with new set of ApplicationStatus
err := r.Client.Status().Update(ctx, applicationSet)
if err != nil {
logCtx.Errorf("unable to set application set status: %v", err)
return fmt.Errorf("unable to set application set status: %w", err)
log.Errorf("unable to set application set status: %v", err)
return fmt.Errorf("unable to set application set status: %v", err)
}
if err := r.Get(ctx, namespacedName, applicationSet); err != nil {
if client.IgnoreNotFound(err) != nil {
return nil
}
return fmt.Errorf("error fetching updated application set: %w", err)
return fmt.Errorf("error fetching updated application set: %v", err)
}
}
return nil
}
func (r *ApplicationSetReconciler) syncValidApplications(logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appMap map[string]argov1alpha1.Application, validApps []argov1alpha1.Application) ([]argov1alpha1.Application, error) {
func (r *ApplicationSetReconciler) syncValidApplications(ctx context.Context, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appMap map[string]argov1alpha1.Application, validApps []argov1alpha1.Application) ([]argov1alpha1.Application, error) {
rolloutApps := []argov1alpha1.Application{}
for i := range validApps {
pruneEnabled := false
@@ -1363,7 +1362,7 @@ func (r *ApplicationSetReconciler) syncValidApplications(logCtx *log.Entry, appl
// check appSyncMap to determine which Applications are ready to be updated and which should be skipped
if appSyncMap[validApps[i].Name] && appMap[validApps[i].Name].Status.Sync.Status == "OutOfSync" && appSetStatusPending {
logCtx.Infof("triggering sync for application: %v, prune enabled: %v", validApps[i].Name, pruneEnabled)
log.Infof("triggering sync for application: %v, prune enabled: %v", validApps[i].Name, pruneEnabled)
validApps[i], _ = syncApplication(validApps[i], pruneEnabled)
}
rolloutApps = append(rolloutApps, validApps[i])
@@ -1373,6 +1372,7 @@ func (r *ApplicationSetReconciler) syncValidApplications(logCtx *log.Entry, appl
// used by the RollingSync Progressive Sync strategy to trigger a sync of a particular Application resource
func syncApplication(application argov1alpha1.Application, prune bool) (argov1alpha1.Application, error) {
operation := argov1alpha1.Operation{
InitiatedBy: argov1alpha1.OperationInitiator{
Username: "applicationset-controller",
@@ -1406,51 +1406,29 @@ func getOwnsHandlerPredicates(enableProgressiveSyncs bool) predicate.Funcs {
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
// re-reconcile
if log.IsLevelEnabled(log.DebugLevel) {
var appName string
app, isApp := e.Object.(*argov1alpha1.Application)
if isApp {
appName = app.QualifiedName()
}
log.WithField("app", appName).Debugln("received create event from owning an application")
}
log.Debugln("received create event from owning an application")
return false
},
DeleteFunc: func(e event.DeleteEvent) bool {
if log.IsLevelEnabled(log.DebugLevel) {
var appName string
app, isApp := e.Object.(*argov1alpha1.Application)
if isApp {
appName = app.QualifiedName()
}
log.WithField("app", appName).Debugln("received delete event from owning an application")
}
log.Debugln("received delete event from owning an application")
return true
},
UpdateFunc: func(e event.UpdateEvent) bool {
log.Debugln("received update event from owning an application")
appOld, isApp := e.ObjectOld.(*argov1alpha1.Application)
if !isApp {
return false
}
logCtx := log.WithField("app", appOld.QualifiedName())
logCtx.Debugln("received update event from owning an application")
appNew, isApp := e.ObjectNew.(*argov1alpha1.Application)
if !isApp {
return false
}
requeue := shouldRequeueApplicationSet(appOld, appNew, enableProgressiveSyncs)
logCtx.WithField("requeue", requeue).Debugf("requeue: %t caused by application %s\n", requeue, appNew.Name)
log.Debugf("requeue: %t caused by application %s\n", requeue, appNew.Name)
return requeue
},
GenericFunc: func(e event.GenericEvent) bool {
if log.IsLevelEnabled(log.DebugLevel) {
var appName string
app, isApp := e.Object.(*argov1alpha1.Application)
if isApp {
appName = app.QualifiedName()
}
log.WithField("app", appName).Debugln("received generic event from owning an application")
}
log.Debugln("received generic event from owning an application")
return true
},
}
@@ -1468,13 +1446,10 @@ func shouldRequeueApplicationSet(appOld *argov1alpha1.Application, appNew *argov
}
// the applicationset controller owns the application spec, labels, annotations, and finalizers on the applications
// reflect.DeepEqual considers nil slices/maps not equal to empty slices/maps
// https://pkg.go.dev/reflect#DeepEqual
// 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{})) ||
!cmp.Equal(appOld.ObjectMeta.GetAnnotations(), appNew.ObjectMeta.GetAnnotations(), cmpopts.EquateEmpty()) ||
!cmp.Equal(appOld.ObjectMeta.GetLabels(), appNew.ObjectMeta.GetLabels(), cmpopts.EquateEmpty()) ||
!cmp.Equal(appOld.ObjectMeta.GetFinalizers(), appNew.ObjectMeta.GetFinalizers(), cmpopts.EquateEmpty()) {
if !reflect.DeepEqual(appOld.Spec, appNew.Spec) ||
!reflect.DeepEqual(appOld.ObjectMeta.GetAnnotations(), appNew.ObjectMeta.GetAnnotations()) ||
!reflect.DeepEqual(appOld.ObjectMeta.GetLabels(), appNew.ObjectMeta.GetLabels()) ||
!reflect.DeepEqual(appOld.ObjectMeta.GetFinalizers(), appNew.ObjectMeta.GetFinalizers()) {
return true
}

File diff suppressed because it is too large Load Diff

View File

@@ -19,25 +19,25 @@ import (
// clusterSecretEventHandler is used when watching Secrets to check if they are ArgoCD Cluster Secrets, and if so
// requeue any related ApplicationSets.
type clusterSecretEventHandler struct {
// handler.EnqueueRequestForOwner
//handler.EnqueueRequestForOwner
Log log.FieldLogger
Client client.Client
}
func (h *clusterSecretEventHandler) Create(ctx context.Context, e event.CreateEvent, q workqueue.RateLimitingInterface) {
h.queueRelatedAppGenerators(ctx, q, e.Object)
func (h *clusterSecretEventHandler) Create(e event.CreateEvent, q workqueue.RateLimitingInterface) {
h.queueRelatedAppGenerators(q, e.Object)
}
func (h *clusterSecretEventHandler) Update(ctx context.Context, e event.UpdateEvent, q workqueue.RateLimitingInterface) {
h.queueRelatedAppGenerators(ctx, q, e.ObjectNew)
func (h *clusterSecretEventHandler) Update(e event.UpdateEvent, q workqueue.RateLimitingInterface) {
h.queueRelatedAppGenerators(q, e.ObjectNew)
}
func (h *clusterSecretEventHandler) Delete(ctx context.Context, e event.DeleteEvent, q workqueue.RateLimitingInterface) {
h.queueRelatedAppGenerators(ctx, q, e.Object)
func (h *clusterSecretEventHandler) Delete(e event.DeleteEvent, q workqueue.RateLimitingInterface) {
h.queueRelatedAppGenerators(q, e.Object)
}
func (h *clusterSecretEventHandler) Generic(ctx context.Context, e event.GenericEvent, q workqueue.RateLimitingInterface) {
h.queueRelatedAppGenerators(ctx, q, e.Object)
func (h *clusterSecretEventHandler) Generic(e event.GenericEvent, q workqueue.RateLimitingInterface) {
h.queueRelatedAppGenerators(q, e.Object)
}
// addRateLimitingInterface defines the Add method of workqueue.RateLimitingInterface, allow us to easily mock
@@ -46,7 +46,7 @@ type addRateLimitingInterface interface {
Add(item interface{})
}
func (h *clusterSecretEventHandler) queueRelatedAppGenerators(ctx context.Context, q addRateLimitingInterface, object client.Object) {
func (h *clusterSecretEventHandler) queueRelatedAppGenerators(q addRateLimitingInterface, object client.Object) {
// Check for label, lookup all ApplicationSets that might match the cluster, queue them all
if object.GetLabels()[generators.ArgoCDSecretTypeLabel] != generators.ArgoCDSecretTypeCluster {
return
@@ -58,7 +58,7 @@ func (h *clusterSecretEventHandler) queueRelatedAppGenerators(ctx context.Contex
}).Info("processing event for cluster secret")
appSetList := &argoprojiov1alpha1.ApplicationSetList{}
err := h.Client.List(ctx, appSetList)
err := h.Client.List(context.Background(), appSetList)
if err != nil {
h.Log.WithError(err).Error("unable to list ApplicationSets")
return
@@ -66,6 +66,7 @@ func (h *clusterSecretEventHandler) queueRelatedAppGenerators(ctx context.Contex
h.Log.WithField("count", len(appSetList.Items)).Info("listed ApplicationSets")
for _, appSet := range appSetList.Items {
foundClusterGenerator := false
for _, generator := range appSet.Spec.Generators {
if generator.Clusters != nil {
@@ -108,6 +109,7 @@ func (h *clusterSecretEventHandler) queueRelatedAppGenerators(ctx context.Contex
}
}
if foundClusterGenerator {
// TODO: only queue the AppGenerator if the labels match this cluster
req := ctrl.Request{NamespacedName: types.NamespacedName{Namespace: appSet.Namespace, Name: appSet.Name}}
q.Add(req)

View File

@@ -1,12 +1,10 @@
package controllers
import (
"context"
"testing"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -21,12 +19,13 @@ import (
)
func TestClusterEventHandler(t *testing.T) {
scheme := runtime.NewScheme()
err := argov1alpha1.AddToScheme(scheme)
require.NoError(t, err)
assert.Nil(t, err)
err = argov1alpha1.AddToScheme(scheme)
require.NoError(t, err)
assert.Nil(t, err)
tests := []struct {
name string
@@ -535,7 +534,9 @@ func TestClusterEventHandler(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
appSetList := argov1alpha1.ApplicationSetList{
Items: test.items,
}
@@ -549,12 +550,14 @@ func TestClusterEventHandler(t *testing.T) {
mockAddRateLimitingInterface := mockAddRateLimitingInterface{}
handler.queueRelatedAppGenerators(context.Background(), &mockAddRateLimitingInterface, &test.secret)
handler.queueRelatedAppGenerators(&mockAddRateLimitingInterface, &test.secret)
assert.False(t, mockAddRateLimitingInterface.errorOccurred)
assert.ElementsMatch(t, mockAddRateLimitingInterface.addedItems, test.expectedRequests)
})
}
}
// Add checks the type, and adds it to the internal list of received additions
@@ -578,7 +581,7 @@ func TestNestedGeneratorHasClusterGenerator_NestedClusterGenerator(t *testing.T)
hasClusterGenerator, err := nestedGeneratorHasClusterGenerator(nested)
require.NoError(t, err)
assert.Nil(t, err)
assert.True(t, hasClusterGenerator)
}
@@ -605,7 +608,7 @@ func TestNestedGeneratorHasClusterGenerator_NestedMergeGenerator(t *testing.T) {
hasClusterGenerator, err := nestedGeneratorHasClusterGenerator(nested)
require.NoError(t, err)
assert.Nil(t, err)
assert.True(t, hasClusterGenerator)
}
@@ -632,6 +635,6 @@ func TestNestedGeneratorHasClusterGenerator_NestedMergeGeneratorWithInvalidJSON(
hasClusterGenerator, err := nestedGeneratorHasClusterGenerator(nested)
require.Error(t, err)
assert.NotNil(t, err)
assert.False(t, hasClusterGenerator)
}

View File

@@ -6,7 +6,6 @@ import (
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
@@ -26,7 +25,7 @@ func TestRequeueAfter(t *testing.T) {
ctx := context.Background()
scheme := runtime.NewScheme()
err := argov1alpha1.AddToScheme(scheme)
require.NoError(t, err)
assert.Nil(t, err)
gvrToListKind := map[schema.GroupVersionResource]string{{
Group: "mallard.io",
Version: "v1",
@@ -56,14 +55,14 @@ func TestRequeueAfter(t *testing.T) {
},
}
fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, duckType)
scmConfig := generators.NewSCMConfig("", []string{""}, true, nil)
terminalGenerators := map[string]generators.Generator{
"List": generators.NewListGenerator(),
"Clusters": generators.NewClusterGenerator(k8sClient, ctx, appClientset, "argocd"),
"Git": generators.NewGitGenerator(mockServer),
"SCMProvider": generators.NewSCMProviderGenerator(fake.NewClientBuilder().WithObjects(&corev1.Secret{}).Build(), scmConfig),
"SCMProvider": generators.NewSCMProviderGenerator(fake.NewClientBuilder().WithObjects(&corev1.Secret{}).Build(), generators.SCMAuthProviders{}, "", []string{""}),
"ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, fakeDynClient, appClientset, "argocd"),
"PullRequest": generators.NewPullRequestGenerator(k8sClient, scmConfig),
"PullRequest": generators.NewPullRequestGenerator(k8sClient, generators.SCMAuthProviders{}, "", []string{""}),
}
nestedGenerators := map[string]generators.Generator{

View File

@@ -1,43 +0,0 @@
package template
import (
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func applyTemplatePatch(app *appv1.Application, templatePatch string) (*appv1.Application, error) {
appString, err := json.Marshal(app)
if err != nil {
return nil, fmt.Errorf("error while marhsalling Application %w", err)
}
convertedTemplatePatch, err := utils.ConvertYAMLToJSON(templatePatch)
if err != nil {
return nil, fmt.Errorf("error while converting template to json %q: %w", convertedTemplatePatch, err)
}
if err := json.Unmarshal([]byte(convertedTemplatePatch), &appv1.Application{}); err != nil {
return nil, fmt.Errorf("invalid templatePatch %q: %w", convertedTemplatePatch, err)
}
data, err := strategicpatch.StrategicMergePatch(appString, []byte(convertedTemplatePatch), appv1.Application{})
if err != nil {
return nil, fmt.Errorf("error while applying templatePatch template to json %q: %w", convertedTemplatePatch, err)
}
finalApp := appv1.Application{}
err = json.Unmarshal(data, &finalApp)
if err != nil {
return nil, fmt.Errorf("error while unmarhsalling patched application: %w", err)
}
// Prevent changes to the `project` field. This helps prevent malicious template patches
finalApp.Spec.Project = app.Spec.Project
return &finalApp, nil
}

View File

@@ -1,249 +0,0 @@
package template
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func Test_ApplyTemplatePatch(t *testing.T) {
testCases := []struct {
name string
appTemplate *appv1.Application
templatePatch string
expectedApp *appv1.Application
}{
{
name: "patch with JSON",
appTemplate: &appv1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-guestbook",
Namespace: "namespace",
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
},
Spec: appv1.ApplicationSpec{
Project: "default",
Source: &appv1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: appv1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "guestbook",
},
},
},
templatePatch: `{
"metadata": {
"annotations": {
"annotation-some-key": "annotation-some-value"
}
},
"spec": {
"source": {
"helm": {
"valueFiles": [
"values.test.yaml",
"values.big.yaml"
]
}
},
"syncPolicy": {
"automated": {
"prune": true
}
}
}
}`,
expectedApp: &appv1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-guestbook",
Namespace: "namespace",
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Annotations: map[string]string{
"annotation-some-key": "annotation-some-value",
},
},
Spec: appv1.ApplicationSpec{
Project: "default",
Source: &appv1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
Helm: &appv1.ApplicationSourceHelm{
ValueFiles: []string{
"values.test.yaml",
"values.big.yaml",
},
},
},
Destination: appv1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "guestbook",
},
SyncPolicy: &appv1.SyncPolicy{
Automated: &appv1.SyncPolicyAutomated{
Prune: true,
},
},
},
},
},
{
name: "patch with YAML",
appTemplate: &appv1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-guestbook",
Namespace: "namespace",
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
},
Spec: appv1.ApplicationSpec{
Project: "default",
Source: &appv1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: appv1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "guestbook",
},
},
},
templatePatch: `
metadata:
annotations:
annotation-some-key: annotation-some-value
spec:
source:
helm:
valueFiles:
- values.test.yaml
- values.big.yaml
syncPolicy:
automated:
prune: true`,
expectedApp: &appv1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-guestbook",
Namespace: "namespace",
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Annotations: map[string]string{
"annotation-some-key": "annotation-some-value",
},
},
Spec: appv1.ApplicationSpec{
Project: "default",
Source: &appv1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
Helm: &appv1.ApplicationSourceHelm{
ValueFiles: []string{
"values.test.yaml",
"values.big.yaml",
},
},
},
Destination: appv1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "guestbook",
},
SyncPolicy: &appv1.SyncPolicy{
Automated: &appv1.SyncPolicyAutomated{
Prune: true,
},
},
},
},
},
{
name: "project field isn't overwritten",
appTemplate: &appv1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-guestbook",
Namespace: "namespace",
},
Spec: appv1.ApplicationSpec{
Project: "default",
Source: &appv1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: appv1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "guestbook",
},
},
},
templatePatch: `
spec:
project: my-project`,
expectedApp: &appv1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-guestbook",
Namespace: "namespace",
},
Spec: appv1.ApplicationSpec{
Project: "default",
Source: &appv1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: appv1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "guestbook",
},
},
},
},
}
for _, tc := range testCases {
tcc := tc
t.Run(tcc.name, func(t *testing.T) {
result, err := applyTemplatePatch(tcc.appTemplate, tcc.templatePatch)
require.NoError(t, err)
assert.Equal(t, *tcc.expectedApp, *result)
})
}
}
func TestError(t *testing.T) {
app := &appv1.Application{}
result, err := applyTemplatePatch(app, "hello world")
require.Error(t, err)
require.Nil(t, result)
}

View File

@@ -1,99 +0,0 @@
package template
import (
"fmt"
"sigs.k8s.io/controller-runtime/pkg/client"
log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v2/applicationset/generators"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
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) {
var res []argov1alpha1.Application
var firstError error
var applicationSetReason argov1alpha1.ApplicationSetReasonType
for _, requestedGenerator := range applicationSetInfo.Spec.Generators {
t, err := generators.Transform(requestedGenerator, g, applicationSetInfo.Spec.Template, &applicationSetInfo, map[string]interface{}{}, client)
if err != nil {
logCtx.WithError(err).WithField("generator", requestedGenerator).
Error("error generating application from params")
if firstError == nil {
firstError = err
applicationSetReason = argov1alpha1.ApplicationSetReasonApplicationParamsGenerationError
}
continue
}
for _, a := range t {
tmplApplication := GetTempApplication(a.Template)
for _, p := range a.Params {
app, err := renderer.RenderTemplateParams(tmplApplication, applicationSetInfo.Spec.SyncPolicy, p, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions)
if err != nil {
logCtx.WithError(err).WithField("params", a.Params).WithField("generator", requestedGenerator).
Error("error generating application from params")
if firstError == nil {
firstError = err
applicationSetReason = argov1alpha1.ApplicationSetReasonRenderTemplateParamsError
}
continue
}
if applicationSetInfo.Spec.TemplatePatch != nil {
patchedApplication, err := renderTemplatePatch(renderer, app, applicationSetInfo, p)
if err != nil {
log.WithError(err).WithField("params", a.Params).WithField("generator", requestedGenerator).
Error("error generating application from params")
if firstError == nil {
firstError = err
applicationSetReason = argov1alpha1.ApplicationSetReasonRenderTemplateParamsError
}
continue
}
app = patchedApplication
}
// The app's namespace must be the same as the AppSet's namespace to preserve the appsets-in-any-namespace
// security boundary.
app.Namespace = applicationSetInfo.Namespace
res = append(res, *app)
}
}
logCtx.WithField("generator", requestedGenerator).Infof("generated %d applications", len(res))
logCtx.WithField("generator", requestedGenerator).Debugf("apps from generator: %+v", res)
}
return res, applicationSetReason, firstError
}
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)
if err != nil {
return nil, fmt.Errorf("error replacing values in templatePatch: %w", err)
}
return applyTemplatePatch(app, replacedTemplate)
}
func GetTempApplication(applicationSetTemplate argov1alpha1.ApplicationSetTemplate) *argov1alpha1.Application {
var tmplApplication argov1alpha1.Application
tmplApplication.Annotations = applicationSetTemplate.Annotations
tmplApplication.Labels = applicationSetTemplate.Labels
tmplApplication.Namespace = applicationSetTemplate.Namespace
tmplApplication.Name = applicationSetTemplate.Name
tmplApplication.Spec = applicationSetTemplate.Spec
tmplApplication.Finalizers = applicationSetTemplate.Finalizers
return &tmplApplication
}

View File

@@ -1,349 +0,0 @@
package template
import (
"fmt"
"testing"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"github.com/argoproj/argo-cd/v2/applicationset/generators"
genmock "github.com/argoproj/argo-cd/v2/applicationset/generators/mocks"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
rendmock "github.com/argoproj/argo-cd/v2/applicationset/utils/mocks"
"github.com/argoproj/argo-cd/v2/pkg/apis/application"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/collections"
)
func TestGenerateApplications(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
err = v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
for _, c := range []struct {
name string
params []map[string]interface{}
template v1alpha1.ApplicationSetTemplate
generateParamsError error
rendererError error
expectErr bool
expectedReason v1alpha1.ApplicationSetReasonType
}{
{
name: "Generate two applications",
params: []map[string]interface{}{{"name": "app1"}, {"name": "app2"}},
template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "name",
Namespace: "namespace",
Labels: map[string]string{"label_name": "label_value"},
},
Spec: v1alpha1.ApplicationSpec{},
},
expectedReason: "",
},
{
name: "Handles error from the generator",
generateParamsError: fmt.Errorf("error"),
expectErr: true,
expectedReason: v1alpha1.ApplicationSetReasonApplicationParamsGenerationError,
},
{
name: "Handles error from the render",
params: []map[string]interface{}{{"name": "app1"}, {"name": "app2"}},
template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "name",
Namespace: "namespace",
Labels: map[string]string{"label_name": "label_value"},
},
Spec: v1alpha1.ApplicationSpec{},
},
rendererError: fmt.Errorf("error"),
expectErr: true,
expectedReason: v1alpha1.ApplicationSetReasonRenderTemplateParamsError,
},
} {
cc := c
app := v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "namespace",
},
TypeMeta: metav1.TypeMeta{
Kind: application.ApplicationKind,
APIVersion: "argoproj.io/v1alpha1",
},
}
t.Run(cc.name, func(t *testing.T) {
generatorMock := genmock.Generator{}
generator := v1alpha1.ApplicationSetGenerator{
List: &v1alpha1.ListGenerator{},
}
generatorMock.On("GenerateParams", &generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).
Return(cc.params, cc.generateParamsError)
generatorMock.On("GetTemplate", &generator).
Return(&v1alpha1.ApplicationSetTemplate{})
rendererMock := rendmock.Renderer{}
var expectedApps []v1alpha1.Application
if cc.generateParamsError == nil {
for _, p := range cc.params {
if cc.rendererError != nil {
rendererMock.On("RenderTemplateParams", GetTempApplication(cc.template), mock.AnythingOfType("*v1alpha1.ApplicationSetSyncPolicy"), p, false, []string(nil)).
Return(nil, cc.rendererError)
} else {
rendererMock.On("RenderTemplateParams", GetTempApplication(cc.template), mock.AnythingOfType("*v1alpha1.ApplicationSetSyncPolicy"), p, false, []string(nil)).
Return(&app, nil)
expectedApps = append(expectedApps, app)
}
}
}
generators := map[string]generators.Generator{
"List": &generatorMock,
}
renderer := &rendererMock
got, reason, err := GenerateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
Spec: v1alpha1.ApplicationSetSpec{
Generators: []v1alpha1.ApplicationSetGenerator{generator},
Template: cc.template,
},
},
generators,
renderer,
nil,
)
if cc.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
assert.Equal(t, expectedApps, got)
assert.Equal(t, cc.expectedReason, reason)
generatorMock.AssertNumberOfCalls(t, "GenerateParams", 1)
if cc.generateParamsError == nil {
rendererMock.AssertNumberOfCalls(t, "RenderTemplateParams", len(cc.params))
}
})
}
}
func TestMergeTemplateApplications(t *testing.T) {
for _, c := range []struct {
name string
params []map[string]interface{}
template v1alpha1.ApplicationSetTemplate
overrideTemplate v1alpha1.ApplicationSetTemplate
expectedMerged v1alpha1.ApplicationSetTemplate
expectedApps []v1alpha1.Application
}{
{
name: "Generate app",
params: []map[string]interface{}{{"name": "app1"}},
template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "name",
Namespace: "namespace",
Labels: map[string]string{"label_name": "label_value"},
},
Spec: v1alpha1.ApplicationSpec{},
},
overrideTemplate: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "test",
Labels: map[string]string{"foo": "bar"},
},
Spec: v1alpha1.ApplicationSpec{},
},
expectedMerged: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "test",
Namespace: "namespace",
Labels: map[string]string{"label_name": "label_value", "foo": "bar"},
},
Spec: v1alpha1.ApplicationSpec{},
},
expectedApps: []v1alpha1.Application{
{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "test",
Labels: map[string]string{"foo": "bar"},
},
Spec: v1alpha1.ApplicationSpec{},
},
},
},
} {
cc := c
t.Run(cc.name, func(t *testing.T) {
generatorMock := genmock.Generator{}
generator := v1alpha1.ApplicationSetGenerator{
List: &v1alpha1.ListGenerator{},
}
generatorMock.On("GenerateParams", &generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).
Return(cc.params, nil)
generatorMock.On("GetTemplate", &generator).
Return(&cc.overrideTemplate)
rendererMock := rendmock.Renderer{}
rendererMock.On("RenderTemplateParams", GetTempApplication(cc.expectedMerged), mock.AnythingOfType("*v1alpha1.ApplicationSetSyncPolicy"), cc.params[0], false, []string(nil)).
Return(&cc.expectedApps[0], nil)
generators := map[string]generators.Generator{
"List": &generatorMock,
}
renderer := &rendererMock
got, _, _ := GenerateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
Spec: v1alpha1.ApplicationSetSpec{
Generators: []v1alpha1.ApplicationSetGenerator{generator},
Template: cc.template,
},
},
generators,
renderer,
nil,
)
assert.Equal(t, cc.expectedApps, got)
})
}
}
// Test app generation from a go template application set using a pull request generator
func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) {
for _, cases := range []struct {
name string
params []map[string]interface{}
template v1alpha1.ApplicationSetTemplate
expectedApp []v1alpha1.Application
}{
{
name: "Generate an application from a go template application set manifest using a pull request generator",
params: []map[string]interface{}{
{
"number": "1",
"branch": "branch1",
"branch_slug": "branchSlug1",
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb",
"branch_slugify_default": "feat/a_really+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
"branch_slugify_smarttruncate_disabled": "feat/areallylongpullrequestnametotestargoslugificationandbranchnameshorteningfeature",
"branch_slugify_smarttruncate_enabled": "feat/testwithsmarttruncateenabledramdomlonglistofcharacters",
"labels": []string{"label1"},
},
},
template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "AppSet-{{.branch}}-{{.number}}",
Labels: map[string]string{
"app1": "{{index .labels 0}}",
"branch-test1": "AppSet-{{.branch_slugify_default | slugify }}",
"branch-test2": "AppSet-{{.branch_slugify_smarttruncate_disabled | slugify 49 false }}",
"branch-test3": "AppSet-{{.branch_slugify_smarttruncate_enabled | slugify 50 true }}",
},
},
Spec: v1alpha1.ApplicationSpec{
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://testurl/testRepo",
TargetRevision: "{{.head_short_sha}}",
},
Destination: v1alpha1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "AppSet-{{.branch_slug}}-{{.head_sha}}",
},
},
},
expectedApp: []v1alpha1.Application{
{
ObjectMeta: metav1.ObjectMeta{
Name: "AppSet-branch1-1",
Labels: map[string]string{
"app1": "label1",
"branch-test1": "AppSet-feat-a-really-long-pull-request-name-to-test-argo",
"branch-test2": "AppSet-feat-areallylongpullrequestnametotestargoslugific",
"branch-test3": "AppSet-feat",
},
},
Spec: v1alpha1.ApplicationSpec{
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://testurl/testRepo",
TargetRevision: "089d92cb",
},
Destination: v1alpha1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "AppSet-branchSlug1-089d92cbf9ff857a39e6feccd32798ca700fb958",
},
},
},
},
},
} {
t.Run(cases.name, func(t *testing.T) {
generatorMock := genmock.Generator{}
generator := v1alpha1.ApplicationSetGenerator{
PullRequest: &v1alpha1.PullRequestGenerator{},
}
generatorMock.On("GenerateParams", &generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).
Return(cases.params, nil)
generatorMock.On("GetTemplate", &generator).
Return(&cases.template, nil)
generators := map[string]generators.Generator{
"PullRequest": &generatorMock,
}
renderer := &utils.Render{}
gotApp, _, _ := GenerateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{
Spec: v1alpha1.ApplicationSetSpec{
GoTemplate: true,
Generators: []v1alpha1.ApplicationSetGenerator{{
PullRequest: &v1alpha1.PullRequestGenerator{},
}},
Template: cases.template,
},
},
generators,
renderer,
nil,
)
assert.EqualValues(t, cases.expectedApp[0].ObjectMeta.Name, gotApp[0].ObjectMeta.Name)
assert.EqualValues(t, cases.expectedApp[0].Spec.Source.TargetRevision, gotApp[0].Spec.Source.TargetRevision)
assert.EqualValues(t, cases.expectedApp[0].Spec.Destination.Namespace, gotApp[0].Spec.Destination.Namespace)
assert.True(t, collections.StringMapsEqual(cases.expectedApp[0].ObjectMeta.Labels, gotApp[0].ObjectMeta.Labels))
})
}
}

View File

@@ -38,6 +38,7 @@ type ClusterGenerator struct {
var render = &utils.Render{}
func NewClusterGenerator(c client.Client, ctx context.Context, clientset kubernetes.Interface, namespace string) Generator {
settingsManager := settings.NewSettingsManager(ctx, clientset, namespace)
g := &ClusterGenerator{
@@ -60,7 +61,8 @@ func (g *ClusterGenerator) GetTemplate(appSetGenerator *argoappsetv1alpha1.Appli
return &appSetGenerator.Clusters.Template
}
func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator, appSet *argoappsetv1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator, appSet *argoappsetv1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil {
return nil, EmptyAppSetGeneratorError
}
@@ -93,10 +95,12 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
secretsFound := []corev1.Secret{}
for _, cluster := range clustersFromArgoCD.Items {
// If there is a secret for this cluster, then it's a non-local cluster, so it will be
// handled by the next step.
if secretForCluster, exists := clusterSecrets[cluster.Name]; exists {
secretsFound = append(secretsFound, secretForCluster)
} else if !ignoreLocalClusters {
// If there is no secret for the cluster, it's the local cluster, so handle it here.
params := map[string]interface{}{}
@@ -181,4 +185,5 @@ func (g *ClusterGenerator) getSecretsByClusterName(appSetGenerator *argoappsetv1
}
return res, nil
}

View File

@@ -17,7 +17,6 @@ import (
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type possiblyErroringFakeCtrlRuntimeClient struct {
@@ -105,15 +104,11 @@ func TestGenerateParams(t *testing.T) {
"aaa": "{{ server }}",
"no-op": "{{ this-does-not-exist }}",
}, 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": "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",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production",
},
{"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",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production"},
{
"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "staging", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "staging", "values.aaa": "https://staging-01.example.com", "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",
},
{"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "staging", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "staging", "values.aaa": "https://staging-01.example.com", "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"},
{"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"},
},
@@ -129,15 +124,11 @@ func TestGenerateParams(t *testing.T) {
},
values: nil,
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",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production",
},
{"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"},
{
"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",
},
{"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"},
},
clientError: false,
expectedError: nil,
@@ -153,10 +144,8 @@ func TestGenerateParams(t *testing.T) {
"foo": "bar",
},
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",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production",
},
{"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"},
},
clientError: false,
expectedError: nil,
@@ -179,14 +168,10 @@ func TestGenerateParams(t *testing.T) {
"foo": "bar",
},
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",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging",
},
{
"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",
},
{"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"},
{"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"},
},
clientError: false,
expectedError: nil,
@@ -212,10 +197,8 @@ func TestGenerateParams(t *testing.T) {
"name": "baz",
},
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",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging",
},
{"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"},
},
clientError: false,
expectedError: nil,
@@ -237,7 +220,9 @@ func TestGenerateParams(t *testing.T) {
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
@@ -246,7 +231,7 @@ func TestGenerateParams(t *testing.T) {
testCase.clientError,
}
clusterGenerator := NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
var clusterGenerator = NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -260,14 +245,15 @@ func TestGenerateParams(t *testing.T) {
Selector: testCase.selector,
Values: testCase.values,
},
}, &applicationSetInfo, nil)
}, &applicationSetInfo)
if testCase.expectedError != nil {
require.EqualError(t, err, testCase.expectedError.Error())
assert.EqualError(t, err, testCase.expectedError.Error())
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, got)
}
})
}
}
@@ -608,7 +594,9 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
fakeClient := fake.NewClientBuilder().WithObjects(clusters...).Build()
@@ -617,7 +605,7 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
testCase.clientError,
}
clusterGenerator := NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
var clusterGenerator = NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -633,14 +621,15 @@ func TestGenerateParamsGoTemplate(t *testing.T) {
Selector: testCase.selector,
Values: testCase.values,
},
}, &applicationSetInfo, nil)
}, &applicationSetInfo)
if testCase.expectedError != nil {
require.EqualError(t, err, testCase.expectedError.Error())
assert.EqualError(t, err, testCase.expectedError.Error())
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, got)
}
})
}
}

View File

@@ -7,7 +7,6 @@ import (
"time"
log "github.com/sirupsen/logrus"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v2/util/settings"
@@ -33,6 +32,7 @@ type DuckTypeGenerator struct {
}
func NewDuckTypeGenerator(ctx context.Context, dynClient dynamic.Interface, clientset kubernetes.Interface, namespace string) Generator {
settingsManager := settings.NewSettingsManager(ctx, clientset, namespace)
g := &DuckTypeGenerator{
@@ -46,6 +46,7 @@ func NewDuckTypeGenerator(ctx context.Context, dynClient dynamic.Interface, clie
}
func (g *DuckTypeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
// Return a requeue default of 3 minutes, if no override is specified.
if appSetGenerator.ClusterDecisionResource.RequeueAfterSeconds != nil {
@@ -59,7 +60,8 @@ func (g *DuckTypeGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Appl
return &appSetGenerator.ClusterDecisionResource.Template
}
func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil {
return nil, EmptyAppSetGeneratorError
}
@@ -81,6 +83,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
// Read the configMapRef
cm, err := g.clientset.CoreV1().ConfigMaps(g.namespace).Get(g.ctx, appSetGenerator.ClusterDecisionResource.ConfigMapRef, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("error reading configMapRef: %w", err)
}
@@ -101,6 +104,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
if (resourceName == "" && labelSelector.MatchLabels == nil && labelSelector.MatchExpressions == 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)
return nil, fmt.Errorf("There is a problem with the definition of the ClusterDecisionResource generator")
}
@@ -118,11 +122,12 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
log.WithField("listOptions.LabelSelector", listOptions.LabelSelector).Info("selection type")
} else {
listOptions.FieldSelector = fields.OneTermEqualSelector("metadata.name", resourceName).String()
// metav1.Convert_fields_Selector_To_string(fields.).Sprintf("metadata.name=%s", resourceName)
//metav1.Convert_fields_Selector_To_string(fields.).Sprintf("metadata.name=%s", resourceName)
log.WithField("listOptions.FieldSelector", listOptions.FieldSelector).Info("selection type")
}
duckResources, err := g.dynClient.Resource(duckGVR).Namespace(g.namespace).List(g.ctx, listOptions)
if err != nil {
log.WithField("GVK", duckGVR).Warning("resources were not found")
return nil, err
@@ -144,6 +149,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
if matchKey == "" {
log.WithField("matchKey", matchKey).Warning("matchKey not found in " + cm.Name)
return nil, nil
}
res := []map[string]interface{}{}
@@ -161,6 +167,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
log.WithField("duckResourceStatus", duckResource.Object["status"]).Debug("found resource")
clusterDecisions = append(clusterDecisions, duckResource.Object["status"].(map[string]interface{})[statusListKey].([]interface{})...)
}
log.Infof("Number of decisions found: %v", len(clusterDecisions))
@@ -169,6 +176,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
if len(clusterDecisions) > 0 {
for _, cluster := range clusterDecisions {
// generated instance of cluster params
params := map[string]interface{}{}
@@ -186,6 +194,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
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
@@ -193,6 +202,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
found = true
break // Stop looking
}
}
if !found {

View File

@@ -6,7 +6,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -19,11 +18,9 @@ import (
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
const (
resourceApiVersion = "mallard.io/v1"
resourceKind = "ducks"
resourceName = "quak"
)
const resourceApiVersion = "mallard.io/v1"
const resourceKind = "ducks"
const resourceName = "quak"
func TestGenerateParamsForDuckType(t *testing.T) {
clusters := []client.Object{
@@ -282,7 +279,9 @@ func TestGenerateParamsForDuckType(t *testing.T) {
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
appClientset := kubefake.NewSimpleClientset(append(runtimeClusters, configMap)...)
gvrToListKind := map[schema.GroupVersionResource]string{{
@@ -293,7 +292,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, testCase.resource)
duckTypeGenerator := NewDuckTypeGenerator(context.Background(), fakeDynClient, appClientset, "namespace")
var duckTypeGenerator = NewDuckTypeGenerator(context.Background(), fakeDynClient, appClientset, "namespace")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -309,12 +308,12 @@ func TestGenerateParamsForDuckType(t *testing.T) {
LabelSelector: testCase.labelSelector,
Values: testCase.values,
},
}, &applicationSetInfo, nil)
}, &applicationSetInfo)
if testCase.expectedError != nil {
require.EqualError(t, err, testCase.expectedError.Error())
assert.EqualError(t, err, testCase.expectedError.Error())
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, got)
}
})
@@ -578,7 +577,9 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
appClientset := kubefake.NewSimpleClientset(append(runtimeClusters, configMap)...)
gvrToListKind := map[schema.GroupVersionResource]string{{
@@ -589,7 +590,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
fakeDynClient := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), gvrToListKind, testCase.resource)
duckTypeGenerator := NewDuckTypeGenerator(context.Background(), fakeDynClient, appClientset, "namespace")
var duckTypeGenerator = NewDuckTypeGenerator(context.Background(), fakeDynClient, appClientset, "namespace")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -607,12 +608,12 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
LabelSelector: testCase.labelSelector,
Values: testCase.values,
},
}, &applicationSetInfo, nil)
}, &applicationSetInfo)
if testCase.expectedError != nil {
require.EqualError(t, err, testCase.expectedError.Error())
assert.EqualError(t, err, testCase.expectedError.Error())
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, got)
}
})

View File

@@ -5,7 +5,6 @@ import (
"reflect"
"github.com/jeremywohl/flatten"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
@@ -27,7 +26,7 @@ type TransformResult struct {
}
// 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]interface{}, client client.Client) ([]TransformResult, error) {
func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, allGenerators map[string]Generator, baseTemplate argoprojiov1alpha1.ApplicationSetTemplate, appSet *argoprojiov1alpha1.ApplicationSet, genParams map[string]interface{}) ([]TransformResult, error) {
// 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
// so that, among other things, we can match on cluster urls.
@@ -65,7 +64,7 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
continue
}
}
params, err = g.GenerateParams(interpolatedGenerator, appSet, client)
params, err = g.GenerateParams(interpolatedGenerator, appSet)
if err != nil {
log.WithError(err).WithField("generator", g).
Error("error generating params")

View File

@@ -65,8 +65,8 @@ func TestMatchValues(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
listGenerator := NewListGenerator()
data := map[string]Generator{
var listGenerator = NewListGenerator()
var data = map[string]Generator{
"List": listGenerator,
}
@@ -84,13 +84,12 @@ func TestMatchValues(t *testing.T) {
List: &argov1alpha1.ListGenerator{
Elements: testCase.elements,
Template: emptyTemplate(),
},
},
}},
data,
emptyTemplate(),
&applicationSetInfo, nil, nil)
&applicationSetInfo, nil)
require.NoError(t, err)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, results[0].Params)
})
}
@@ -149,8 +148,8 @@ func TestMatchValuesGoTemplate(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
listGenerator := NewListGenerator()
data := map[string]Generator{
var listGenerator = NewListGenerator()
var data = map[string]Generator{
"List": listGenerator,
}
@@ -168,13 +167,12 @@ func TestMatchValuesGoTemplate(t *testing.T) {
List: &argov1alpha1.ListGenerator{
Elements: testCase.elements,
Template: emptyTemplate(),
},
},
}},
data,
emptyTemplate(),
&applicationSetInfo, nil, nil)
&applicationSetInfo, nil)
require.NoError(t, err)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, results[0].Params)
})
}
@@ -238,13 +236,12 @@ func TestTransForm(t *testing.T) {
Selector: metav1.LabelSelector{},
Template: argov1alpha1.ApplicationSetTemplate{},
Values: nil,
},
},
}},
testGenerators,
emptyTemplate(),
&applicationSetInfo, nil, nil)
&applicationSetInfo, nil)
require.NoError(t, err)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, results[0].Params)
})
}
@@ -346,11 +343,12 @@ func getMockClusterGenerator() Generator {
func getMockGitGenerator() Generator {
argoCDServiceMock := mocks.Repos{}
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return([]string{"app1", "app2", "app_3", "p1/app4"}, nil)
gitGenerator := NewGitGenerator(&argoCDServiceMock)
var gitGenerator = NewGitGenerator(&argoCDServiceMock)
return gitGenerator
}
func TestGetRelevantGenerators(t *testing.T) {
testGenerators := map[string]Generator{
"Clusters": getMockClusterGenerator(),
"Git": getMockGitGenerator(),
@@ -363,8 +361,7 @@ func TestGetRelevantGenerators(t *testing.T) {
requestedGenerator := &argov1alpha1.ApplicationSetGenerator{
List: &argov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}},
},
}
}}
relevantGenerators := GetRelevantGenerators(requestedGenerator, testGenerators)
assert.Len(t, relevantGenerators, 1)
@@ -407,8 +404,7 @@ func TestInterpolateGenerator(t *testing.T) {
"path-basename": "{{path.basename}}",
"path-zero": "{{path[0]}}",
"path-full": "{{path}}",
},
},
}},
},
}
gitGeneratorParams := map[string]interface{}{
@@ -462,8 +458,7 @@ func TestInterpolateGenerator_go(t *testing.T) {
"path-zero": "{{index .path.segments 0}}",
"path-full": "{{.path.path}}",
"kubernetes.io/environment": `{{default "foo" .my_label}}`,
},
},
}},
},
}
gitGeneratorParams := map[string]interface{}{
@@ -555,7 +550,7 @@ func TestInterpolateGeneratorError(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
got, err := InterpolateGenerator(tt.args.requestedGenerator, tt.args.params, tt.args.useGoTemplate, tt.args.goTemplateOptions)
if tt.expectedErrStr != "" {
require.EqualError(t, err, tt.expectedErrStr)
assert.EqualError(t, err, tt.expectedErrStr)
} else {
require.NoError(t, err)
}

View File

@@ -11,14 +11,11 @@ import (
"github.com/jeremywohl/flatten"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
"github.com/argoproj/argo-cd/v2/applicationset/services"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/gpg"
)
var _ Generator = (*GitGenerator)(nil)
@@ -39,6 +36,7 @@ func (g *GitGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Applicati
}
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 {
@@ -48,7 +46,8 @@ func (g *GitGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Appli
return DefaultRequeueAfterSeconds
}
func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]interface{}, error) {
func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil {
return nil, EmptyAppSetGeneratorError
}
@@ -59,27 +58,12 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
noRevisionCache := appSet.RefreshRequired()
var project string
if strings.Contains(appSet.Spec.Template.Spec.Project, "{{") {
project = appSetGenerator.Git.Template.Spec.Project
} else {
project = appSet.Spec.Template.Spec.Project
}
appProject := &argoprojiov1alpha1.AppProject{}
if err := client.Get(context.TODO(), types.NamespacedName{Name: appSet.Spec.Template.Spec.Project, Namespace: appSet.Namespace}, appProject); err != nil {
return nil, fmt.Errorf("error getting project %s: %w", project, err)
}
// we need to verify the signature on the Git revision if GPG is enabled
verifyCommit := appProject.Spec.SignatureKeys != nil && len(appProject.Spec.SignatureKeys) > 0 && gpg.IsGPGEnabled()
var err error
var res []map[string]interface{}
if len(appSetGenerator.Git.Directories) != 0 {
res, err = g.generateParamsForGitDirectories(appSetGenerator, noRevisionCache, verifyCommit, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
res, err = g.generateParamsForGitDirectories(appSetGenerator, noRevisionCache, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
} else if len(appSetGenerator.Git.Files) != 0 {
res, err = g.generateParamsForGitFiles(appSetGenerator, noRevisionCache, verifyCommit, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
res, err = g.generateParamsForGitFiles(appSetGenerator, noRevisionCache, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
} else {
return nil, EmptyAppSetGeneratorError
}
@@ -90,9 +74,10 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
return res, nil
}
func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit bool, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache bool, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
// Directories, not files
allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, noRevisionCache, verifyCommit)
allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, noRevisionCache)
if err != nil {
return nil, fmt.Errorf("error getting directories from repo: %w", err)
}
@@ -115,11 +100,12 @@ func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoproj
return res, nil
}
func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit bool, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache bool, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
// Get all files that match the requested path string, removing duplicates
allFiles := make(map[string][]byte)
for _, requestedPath := range appSetGenerator.Git.Files {
files, err := g.repos.GetFiles(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, requestedPath.Path, noRevisionCache, verifyCommit)
files, err := g.repos.GetFiles(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, requestedPath.Path, noRevisionCache)
if err != nil {
return nil, err
}
@@ -139,10 +125,11 @@ func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1al
// Generate params from each path, and return
res := []map[string]interface{}{}
for _, path := range allPaths {
// A JSON / YAML file path can contain multiple sets of parameters (ie it is an array)
paramsArray, err := g.generateParamsFromGitFile(path, allFiles[path], appSetGenerator.Git.Values, useGoTemplate, goTemplateOptions, appSetGenerator.Git.PathParamPrefix)
if err != nil {
return nil, fmt.Errorf("unable to process file '%s': %w", path, err)
return nil, fmt.Errorf("unable to process file '%s': %v", path, err)
}
res = append(res, paramsArray...)
@@ -160,17 +147,15 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
singleObj := make(map[string]interface{})
err = yaml.Unmarshal(fileContent, &singleObj)
if err != nil {
return nil, fmt.Errorf("unable to parse file: %w", err)
return nil, fmt.Errorf("unable to parse file: %v", err)
}
objectsFound = append(objectsFound, singleObj)
} else if len(objectsFound) == 0 {
// If file is valid but empty, add a default empty item
objectsFound = append(objectsFound, map[string]interface{}{})
}
res := []map[string]interface{}{}
for _, objectFound := range objectsFound {
params := map[string]interface{}{}
if useGoTemplate {
@@ -226,13 +211,13 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
return res, nil
}
func (g *GitGenerator) filterApps(directories []argoprojiov1alpha1.GitDirectoryGeneratorItem, allPaths []string) []string {
func (g *GitGenerator) filterApps(Directories []argoprojiov1alpha1.GitDirectoryGeneratorItem, allPaths []string) []string {
res := []string{}
for _, appPath := range allPaths {
appInclude := false
appExclude := false
// Iterating over each appPath and check whether directories object has requestedPath that matches the appPath
for _, requestedPath := range directories {
for _, requestedPath := range Directories {
match, err := path.Match(requestedPath.Path, appPath)
if err != nil {
log.WithError(err).WithField("requestedPath", requestedPath).
@@ -257,6 +242,7 @@ func (g *GitGenerator) filterApps(directories []argoprojiov1alpha1.GitDirectoryG
func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
res := make([]map[string]interface{}, len(requestedApps))
for i, a := range requestedApps {
params := make(map[string]interface{}, 5)
if useGoTemplate {

View File

@@ -6,179 +6,121 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func Test_generateParamsFromGitFile(t *testing.T) {
defaultContent := []byte(`
values := map[string]string{}
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
foo:
bar: baz
`)
type args struct {
filePath string
fileContent []byte
values map[string]string
useGoTemplate bool
goTemplateOptions []string
pathParamPrefix string
`), values, false, nil, "")
if err != nil {
t.Fatal(err)
}
tests := []struct {
name string
args args
want []map[string]interface{}
wantErr bool
}{
assert.Equal(t, []map[string]interface{}{
{
name: "empty file returns path parameters",
args: args{
filePath: "path/dir/file_name.yaml",
fileContent: []byte(""),
values: map[string]string{},
useGoTemplate: false,
},
want: []map[string]interface{}{
{
"path": "path/dir",
"path.basename": "dir",
"path.filename": "file_name.yaml",
"path.basenameNormalized": "dir",
"path.filenameNormalized": "file-name.yaml",
"path[0]": "path",
"path[1]": "dir",
},
},
},
{
name: "invalid json/yaml file returns error",
args: args{
filePath: "path/dir/file_name.yaml",
fileContent: []byte("this is not json or yaml"),
values: map[string]string{},
useGoTemplate: false,
},
wantErr: true,
},
{
name: "file parameters are added to params",
args: args{
filePath: "path/dir/file_name.yaml",
fileContent: defaultContent,
values: map[string]string{},
useGoTemplate: false,
},
want: []map[string]interface{}{
{
"foo.bar": "baz",
"path": "path/dir",
"path.basename": "dir",
"path.filename": "file_name.yaml",
"path.basenameNormalized": "dir",
"path.filenameNormalized": "file-name.yaml",
"path[0]": "path",
"path[1]": "dir",
},
},
},
{
name: "path parameter are prefixed",
args: args{
filePath: "path/dir/file_name.yaml",
fileContent: defaultContent,
values: map[string]string{},
useGoTemplate: false,
pathParamPrefix: "myRepo",
},
want: []map[string]interface{}{
{
"foo.bar": "baz",
"myRepo.path": "path/dir",
"myRepo.path.basename": "dir",
"myRepo.path.filename": "file_name.yaml",
"myRepo.path.basenameNormalized": "dir",
"myRepo.path.filenameNormalized": "file-name.yaml",
"myRepo.path[0]": "path",
"myRepo.path[1]": "dir",
},
},
},
{
name: "file parameters are added to params with go template",
args: args{
filePath: "path/dir/file_name.yaml",
fileContent: defaultContent,
values: map[string]string{},
useGoTemplate: true,
},
want: []map[string]interface{}{
{
"foo": map[string]interface{}{
"bar": "baz",
},
"path": map[string]interface{}{
"path": "path/dir",
"basename": "dir",
"filename": "file_name.yaml",
"basenameNormalized": "dir",
"filenameNormalized": "file-name.yaml",
"segments": []string{
"path",
"dir",
},
},
},
},
},
{
name: "path parameter are prefixed with go template",
args: args{
filePath: "path/dir/file_name.yaml",
fileContent: defaultContent,
values: map[string]string{},
useGoTemplate: true,
pathParamPrefix: "myRepo",
},
want: []map[string]interface{}{
{
"foo": map[string]interface{}{
"bar": "baz",
},
"myRepo": map[string]interface{}{
"path": map[string]interface{}{
"path": "path/dir",
"basename": "dir",
"filename": "file_name.yaml",
"basenameNormalized": "dir",
"filenameNormalized": "file-name.yaml",
"segments": []string{
"path",
"dir",
},
},
},
},
},
"foo.bar": "baz",
"path": "path/dir",
"path.basename": "dir",
"path.filename": "file_name.yaml",
"path.basenameNormalized": "dir",
"path.filenameNormalized": "file-name.yaml",
"path[0]": "path",
"path[1]": "dir",
},
}, params)
}
func Test_generatePrefixedParamsFromGitFile(t *testing.T) {
values := map[string]string{}
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
foo:
bar: baz
`), values, false, nil, "myRepo")
if err != nil {
t.Fatal(err)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
params, err := (*GitGenerator)(nil).generateParamsFromGitFile(tt.args.filePath, tt.args.fileContent, tt.args.values, tt.args.useGoTemplate, tt.args.goTemplateOptions, tt.args.pathParamPrefix)
if (err != nil) != tt.wantErr {
t.Errorf("GitGenerator.generateParamsFromGitFile() error = %v, wantErr %v", err, tt.wantErr)
return
}
assert.Equal(t, tt.want, params)
})
assert.Equal(t, []map[string]interface{}{
{
"foo.bar": "baz",
"myRepo.path": "path/dir",
"myRepo.path.basename": "dir",
"myRepo.path.filename": "file_name.yaml",
"myRepo.path.basenameNormalized": "dir",
"myRepo.path.filenameNormalized": "file-name.yaml",
"myRepo.path[0]": "path",
"myRepo.path[1]": "dir",
},
}, params)
}
func Test_generateParamsFromGitFileGoTemplate(t *testing.T) {
values := map[string]string{}
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
foo:
bar: baz
`), values, true, nil, "")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, []map[string]interface{}{
{
"foo": map[string]interface{}{
"bar": "baz",
},
"path": map[string]interface{}{
"path": "path/dir",
"basename": "dir",
"filename": "file_name.yaml",
"basenameNormalized": "dir",
"filenameNormalized": "file-name.yaml",
"segments": []string{
"path",
"dir",
},
},
},
}, params)
}
func Test_generatePrefixedParamsFromGitFileGoTemplate(t *testing.T) {
values := map[string]string{}
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
foo:
bar: baz
`), values, true, nil, "myRepo")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, []map[string]interface{}{
{
"foo": map[string]interface{}{
"bar": "baz",
},
"myRepo": map[string]interface{}{
"path": map[string]interface{}{
"path": "path/dir",
"basename": "dir",
"filename": "file_name.yaml",
"basenameNormalized": "dir",
"filenameNormalized": "file-name.yaml",
"segments": []string{
"path",
"dir",
},
},
},
},
}, params)
}
func TestGitGenerateParamsFromDirectories(t *testing.T) {
cases := []struct {
name string
directories []argoprojiov1alpha1.GitDirectoryGeneratorItem
@@ -321,9 +263,9 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
argoCDServiceMock := mocks.Repos{}
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
gitGenerator := NewGitGenerator(&argoCDServiceMock)
var gitGenerator = NewGitGenerator(&argoCDServiceMock)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -341,19 +283,12 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
},
}
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
appProject := argoprojiov1alpha1.AppProject{}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, client)
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
if testCaseCopy.expectedError != nil {
require.EqualError(t, err, testCaseCopy.expectedError.Error())
assert.EqualError(t, err, testCaseCopy.expectedError.Error())
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got)
}
@@ -363,6 +298,7 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
}
func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
cases := []struct {
name string
directories []argoprojiov1alpha1.GitDirectoryGeneratorItem
@@ -562,6 +498,7 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
},
repoError: nil,
expected: []map[string]interface{}{
{
"path": map[string]interface{}{
"path": "app1",
@@ -622,9 +559,9 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
argoCDServiceMock := mocks.Repos{}
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
gitGenerator := NewGitGenerator(&argoCDServiceMock)
var gitGenerator = NewGitGenerator(&argoCDServiceMock)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -642,28 +579,23 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
},
}
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
appProject := argoprojiov1alpha1.AppProject{}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, client)
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
if testCaseCopy.expectedError != nil {
require.EqualError(t, err, testCaseCopy.expectedError.Error())
assert.EqualError(t, err, testCaseCopy.expectedError.Error())
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got)
}
argoCDServiceMock.AssertExpectations(t)
})
}
}
func TestGitGenerateParamsFromFiles(t *testing.T) {
cases := []struct {
name string
// files is the list of paths/globs to match
@@ -986,10 +918,10 @@ cluster:
t.Parallel()
argoCDServiceMock := mocks.Repos{}
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
gitGenerator := NewGitGenerator(&argoCDServiceMock)
var gitGenerator = NewGitGenerator(&argoCDServiceMock)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -1006,20 +938,13 @@ cluster:
},
}
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
appProject := argoprojiov1alpha1.AppProject{}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, client)
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
fmt.Println(got, err)
if testCaseCopy.expectedError != nil {
require.EqualError(t, err, testCaseCopy.expectedError.Error())
assert.EqualError(t, err, testCaseCopy.expectedError.Error())
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.ElementsMatch(t, testCaseCopy.expected, got)
}
@@ -1029,6 +954,7 @@ cluster:
}
func TestGitGenerateParamsFromFilesGoTemplate(t *testing.T) {
cases := []struct {
name string
// files is the list of paths/globs to match
@@ -1342,10 +1268,10 @@ cluster:
t.Parallel()
argoCDServiceMock := mocks.Repos{}
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
gitGenerator := NewGitGenerator(&argoCDServiceMock)
var gitGenerator = NewGitGenerator(&argoCDServiceMock)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -1362,20 +1288,13 @@ cluster:
},
}
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
appProject := argoprojiov1alpha1.AppProject{}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, client)
got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
fmt.Println(got, err)
if testCaseCopy.expectedError != nil {
require.EqualError(t, err, testCaseCopy.expectedError.Error())
assert.EqualError(t, err, testCaseCopy.expectedError.Error())
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.ElementsMatch(t, testCaseCopy.expected, got)
}

View File

@@ -4,19 +4,15 @@ import (
"fmt"
"time"
"sigs.k8s.io/controller-runtime/pkg/client"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
//go:generate go run github.com/vektra/mockery/v2@v2.40.2 --name=Generator
// Generator defines the interface implemented by all ApplicationSet generators.
type Generator interface {
// 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
// against the current state of the Applications in the cluster.
GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]interface{}, error)
GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error)
// 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.
@@ -27,10 +23,8 @@ type Generator interface {
GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate
}
var (
EmptyAppSetGeneratorError = fmt.Errorf("ApplicationSet is empty")
NoRequeueAfter time.Duration
)
var EmptyAppSetGeneratorError = fmt.Errorf("ApplicationSet is empty")
var NoRequeueAfter time.Duration
// DefaultRequeueAfterSeconds is used when GetRequeueAfter is not specified, it is the default time to wait before the next reconcile loop
const (

View File

@@ -5,7 +5,6 @@ import (
"fmt"
"time"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
@@ -13,7 +12,8 @@ import (
var _ Generator = (*ListGenerator)(nil)
type ListGenerator struct{}
type ListGenerator struct {
}
func NewListGenerator() Generator {
g := &ListGenerator{}
@@ -28,7 +28,7 @@ func (g *ListGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Applicat
return &appSetGenerator.List.Template
}
func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil {
return nil, EmptyAppSetGeneratorError
}
@@ -44,7 +44,7 @@ func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appli
var element map[string]interface{}
err := json.Unmarshal(tmpItem.Raw, &element)
if err != nil {
return nil, fmt.Errorf("error unmarshling list element %w", err)
return nil, fmt.Errorf("error unmarshling list element %v", err)
}
if appSet.Spec.GoTemplate {
@@ -59,14 +59,14 @@ func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appli
for k, v := range values {
value, ok := v.(string)
if !ok {
return nil, fmt.Errorf("error parsing value as string %w", err)
return nil, fmt.Errorf("error parsing value as string %v", err)
}
params[fmt.Sprintf("values.%s", k)] = value
}
} else {
v, ok := value.(string)
if !ok {
return nil, fmt.Errorf("error parsing value as string %w", err)
return nil, fmt.Errorf("error parsing value as string %v", err)
}
params[key] = v
}
@@ -77,10 +77,11 @@ func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appli
// Append elements from ElementsYaml to the response
if len(appSetGenerator.List.ElementsYaml) > 0 {
var yamlElements []map[string]interface{}
err := yaml.Unmarshal([]byte(appSetGenerator.List.ElementsYaml), &yamlElements)
if err != nil {
return nil, fmt.Errorf("error unmarshling decoded ElementsYaml %w", err)
return nil, fmt.Errorf("error unmarshling decoded ElementsYaml %v", err)
}
res = append(res, yamlElements...)
}

View File

@@ -4,7 +4,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -26,7 +25,8 @@ func TestGenerateListParams(t *testing.T) {
}
for _, testCase := range testCases {
listGenerator := NewListGenerator()
var listGenerator = NewListGenerator()
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -38,11 +38,11 @@ func TestGenerateListParams(t *testing.T) {
got, err := listGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
List: &argoprojiov1alpha1.ListGenerator{
Elements: testCase.elements,
},
}, &applicationSetInfo, nil)
}}, &applicationSetInfo)
require.NoError(t, err)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, got)
}
}
@@ -61,7 +61,8 @@ func TestGenerateListParamsGoTemplate(t *testing.T) {
}
for _, testCase := range testCases {
listGenerator := NewListGenerator()
var listGenerator = NewListGenerator()
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -75,10 +76,9 @@ func TestGenerateListParamsGoTemplate(t *testing.T) {
got, err := listGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
List: &argoprojiov1alpha1.ListGenerator{
Elements: testCase.elements,
},
}, &applicationSetInfo, nil)
}}, &applicationSetInfo)
require.NoError(t, err)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, got)
}
}

View File

@@ -5,7 +5,6 @@ import (
"time"
"github.com/imdario/mergo"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
@@ -33,7 +32,8 @@ func NewMatrixGenerator(supportedGenerators map[string]Generator) Generator {
return m
}
func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]interface{}, error) {
func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator.Matrix == nil {
return nil, EmptyAppSetGeneratorError
}
@@ -48,16 +48,17 @@ func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.App
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)
if err != nil {
return nil, fmt.Errorf("error failed to get params for first generator in matrix generator: %w", err)
}
for _, a := range g0 {
g1, err := m.getParams(appSetGenerator.Matrix.Generators[1], appSet, a, client)
g1, err := m.getParams(appSetGenerator.Matrix.Generators[1], appSet, a)
if err != nil {
return nil, fmt.Errorf("failed to get params for second generator in the matrix generator: %w", err)
}
for _, b := range g1 {
if appSet.Spec.GoTemplate {
tmp := map[string]interface{}{}
if err := mergo.Merge(&tmp, b, mergo.WithOverride); err != nil {
@@ -80,7 +81,7 @@ func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.App
return res, nil
}
func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, params map[string]interface{}, client client.Client) ([]map[string]interface{}, error) {
func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, params map[string]interface{}) ([]map[string]interface{}, error) {
matrixGen, err := getMatrixGenerator(appSetBaseGenerator)
if err != nil {
return nil, err
@@ -118,10 +119,10 @@ func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Appli
m.supportedGenerators,
argoprojiov1alpha1.ApplicationSetTemplate{},
appSet,
params,
client)
params)
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: %v", err)
}
if len(t) == 0 {
@@ -171,6 +172,7 @@ func (m *MatrixGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Ap
} else {
return NoRequeueAfter
}
}
func getMatrixGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*argoprojiov1alpha1.MatrixGenerator, error) {

View File

@@ -19,11 +19,11 @@ import (
"github.com/stretchr/testify/mock"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"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) {
gitGenerator := &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL",
Revision: "Revision",
@@ -147,11 +147,12 @@ func TestMatrixGenerate(t *testing.T) {
}
for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git,
List: g.List,
}
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet, mock.Anything).Return([]map[string]interface{}{
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]interface{}{
{
"path": "app1",
"path.basename": "app1",
@@ -168,7 +169,7 @@ func TestMatrixGenerate(t *testing.T) {
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
}
matrixGenerator := NewMatrixGenerator(
var matrixGenerator = NewMatrixGenerator(
map[string]Generator{
"Git": genMock,
"List": &ListGenerator{},
@@ -180,19 +181,22 @@ func TestMatrixGenerate(t *testing.T) {
Generators: testCaseCopy.baseGenerators,
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
},
}, appSet, nil)
}, appSet)
if testCaseCopy.expectedErr != nil {
require.ErrorIs(t, err, testCaseCopy.expectedErr)
assert.ErrorIs(t, err, testCaseCopy.expectedErr)
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got)
}
})
}
}
func TestMatrixGenerateGoTemplate(t *testing.T) {
gitGenerator := &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL",
Revision: "Revision",
@@ -356,11 +360,12 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
}
for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git,
List: g.List,
}
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet, mock.Anything).Return([]map[string]interface{}{
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]interface{}{
{
"path": map[string]string{
"path": "app1",
@@ -381,7 +386,7 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
}
matrixGenerator := NewMatrixGenerator(
var matrixGenerator = NewMatrixGenerator(
map[string]Generator{
"Git": genMock,
"List": &ListGenerator{},
@@ -393,19 +398,22 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
Generators: testCaseCopy.baseGenerators,
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
},
}, appSet, nil)
}, appSet)
if testCaseCopy.expectedErr != nil {
require.ErrorIs(t, err, testCaseCopy.expectedErr)
assert.ErrorIs(t, err, testCaseCopy.expectedErr)
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got)
}
})
}
}
func TestMatrixGetRequeueAfter(t *testing.T) {
gitGenerator := &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL",
Revision: "Revision",
@@ -522,7 +530,7 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
mock.On("GetRequeueAfter", &gitGeneratorSpec).Return(testCaseCopy.gitGetRequeueAfter, nil)
}
matrixGenerator := NewMatrixGenerator(
var matrixGenerator = NewMatrixGenerator(
map[string]Generator{
"Git": mock,
"List": &ListGenerator{},
@@ -540,7 +548,9 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
})
assert.Equal(t, testCaseCopy.expected, got)
})
}
}
@@ -645,9 +655,10 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
fakeClient,
testCase.clientError,
}
clusterGenerator := NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
var clusterGenerator = NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git,
Clusters: g.Clusters,
@@ -667,7 +678,7 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
}
matrixGenerator := NewMatrixGenerator(
var matrixGenerator = NewMatrixGenerator(
map[string]Generator{
"Git": genMock,
"Clusters": clusterGenerator,
@@ -679,14 +690,15 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
Generators: testCaseCopy.baseGenerators,
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
},
}, appSet, nil)
}, appSet)
if testCaseCopy.expectedErr != nil {
require.ErrorIs(t, err, testCaseCopy.expectedErr)
assert.ErrorIs(t, err, testCaseCopy.expectedErr)
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got)
}
})
}
}
@@ -826,14 +838,16 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
fakeClient,
testCase.clientError,
}
clusterGenerator := NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
var clusterGenerator = NewClusterGenerator(cl, context.Background(), appClientset, "namespace")
for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git,
Clusters: g.Clusters,
}
genMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), appSet).Return([]map[string]interface{}{
{
"path": map[string]string{
"path": "examples/git-generator-files-discovery/cluster-config/dev/config.json",
@@ -852,7 +866,7 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
}
matrixGenerator := NewMatrixGenerator(
var matrixGenerator = NewMatrixGenerator(
map[string]Generator{
"Git": genMock,
"Clusters": clusterGenerator,
@@ -864,19 +878,22 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
Generators: testCaseCopy.baseGenerators,
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
},
}, appSet, nil)
}, appSet)
if testCaseCopy.expectedErr != nil {
require.ErrorIs(t, err, testCaseCopy.expectedErr)
assert.ErrorIs(t, err, testCaseCopy.expectedErr)
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got)
}
})
}
}
func TestMatrixGenerateListElementsYaml(t *testing.T) {
gitGenerator := &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL",
Revision: "Revision",
@@ -980,6 +997,7 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
}
for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git,
List: g.List,
@@ -1011,9 +1029,10 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
}}, nil)
genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
}
matrixGenerator := NewMatrixGenerator(
var matrixGenerator = NewMatrixGenerator(
map[string]Generator{
"Git": genMock,
"List": &ListGenerator{},
@@ -1025,15 +1044,17 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
Generators: testCaseCopy.baseGenerators,
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
},
}, appSet, nil)
}, appSet)
if testCaseCopy.expectedErr != nil {
require.ErrorIs(t, err, testCaseCopy.expectedErr)
assert.ErrorIs(t, err, testCaseCopy.expectedErr)
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got)
}
})
}
}
@@ -1047,7 +1068,7 @@ func (g *generatorMock) GetTemplate(appSetGenerator *argoprojiov1alpha1.Applicat
return args.Get(0).(*argoprojiov1alpha1.ApplicationSetTemplate)
}
func (g *generatorMock) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
func (g *generatorMock) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
args := g.Called(appSetGenerator, appSet)
return args.Get(0).([]map[string]interface{}), args.Error(1)
@@ -1057,6 +1078,7 @@ func (g *generatorMock) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Appl
args := g.Called(appSetGenerator)
return args.Get(0).(time.Duration)
}
func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
@@ -1073,7 +1095,7 @@ func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
// of that bug.
listGeneratorMock := &generatorMock{}
listGeneratorMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).Return([]map[string]interface{}{
listGeneratorMock.On("GenerateParams", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator"), mock.AnythingOfType("*v1alpha1.ApplicationSet")).Return([]map[string]interface{}{
{"some": "value"},
}, nil)
listGeneratorMock.On("GetTemplate", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator")).Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
@@ -1086,7 +1108,7 @@ func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
}
repoServiceMock := &mocks.Repos{}
repoServiceMock.On("GetFiles", 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).Return(map[string][]byte{
"some/path.json": []byte("test: content"),
}, nil)
gitGenerator := NewGitGenerator(repoServiceMock)
@@ -1112,17 +1134,9 @@ func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
},
},
}
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
appProject := argoprojiov1alpha1.AppProject{}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
params, err := matrixGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
Matrix: matrixGeneratorSpec,
}, &argoprojiov1alpha1.ApplicationSet{}, client)
}, &argoprojiov1alpha1.ApplicationSet{})
require.NoError(t, err)
assert.Equal(t, []map[string]interface{}{{
"path": "some",

View File

@@ -6,7 +6,6 @@ import (
"time"
"github.com/imdario/mergo"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
@@ -37,10 +36,10 @@ func NewMergeGenerator(supportedGenerators map[string]Generator) Generator {
// 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.
func (m *MergeGenerator) getParamSetsForAllGenerators(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([][]map[string]interface{}, error) {
func (m *MergeGenerator) getParamSetsForAllGenerators(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([][]map[string]interface{}, error) {
var paramSets [][]map[string]interface{}
for i, generator := range generators {
generatorParamSets, err := m.getParams(generator, appSet, client)
generatorParamSets, err := m.getParams(generator, appSet)
if err != nil {
return nil, fmt.Errorf("error getting params from generator %d of %d: %w", i+1, len(generators), err)
}
@@ -51,7 +50,7 @@ func (m *MergeGenerator) getParamSetsForAllGenerators(generators []argoprojiov1a
}
// GenerateParams gets the params produced by the MergeGenerator.
func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]interface{}, error) {
func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator.Merge == nil {
return nil, EmptyAppSetGeneratorError
}
@@ -60,7 +59,7 @@ func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appl
return nil, ErrLessThanTwoGeneratorsInMerge
}
paramSetsFromGenerators, err := m.getParamSetsForAllGenerators(appSetGenerator.Merge.Generators, appSet, client)
paramSetsFromGenerators, err := m.getParamSetsForAllGenerators(appSetGenerator.Merge.Generators, appSet)
if err != nil {
return nil, fmt.Errorf("error getting param sets from generators: %w", err)
}
@@ -78,6 +77,7 @@ func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appl
for mergeKeyValue, baseParamSet := range baseParamSetsByMergeKey {
if overrideParamSet, exists := paramSetsByMergeKey[mergeKeyValue]; exists {
if appSet.Spec.GoTemplate {
if err := mergo.Merge(&baseParamSet, overrideParamSet, mergo.WithOverride); err != nil {
return nil, fmt.Errorf("error merging base param set with override param set: %w", err)
@@ -95,7 +95,7 @@ func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appl
}
mergedParamSets := make([]map[string]interface{}, len(baseParamSetsByMergeKey))
i := 0
var i = 0
for _, mergedParamSet := range baseParamSetsByMergeKey {
mergedParamSets[i] = mergedParamSet
i += 1
@@ -138,7 +138,7 @@ func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]interface
}
// getParams get the parameters generated by this generator.
func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]interface{}, error) {
func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
matrixGen, err := getMatrixGenerator(appSetBaseGenerator)
if err != nil {
return nil, err
@@ -176,9 +176,10 @@ func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Applic
m.supportedGenerators,
argoprojiov1alpha1.ApplicationSetTemplate{},
appSet,
map[string]interface{}{}, client)
map[string]interface{}{})
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: %v", err)
}
if len(t) == 0 {
@@ -226,6 +227,7 @@ func (m *MergeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.App
} else {
return NoRequeueAfter
}
}
func getMergeGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*argoprojiov1alpha1.MergeGenerator, error) {

View File

@@ -6,7 +6,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
@@ -50,6 +49,7 @@ func listOfMapsToSet(maps []map[string]interface{}) (map[string]bool, error) {
}
func TestMergeGenerate(t *testing.T) {
testCases := []struct {
name string
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
@@ -156,7 +156,7 @@ func TestMergeGenerate(t *testing.T) {
appSet := &argoprojiov1alpha1.ApplicationSet{}
mergeGenerator := NewMergeGenerator(
var mergeGenerator = NewMergeGenerator(
map[string]Generator{
"List": &ListGenerator{},
"Matrix": &MatrixGenerator{
@@ -178,18 +178,18 @@ func TestMergeGenerate(t *testing.T) {
MergeKeys: testCaseCopy.mergeKeys,
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
},
}, appSet, nil)
}, appSet)
if testCaseCopy.expectedErr != nil {
require.EqualError(t, err, testCaseCopy.expectedErr.Error())
assert.EqualError(t, err, testCaseCopy.expectedErr.Error())
} else {
expectedSet, err := listOfMapsToSet(testCaseCopy.expected)
require.NoError(t, err)
assert.NoError(t, err)
actualSet, err := listOfMapsToSet(got)
require.NoError(t, err)
assert.NoError(t, err)
require.NoError(t, err)
assert.NoError(t, err)
assert.Equal(t, expectedSet, actualSet)
}
})
@@ -197,6 +197,7 @@ func TestMergeGenerate(t *testing.T) {
}
func toAPIExtensionsJSON(t *testing.T, g interface{}) *apiextensionsv1.JSON {
resVal, err := json.Marshal(g)
if err != nil {
t.Error("unable to unmarshal json", g)
@@ -338,11 +339,13 @@ func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
got, err := getParamSetsByMergeKey(testCaseCopy.mergeKeys, testCaseCopy.paramSets)
if testCaseCopy.expectedErr != nil {
require.EqualError(t, err, testCaseCopy.expectedErr.Error())
assert.EqualError(t, err, testCaseCopy.expectedErr.Error())
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got)
}
})
}
}

View File

@@ -1,100 +0,0 @@
// Code generated by mockery v2.40.2. DO NOT EDIT.
package mocks
import (
client "sigs.k8s.io/controller-runtime/pkg/client"
mock "github.com/stretchr/testify/mock"
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.
// The first argument is typically a *testing.T value.
func NewGenerator(t interface {
mock.TestingT
Cleanup(func())
}) *Generator {
mock := &Generator{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@@ -55,7 +55,8 @@ func (g *PluginGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Applic
return &appSetGenerator.Plugin.Template
}
func (g *PluginGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
func (g *PluginGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil {
return nil, EmptyAppSetGeneratorError
}
@@ -93,7 +94,7 @@ func (g *PluginGenerator) getPluginFromGenerator(ctx context.Context, appSetName
}
token, err := g.getToken(ctx, cm["token"])
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err)
return nil, fmt.Errorf("error fetching Secret token: %v", err)
}
var requestTimeout int
@@ -116,6 +117,7 @@ func (g *PluginGenerator) generateParams(appSetGenerator *argoprojiov1alpha1.App
res := []map[string]interface{}{}
for _, objectFound := range objectsFound {
params := map[string]interface{}{}
if useGoTemplate {
@@ -150,6 +152,7 @@ func (g *PluginGenerator) generateParams(appSetGenerator *argoprojiov1alpha1.App
}
func (g *PluginGenerator) getToken(ctx context.Context, tokenRef string) (string, error) {
if tokenRef == "" || !strings.HasPrefix(tokenRef, "$") {
return "", fmt.Errorf("token is empty, or does not reference a secret key starting with '$': %v", tokenRef)
}
@@ -164,8 +167,9 @@ func (g *PluginGenerator) getToken(ctx context.Context, tokenRef string) (string
Namespace: g.namespace,
},
secret)
if err != nil {
return "", fmt.Errorf("error fetching secret %s/%s: %w", g.namespace, secretName, err)
return "", fmt.Errorf("error fetching secret %s/%s: %v", g.namespace, secretName, err)
}
secretValues := make(map[string]string, len(secret.Data))
@@ -188,6 +192,7 @@ func (g *PluginGenerator) getConfigMap(ctx context.Context, configMapRef string)
Namespace: g.namespace,
},
cm)
if err != nil {
return nil, err
}

View File

@@ -631,7 +631,9 @@ func TestPluginGenerateParams(t *testing.T) {
ctx := context.Background()
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
generatorConfig := argoprojiov1alpha1.ApplicationSetGenerator{
Plugin: &argoprojiov1alpha1.PluginGenerator{
ConfigMapRef: argoprojiov1alpha1.PluginConfigMapRef{Name: testCase.configmap.Name},
@@ -643,9 +645,10 @@ func TestPluginGenerateParams(t *testing.T) {
}
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
_, tokenKey := plugin.ParseSecretKey(testCase.configmap.Data["token"])
expectedToken := testCase.secret.Data[strings.ReplaceAll(tokenKey, "$", "")]
expectedToken := testCase.secret.Data[strings.Replace(tokenKey, "$", "", -1)]
if authHeader != "Bearer "+string(expectedToken) {
w.WriteHeader(http.StatusUnauthorized)
return
@@ -654,7 +657,7 @@ func TestPluginGenerateParams(t *testing.T) {
w.Header().Set("Content-Type", "application/json")
_, err := w.Write(testCase.content)
if err != nil {
require.NoError(t, fmt.Errorf("Error Write %w", err))
assert.NoError(t, fmt.Errorf("Error Write %v", err))
}
})
@@ -670,7 +673,7 @@ func TestPluginGenerateParams(t *testing.T) {
fakeClientWithCache := fake.NewClientBuilder().WithObjects([]client.Object{testCase.configmap, testCase.secret}...).Build()
pluginGenerator := NewPluginGenerator(fakeClientWithCache, ctx, fakeClient, "default")
var pluginGenerator = NewPluginGenerator(fakeClientWithCache, ctx, fakeClient, "default")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -681,15 +684,16 @@ func TestPluginGenerateParams(t *testing.T) {
},
}
got, err := pluginGenerator.GenerateParams(&generatorConfig, &applicationSetInfo, nil)
got, err := pluginGenerator.GenerateParams(&generatorConfig, &applicationSetInfo)
if err != nil {
fmt.Println(err)
}
if testCase.expectedError != nil {
require.EqualError(t, err, testCase.expectedError.Error())
assert.EqualError(t, err, testCase.expectedError.Error())
} else {
require.NoError(t, err)
assert.NoError(t, err)
expectedJson, err := json.Marshal(testCase.expected)
require.NoError(t, err)
gotJson, err := json.Marshal(got)

View File

@@ -24,13 +24,17 @@ const (
type PullRequestGenerator struct {
client client.Client
selectServiceProviderFunc func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error)
SCMConfig
auth SCMAuthProviders
scmRootCAPath string
allowedSCMProviders []string
}
func NewPullRequestGenerator(client client.Client, scmConfig SCMConfig) Generator {
func NewPullRequestGenerator(client client.Client, auth SCMAuthProviders, scmRootCAPath string, allowedScmProviders []string) Generator {
g := &PullRequestGenerator{
client: client,
SCMConfig: scmConfig,
client: client,
auth: auth,
scmRootCAPath: scmRootCAPath,
allowedSCMProviders: allowedScmProviders,
}
g.selectServiceProviderFunc = g.selectServiceProvider
return g
@@ -50,7 +54,7 @@ func (g *PullRequestGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.A
return &appSetGenerator.PullRequest.Template
}
func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil {
return nil, EmptyAppSetGeneratorError
}
@@ -62,12 +66,12 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
ctx := context.Background()
svc, err := g.selectServiceProviderFunc(ctx, appSetGenerator.PullRequest, applicationSetInfo)
if err != nil {
return nil, fmt.Errorf("failed to select pull request service provider: %w", err)
return nil, fmt.Errorf("failed to select pull request service provider: %v", err)
}
pulls, err := pullrequest.ListPullRequests(ctx, svc, appSetGenerator.PullRequest.Filters)
if err != nil {
return nil, fmt.Errorf("error listing repos: %w", err)
return nil, fmt.Errorf("error listing repos: %v", err)
}
params := make([]map[string]interface{}, 0, len(pulls))
@@ -117,38 +121,43 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
// selectServiceProvider selects the provider to get pull requests from the configuration
func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, generatorConfig *argoprojiov1alpha1.PullRequestGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
if !g.enableSCMProviders {
return nil, ErrSCMProvidersDisabled
}
if err := ScmProviderAllowed(applicationSetInfo, generatorConfig, g.allowedSCMProviders); err != nil {
return nil, fmt.Errorf("scm provider not allowed: %w", err)
}
if generatorConfig.Github != nil {
if !ScmProviderAllowed(applicationSetInfo, generatorConfig.Github.API, g.allowedSCMProviders) {
return nil, fmt.Errorf("scm provider not allowed: %s", generatorConfig.Github.API)
}
return g.github(ctx, generatorConfig.Github, applicationSetInfo)
}
if generatorConfig.GitLab != nil {
providerConfig := generatorConfig.GitLab
if !ScmProviderAllowed(applicationSetInfo, providerConfig.API, g.allowedSCMProviders) {
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.API)
}
token, err := g.getSecretRef(ctx, providerConfig.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err)
return nil, fmt.Errorf("error fetching Secret token: %v", err)
}
return pullrequest.NewGitLabService(ctx, token, providerConfig.API, providerConfig.Project, providerConfig.Labels, providerConfig.PullRequestState, g.scmRootCAPath, providerConfig.Insecure)
}
if generatorConfig.Gitea != nil {
providerConfig := generatorConfig.Gitea
if !ScmProviderAllowed(applicationSetInfo, providerConfig.API, g.allowedSCMProviders) {
return nil, fmt.Errorf("scm provider not allowed: %s", generatorConfig.Gitea.API)
}
token, err := g.getSecretRef(ctx, providerConfig.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err)
return nil, fmt.Errorf("error fetching Secret token: %v", err)
}
return pullrequest.NewGiteaService(ctx, token, providerConfig.API, providerConfig.Owner, providerConfig.Repo, providerConfig.Insecure)
}
if generatorConfig.BitbucketServer != nil {
providerConfig := generatorConfig.BitbucketServer
if !ScmProviderAllowed(applicationSetInfo, providerConfig.API, g.allowedSCMProviders) {
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.API)
}
if providerConfig.BasicAuth != nil {
password, err := g.getSecretRef(ctx, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err)
return nil, fmt.Errorf("error fetching Secret token: %v", err)
}
return pullrequest.NewBitbucketServiceBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.Repo)
} else {
@@ -160,13 +169,13 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
if providerConfig.BearerToken != nil {
appToken, err := g.getSecretRef(ctx, providerConfig.BearerToken.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Secret Bearer token: %w", err)
return nil, fmt.Errorf("error fetching Secret Bearer token: %v", err)
}
return pullrequest.NewBitbucketCloudServiceBearerToken(providerConfig.API, appToken, providerConfig.Owner, providerConfig.Repo)
} else if providerConfig.BasicAuth != nil {
password, err := g.getSecretRef(ctx, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err)
return nil, fmt.Errorf("error fetching Secret token: %v", err)
}
return pullrequest.NewBitbucketCloudServiceBasicAuth(providerConfig.API, providerConfig.BasicAuth.Username, password, providerConfig.Owner, providerConfig.Repo)
} else {
@@ -177,7 +186,7 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
providerConfig := generatorConfig.AzureDevOps
token, err := g.getSecretRef(ctx, providerConfig.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err)
return nil, fmt.Errorf("error fetching Secret token: %v", err)
}
return pullrequest.NewAzureDevOpsService(ctx, token, providerConfig.API, providerConfig.Organization, providerConfig.Project, providerConfig.Repo, providerConfig.Labels)
}
@@ -187,9 +196,9 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
func (g *PullRequestGenerator) github(ctx context.Context, cfg *argoprojiov1alpha1.PullRequestGeneratorGithub, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
// use an app if it was configured
if cfg.AppSecretName != "" {
auth, err := g.GitHubApps.GetAuthSecret(ctx, cfg.AppSecretName)
auth, err := g.auth.GitHubApps.GetAuthSecret(ctx, cfg.AppSecretName)
if err != nil {
return nil, fmt.Errorf("error getting GitHub App secret: %w", err)
return nil, fmt.Errorf("error getting GitHub App secret: %v", err)
}
return pullrequest.NewGithubAppService(*auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
}
@@ -197,7 +206,7 @@ func (g *PullRequestGenerator) github(ctx context.Context, cfg *argoprojiov1alph
// always default to token, even if not set (public access)
token, err := g.getSecretRef(ctx, cfg.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err)
return nil, fmt.Errorf("error fetching Secret token: %v", err)
}
return pullrequest.NewGithubService(ctx, token, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
}
@@ -217,7 +226,7 @@ func (g *PullRequestGenerator) getSecretRef(ctx context.Context, ref *argoprojio
},
secret)
if err != nil {
return "", fmt.Errorf("error fetching secret %s/%s: %w", namespace, ref.SecretName, err)
return "", fmt.Errorf("error fetching secret %s/%s: %v", namespace, ref.SecretName, err)
}
tokenBytes, ok := secret.Data[ref.Key]
if !ok {

View File

@@ -6,7 +6,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
@@ -204,12 +203,8 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
PullRequest: &argoprojiov1alpha1.PullRequestGenerator{},
}
got, gotErr := gen.GenerateParams(&generatorConfig, &c.applicationSet, nil)
if c.expectedErr != nil {
assert.Equal(t, c.expectedErr.Error(), gotErr.Error())
} else {
require.NoError(t, gotErr)
}
got, gotErr := gen.GenerateParams(&generatorConfig, &c.applicationSet)
assert.Equal(t, c.expectedErr, gotErr)
assert.ElementsMatch(t, c.expected, got)
}
}
@@ -270,9 +265,9 @@ func TestPullRequestGetSecretRef(t *testing.T) {
t.Run(c.name, func(t *testing.T) {
token, err := gen.getSecretRef(ctx, c.ref, c.namespace)
if c.hasError {
require.Error(t, err)
assert.NotNil(t, err)
} else {
require.NoError(t, err)
assert.Nil(t, err)
}
assert.Equal(t, c.token, token)
})
@@ -283,6 +278,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
cases := []struct {
name string
providerConfig *argoprojiov1alpha1.PullRequestGenerator
expectedError string
}{
{
name: "Error Github",
@@ -291,6 +287,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: "failed to select pull request service provider: scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
},
{
name: "Error Gitlab",
@@ -299,6 +296,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: "failed to select pull request service provider: scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
},
{
name: "Error Gitea",
@@ -307,6 +305,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: "failed to select pull request service provider: scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
},
{
name: "Error Bitbucket",
@@ -315,6 +314,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: "failed to select pull request service provider: scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
},
}
@@ -324,13 +324,13 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel()
pullRequestGenerator := NewPullRequestGenerator(nil, NewSCMConfig("", []string{
pullRequestGenerator := NewPullRequestGenerator(nil, SCMAuthProviders{}, "", []string{
"github.myorg.com",
"gitlab.myorg.com",
"gitea.myorg.com",
"bitbucket.myorg.com",
"azuredevops.myorg.com",
}, true, nil))
})
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -343,33 +343,10 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
},
}
_, err := pullRequestGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
_, err := pullRequestGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
require.Error(t, err, "Must return an error")
var expectedError ErrDisallowedSCMProvider
assert.ErrorAs(t, err, &expectedError)
assert.Error(t, err, "Must return an error")
assert.Equal(t, testCaseCopy.expectedError, err.Error())
})
}
}
func TestSCMProviderDisabled_PRGenerator(t *testing.T) {
generator := NewPullRequestGenerator(nil, NewSCMConfig("", []string{}, false, nil))
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
PullRequest: &argoprojiov1alpha1.PullRequestGenerator{
Github: &argoprojiov1alpha1.PullRequestGeneratorGithub{
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
}},
},
}
_, err := generator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
assert.ErrorIs(t, err, ErrSCMProvidersDisabled)
}

View File

@@ -2,7 +2,6 @@ package generators
import (
"context"
"errors"
"fmt"
"strings"
"time"
@@ -29,36 +28,27 @@ type SCMProviderGenerator struct {
client client.Client
// Testing hooks.
overrideProvider scm_provider.SCMProviderService
SCMConfig
}
type SCMConfig struct {
SCMAuthProviders
scmRootCAPath string
allowedSCMProviders []string
enableSCMProviders bool
GitHubApps github_app_auth.Credentials
}
func NewSCMConfig(scmRootCAPath string, allowedSCMProviders []string, enableSCMProviders bool, gitHubApps github_app_auth.Credentials) SCMConfig {
return SCMConfig{
type SCMAuthProviders struct {
GitHubApps github_app_auth.Credentials
}
func NewSCMProviderGenerator(client client.Client, providers SCMAuthProviders, scmRootCAPath string, allowedSCMProviders []string) Generator {
return &SCMProviderGenerator{
client: client,
SCMAuthProviders: providers,
scmRootCAPath: scmRootCAPath,
allowedSCMProviders: allowedSCMProviders,
enableSCMProviders: enableSCMProviders,
GitHubApps: gitHubApps,
}
}
func NewSCMProviderGenerator(client client.Client, scmConfig SCMConfig) Generator {
return &SCMProviderGenerator{
client: client,
SCMConfig: scmConfig,
}
}
// Testing generator
func NewTestSCMProviderGenerator(overrideProvider scm_provider.SCMProviderService) Generator {
return &SCMProviderGenerator{overrideProvider: overrideProvider, SCMConfig: SCMConfig{
enableSCMProviders: true,
}}
return &SCMProviderGenerator{overrideProvider: overrideProvider}
}
func (g *SCMProviderGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
@@ -75,34 +65,14 @@ func (g *SCMProviderGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.A
return &appSetGenerator.SCMProvider.Template
}
var ErrSCMProvidersDisabled = errors.New("scm providers are disabled")
type ErrDisallowedSCMProvider struct {
Provider string
Allowed []string
}
func NewErrDisallowedSCMProvider(provider string, allowed []string) ErrDisallowedSCMProvider {
return ErrDisallowedSCMProvider{
Provider: provider,
Allowed: allowed,
}
}
func (e ErrDisallowedSCMProvider) Error() string {
return fmt.Sprintf("scm provider %q not allowed, must use one of the following: %s", e.Provider, strings.Join(e.Allowed, ", "))
}
func ScmProviderAllowed(applicationSetInfo *argoprojiov1alpha1.ApplicationSet, generator SCMGeneratorWithCustomApiUrl, allowedScmProviders []string) error {
url := generator.CustomApiUrl()
func ScmProviderAllowed(applicationSetInfo *argoprojiov1alpha1.ApplicationSet, url string, allowedScmProviders []string) bool {
if url == "" || len(allowedScmProviders) == 0 {
return nil
return true
}
for _, allowedScmProvider := range allowedScmProviders {
if url == allowedScmProvider {
return nil
return true
}
}
@@ -110,12 +80,12 @@ func ScmProviderAllowed(applicationSetInfo *argoprojiov1alpha1.ApplicationSet, g
common.SecurityField: common.SecurityMedium,
"applicationset": applicationSetInfo.Name,
"appSetNamespace": applicationSetInfo.Namespace,
}).Debugf("attempted to use disallowed SCM %q, must use one of the following: %s", url, strings.Join(allowedScmProviders, ", "))
}).Debugf("attempted to use disallowed SCM %q", url)
return NewErrDisallowedSCMProvider(url, allowedScmProviders)
return false
}
func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil {
return nil, EmptyAppSetGeneratorError
}
@@ -124,83 +94,90 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
return nil, EmptyAppSetGeneratorError
}
if !g.enableSCMProviders {
return nil, ErrSCMProvidersDisabled
}
ctx := context.Background()
// Create the SCM provider helper.
providerConfig := appSetGenerator.SCMProvider
if err := ScmProviderAllowed(applicationSetInfo, providerConfig, g.allowedSCMProviders); err != nil {
return nil, fmt.Errorf("scm provider not allowed: %w", err)
}
ctx := context.Background()
var provider scm_provider.SCMProviderService
if g.overrideProvider != nil {
provider = g.overrideProvider
} else if providerConfig.Github != nil {
if !ScmProviderAllowed(applicationSetInfo, providerConfig.Github.API, g.allowedSCMProviders) {
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.Github.API)
}
var err error
provider, err = g.githubProvider(ctx, providerConfig.Github, applicationSetInfo)
if err != nil {
return nil, fmt.Errorf("scm provider: %w", err)
}
} else if providerConfig.Gitlab != nil {
if !ScmProviderAllowed(applicationSetInfo, providerConfig.Gitlab.API, g.allowedSCMProviders) {
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.Gitlab.API)
}
token, err := g.getSecretRef(ctx, providerConfig.Gitlab.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Gitlab token: %w", err)
return nil, fmt.Errorf("error fetching Gitlab token: %v", err)
}
provider, err = scm_provider.NewGitlabProvider(ctx, providerConfig.Gitlab.Group, token, providerConfig.Gitlab.API, providerConfig.Gitlab.AllBranches, providerConfig.Gitlab.IncludeSubgroups, providerConfig.Gitlab.WillIncludeSharedProjects(), providerConfig.Gitlab.Insecure, g.scmRootCAPath, providerConfig.Gitlab.Topic)
if err != nil {
return nil, fmt.Errorf("error initializing Gitlab service: %w", err)
return nil, fmt.Errorf("error initializing Gitlab service: %v", err)
}
} else if providerConfig.Gitea != nil {
if !ScmProviderAllowed(applicationSetInfo, providerConfig.Gitea.API, g.allowedSCMProviders) {
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.Gitea.API)
}
token, err := g.getSecretRef(ctx, providerConfig.Gitea.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Gitea token: %w", err)
return nil, fmt.Errorf("error fetching Gitea token: %v", err)
}
provider, err = scm_provider.NewGiteaProvider(ctx, providerConfig.Gitea.Owner, token, providerConfig.Gitea.API, providerConfig.Gitea.AllBranches, providerConfig.Gitea.Insecure)
if err != nil {
return nil, fmt.Errorf("error initializing Gitea service: %w", err)
return nil, fmt.Errorf("error initializing Gitea service: %v", err)
}
} else if providerConfig.BitbucketServer != nil {
providerConfig := providerConfig.BitbucketServer
if !ScmProviderAllowed(applicationSetInfo, providerConfig.API, g.allowedSCMProviders) {
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.API)
}
var scmError error
if providerConfig.BasicAuth != nil {
password, err := g.getSecretRef(ctx, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err)
return nil, fmt.Errorf("error fetching Secret token: %v", err)
}
provider, scmError = scm_provider.NewBitbucketServerProviderBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.AllBranches)
} else {
provider, scmError = scm_provider.NewBitbucketServerProviderNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.AllBranches)
}
if scmError != nil {
return nil, fmt.Errorf("error initializing Bitbucket Server service: %w", scmError)
return nil, fmt.Errorf("error initializing Bitbucket Server service: %v", scmError)
}
} else if providerConfig.AzureDevOps != nil {
if !ScmProviderAllowed(applicationSetInfo, providerConfig.AzureDevOps.API, g.allowedSCMProviders) {
return nil, fmt.Errorf("scm provider not allowed: %s", providerConfig.AzureDevOps.API)
}
token, err := g.getSecretRef(ctx, providerConfig.AzureDevOps.AccessTokenRef, applicationSetInfo.Namespace)
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: %v", err)
}
provider, err = scm_provider.NewAzureDevOpsProvider(ctx, token, providerConfig.AzureDevOps.Organization, providerConfig.AzureDevOps.API, providerConfig.AzureDevOps.TeamProject, providerConfig.AzureDevOps.AllBranches)
if err != nil {
return nil, fmt.Errorf("error initializing Azure Devops service: %w", err)
return nil, fmt.Errorf("error initializing Azure Devops service: %v", err)
}
} else if providerConfig.Bitbucket != nil {
appPassword, err := g.getSecretRef(ctx, providerConfig.Bitbucket.AppPasswordRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Bitbucket cloud appPassword: %w", err)
return nil, fmt.Errorf("error fetching Bitbucket cloud appPassword: %v", err)
}
provider, err = scm_provider.NewBitBucketCloudProvider(ctx, providerConfig.Bitbucket.Owner, providerConfig.Bitbucket.User, appPassword, providerConfig.Bitbucket.AllBranches)
if err != nil {
return nil, fmt.Errorf("error initializing Bitbucket cloud service: %w", err)
return nil, fmt.Errorf("error initializing Bitbucket cloud service: %v", err)
}
} else if providerConfig.AWSCodeCommit != nil {
var awsErr error
provider, awsErr = scm_provider.NewAWSCodeCommitProvider(ctx, providerConfig.AWSCodeCommit.TagFilters, providerConfig.AWSCodeCommit.Role, providerConfig.AWSCodeCommit.Region, providerConfig.AWSCodeCommit.AllBranches)
if awsErr != nil {
return nil, fmt.Errorf("error initializing AWS codecommit service: %w", awsErr)
return nil, fmt.Errorf("error initializing AWS codecommit service: %v", awsErr)
}
} else {
return nil, fmt.Errorf("no SCM provider implementation configured")
@@ -209,7 +186,7 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
// Find all the available repos.
repos, err := scm_provider.ListRepos(ctx, provider, providerConfig.Filters, providerConfig.CloneProtocol)
if err != nil {
return nil, fmt.Errorf("error listing repos: %w", err)
return nil, fmt.Errorf("error listing repos: %v", err)
}
paramsArray := make([]map[string]interface{}, 0, len(repos))
var shortSHALength int
@@ -261,7 +238,7 @@ func (g *SCMProviderGenerator) getSecretRef(ctx context.Context, ref *argoprojio
},
secret)
if err != nil {
return "", fmt.Errorf("error fetching secret %s/%s: %w", namespace, ref.SecretName, err)
return "", fmt.Errorf("error fetching secret %s/%s: %v", namespace, ref.SecretName, err)
}
tokenBytes, ok := secret.Data[ref.Key]
if !ok {
@@ -274,7 +251,7 @@ func (g *SCMProviderGenerator) githubProvider(ctx context.Context, github *argop
if github.AppSecretName != "" {
auth, err := g.GitHubApps.GetAuthSecret(ctx, github.AppSecretName)
if err != nil {
return nil, fmt.Errorf("error fetching Github app secret: %w", err)
return nil, fmt.Errorf("error fetching Github app secret: %v", err)
}
return scm_provider.NewGithubAppProviderFor(
@@ -287,7 +264,7 @@ func (g *SCMProviderGenerator) githubProvider(ctx context.Context, github *argop
token, err := g.getSecretRef(ctx, github.TokenRef, applicationSetInfo.Namespace)
if err != nil {
return nil, fmt.Errorf("error fetching Github token: %w", err)
return nil, fmt.Errorf("error fetching Github token: %v", err)
}
return scm_provider.NewGithubProvider(ctx, github.Organization, token, github.API, github.AllBranches)
}

View File

@@ -5,7 +5,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
@@ -70,11 +69,12 @@ func TestSCMProviderGetSecretRef(t *testing.T) {
t.Run(c.name, func(t *testing.T) {
token, err := gen.getSecretRef(ctx, c.ref, c.namespace)
if c.hasError {
require.Error(t, err)
assert.NotNil(t, err)
} else {
require.NoError(t, err)
assert.Nil(t, err)
}
assert.Equal(t, c.token, token)
})
}
}
@@ -174,7 +174,7 @@ func TestSCMProviderGenerateParams(t *testing.T) {
mockProvider := &scm_provider.MockProvider{
Repos: testCaseCopy.repos,
}
scmGenerator := &SCMProviderGenerator{overrideProvider: mockProvider, SCMConfig: SCMConfig{enableSCMProviders: true}}
scmGenerator := &SCMProviderGenerator{overrideProvider: mockProvider}
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -188,14 +188,15 @@ func TestSCMProviderGenerateParams(t *testing.T) {
},
}
got, err := scmGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
got, err := scmGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
if testCaseCopy.expectedError != nil {
assert.EqualError(t, err, testCaseCopy.expectedError.Error())
} else {
require.NoError(t, err)
assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got)
}
})
}
}
@@ -204,6 +205,7 @@ func TestAllowedSCMProvider(t *testing.T) {
cases := []struct {
name string
providerConfig *argoprojiov1alpha1.SCMProviderGenerator
expectedError string
}{
{
name: "Error Github",
@@ -212,6 +214,7 @@ func TestAllowedSCMProvider(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: "scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
},
{
name: "Error Gitlab",
@@ -220,6 +223,7 @@ func TestAllowedSCMProvider(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: "scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
},
{
name: "Error Gitea",
@@ -228,6 +232,7 @@ func TestAllowedSCMProvider(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: "scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
},
{
name: "Error Bitbucket",
@@ -236,6 +241,7 @@ func TestAllowedSCMProvider(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: "scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
},
{
name: "Error AzureDevops",
@@ -244,6 +250,7 @@ func TestAllowedSCMProvider(t *testing.T) {
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: "scm provider not allowed: https://myservice.mynamespace.svc.cluster.local",
},
}
@@ -253,18 +260,13 @@ func TestAllowedSCMProvider(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel()
scmGenerator := &SCMProviderGenerator{
SCMConfig: SCMConfig{
allowedSCMProviders: []string{
"github.myorg.com",
"gitlab.myorg.com",
"gitea.myorg.com",
"bitbucket.myorg.com",
"azuredevops.myorg.com",
},
enableSCMProviders: true,
},
}
scmGenerator := &SCMProviderGenerator{allowedSCMProviders: []string{
"github.myorg.com",
"gitlab.myorg.com",
"gitea.myorg.com",
"bitbucket.myorg.com",
"azuredevops.myorg.com",
}}
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
@@ -277,33 +279,10 @@ func TestAllowedSCMProvider(t *testing.T) {
},
}
_, err := scmGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
_, err := scmGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
require.Error(t, err, "Must return an error")
var expectedError ErrDisallowedSCMProvider
assert.ErrorAs(t, err, &expectedError)
assert.Error(t, err, "Must return an error")
assert.Equal(t, testCaseCopy.expectedError, err.Error())
})
}
}
func TestSCMProviderDisabled_SCMGenerator(t *testing.T) {
generator := &SCMProviderGenerator{SCMConfig: SCMConfig{enableSCMProviders: false}}
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
SCMProvider: &argoprojiov1alpha1.SCMProviderGenerator{
Github: &argoprojiov1alpha1.SCMProviderGeneratorGithub{
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
}},
},
}
_, err := generator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo, nil)
assert.ErrorIs(t, err, ErrSCMProvidersDisabled)
}

View File

@@ -1,5 +0,0 @@
package generators
type SCMGeneratorWithCustomApiUrl interface {
CustomApiUrl() string
}

View File

@@ -1,49 +0,0 @@
package generators
import (
"context"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"
"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 {
terminalGenerators := map[string]Generator{
"List": NewListGenerator(),
"Clusters": NewClusterGenerator(c, ctx, k8sClient, namespace),
"Git": NewGitGenerator(argoCDService),
"SCMProvider": NewSCMProviderGenerator(c, scmConfig),
"ClusterDecisionResource": NewDuckTypeGenerator(ctx, dynamicClient, k8sClient, namespace),
"PullRequest": NewPullRequestGenerator(c, scmConfig),
"Plugin": NewPluginGenerator(c, ctx, k8sClient, namespace),
}
nestedGenerators := map[string]Generator{
"List": terminalGenerators["List"],
"Clusters": terminalGenerators["Clusters"],
"Git": terminalGenerators["Git"],
"SCMProvider": terminalGenerators["SCMProvider"],
"ClusterDecisionResource": terminalGenerators["ClusterDecisionResource"],
"PullRequest": terminalGenerators["PullRequest"],
"Plugin": terminalGenerators["Plugin"],
"Matrix": NewMatrixGenerator(terminalGenerators),
"Merge": NewMergeGenerator(terminalGenerators),
}
topLevelGenerators := map[string]Generator{
"List": terminalGenerators["List"],
"Clusters": terminalGenerators["Clusters"],
"Git": terminalGenerators["Git"],
"SCMProvider": terminalGenerators["SCMProvider"],
"ClusterDecisionResource": terminalGenerators["ClusterDecisionResource"],
"PullRequest": terminalGenerators["PullRequest"],
"Plugin": terminalGenerators["Plugin"],
"Matrix": NewMatrixGenerator(nestedGenerators),
"Merge": NewMergeGenerator(nestedGenerators),
}
return topLevelGenerators
}

View File

@@ -12,6 +12,7 @@ func appendTemplatedValues(values map[string]string, params map[string]interface
for key, value := range values {
result, err := replaceTemplatedString(value, params, useGoTemplate, goTemplateOptions)
if err != nil {
return fmt.Errorf("failed to replace templated string: %w", err)
}

View File

@@ -4,7 +4,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestValueInterpolation(t *testing.T) {
@@ -54,9 +53,10 @@ func TestValueInterpolation(t *testing.T) {
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
err := appendTemplatedValues(testCase.values, testCase.params, false, nil)
require.NoError(t, err)
assert.NoError(t, err)
assert.EqualValues(t, testCase.expected, testCase.params)
})
}
@@ -115,9 +115,10 @@ func TestValueInterpolationWithGoTemplating(t *testing.T) {
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
err := appendTemplatedValues(testCase.values, testCase.params, true, nil)
require.NoError(t, err)
assert.NoError(t, err)
assert.EqualValues(t, testCase.expected, testCase.params)
})
}

View File

@@ -66,6 +66,7 @@ func newClient(baseURL string, options ...ClientOptionFunc) (*Client, error) {
}
func (c *Client) NewRequest(method, path string, body interface{}, options []ClientOptionFunc) (*http.Request, error) {
// Make sure the given URL end with a slash
if !strings.HasSuffix(c.baseURL, "/") {
c.baseURL += "/"
@@ -134,13 +135,14 @@ func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*htt
// CheckResponse checks the API response for errors, and returns them if present.
func CheckResponse(resp *http.Response) error {
if c := resp.StatusCode; 200 <= c && c <= 299 {
return nil
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("API error with status code %d: %w", resp.StatusCode, err)
return fmt.Errorf("API error with status code %d: %v", resp.StatusCode, err)
}
var raw map[string]interface{}

View File

@@ -17,13 +17,14 @@ func TestClient(t *testing.T) {
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte("Hello, World!"))
if err != nil {
assert.NoError(t, fmt.Errorf("Error Write %w", err))
assert.NoError(t, fmt.Errorf("Error Write %v", err))
}
}))
defer server.Close()
var clientOptionFns []ClientOptionFunc
_, err := NewClient(server.URL, clientOptionFns...)
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
@@ -61,7 +62,7 @@ func TestClientDo(t *testing.T) {
"key3": 123
}]`))
if err != nil {
assert.NoError(t, fmt.Errorf("Error Write %w", err))
assert.NoError(t, fmt.Errorf("Error Write %v", err))
}
})),
clientOptionFns: nil,
@@ -104,7 +105,7 @@ func TestClientDo(t *testing.T) {
"key3": 123
}]`))
if err != nil {
assert.NoError(t, fmt.Errorf("Error Write %w", err))
assert.NoError(t, fmt.Errorf("Error Write %v", err))
}
})),
clientOptionFns: nil,
@@ -118,11 +119,13 @@ func TestClientDo(t *testing.T) {
defer cc.fakeServer.Close()
client, err := NewClient(cc.fakeServer.URL, cc.clientOptionFns...)
if err != nil {
t.Fatalf("NewClient returned unexpected error: %v", err)
}
req, err := client.NewRequest("POST", "", cc.params, nil)
if err != nil {
t.Fatalf("NewRequest returned unexpected error: %v", err)
}
@@ -134,8 +137,8 @@ func TestClientDo(t *testing.T) {
if cc.expectedError != nil {
assert.EqualError(t, err, cc.expectedError.Error())
} else {
assert.Equal(t, cc.expectedCode, resp.StatusCode)
assert.Equal(t, cc.expected, data)
assert.Equal(t, resp.StatusCode, cc.expectedCode)
assert.Equal(t, data, cc.expected)
assert.NoError(t, err)
}
})

View File

@@ -1,4 +1,4 @@
// Code generated by mockery v2.40.2. DO NOT EDIT.
// Code generated by mockery v2.25.1. DO NOT EDIT.
package mocks
@@ -13,29 +13,25 @@ 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")
}
// GetDirectories provides a mock function with given fields: ctx, repoURL, revision, noRevisionCache
func (_m *Repos) GetDirectories(ctx context.Context, repoURL string, revision string, noRevisionCache bool) ([]string, error) {
ret := _m.Called(ctx, repoURL, revision, noRevisionCache)
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) ([]string, error)); ok {
return rf(ctx, repoURL, revision, noRevisionCache)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, bool, bool) []string); ok {
r0 = rf(ctx, repoURL, revision, noRevisionCache, verifyCommit)
if rf, ok := ret.Get(0).(func(context.Context, string, string, bool) []string); ok {
r0 = rf(ctx, repoURL, revision, noRevisionCache)
} 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)
if rf, ok := ret.Get(1).(func(context.Context, string, string, bool) error); ok {
r1 = rf(ctx, repoURL, revision, noRevisionCache)
} else {
r1 = ret.Error(1)
}
@@ -43,29 +39,25 @@ func (_m *Repos) GetDirectories(ctx context.Context, repoURL string, revision st
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")
}
// GetFiles provides a mock function with given fields: ctx, repoURL, revision, pattern, noRevisionCache
func (_m *Repos) GetFiles(ctx context.Context, repoURL string, revision string, pattern string, noRevisionCache bool) (map[string][]byte, error) {
ret := _m.Called(ctx, repoURL, revision, pattern, noRevisionCache)
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) (map[string][]byte, error)); ok {
return rf(ctx, repoURL, revision, pattern, noRevisionCache)
}
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)
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool) map[string][]byte); ok {
r0 = rf(ctx, repoURL, revision, pattern, noRevisionCache)
} 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)
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, bool) error); ok {
r1 = rf(ctx, repoURL, revision, pattern, noRevisionCache)
} else {
r1 = ret.Error(1)
}
@@ -73,12 +65,13 @@ func (_m *Repos) GetFiles(ctx context.Context, repoURL string, revision string,
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.
// The first argument is typically a *testing.T value.
func NewRepos(t interface {
type mockConstructorTestingTNewRepos interface {
mock.TestingT
Cleanup(func())
}) *Repos {
}
// 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.
func NewRepos(t mockConstructorTestingTNewRepos) *Repos {
mock := &Repos{}
mock.Mock.Test(t)

View File

@@ -0,0 +1,57 @@
// Code generated by mockery v2.21.1. DO NOT EDIT.
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
v1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
// RepositoryDB is an autogenerated mock type for the RepositoryDB type
type RepositoryDB struct {
mock.Mock
}
// GetRepository provides a mock function with given fields: ctx, url
func (_m *RepositoryDB) GetRepository(ctx context.Context, url string) (*v1alpha1.Repository, error) {
ret := _m.Called(ctx, url)
var r0 *v1alpha1.Repository
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (*v1alpha1.Repository, error)); ok {
return rf(ctx, url)
}
if rf, ok := ret.Get(0).(func(context.Context, string) *v1alpha1.Repository); ok {
r0 = rf(ctx, url)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.Repository)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, url)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
type mockConstructorTestingTNewRepositoryDB interface {
mock.TestingT
Cleanup(func())
}
// NewRepositoryDB creates a new instance of RepositoryDB. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewRepositoryDB(t mockConstructorTestingTNewRepositoryDB) *RepositoryDB {
mock := &RepositoryDB{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@@ -45,7 +45,7 @@ func NewPluginService(ctx context.Context, appSetName string, baseURL string, to
client, err := internalhttp.NewClient(baseURL, clientOptionFns...)
if err != nil {
return nil, fmt.Errorf("error creating plugin client: %w", err)
return nil, fmt.Errorf("error creating plugin client: %v", err)
}
return &Service{
@@ -56,15 +56,17 @@ func NewPluginService(ctx context.Context, appSetName string, baseURL string, to
func (p *Service) List(ctx context.Context, parameters v1alpha1.PluginParameters) (*ServiceResponse, error) {
req, err := p.client.NewRequest(http.MethodPost, "api/v1/getparams.execute", ServiceRequest{ApplicationSetName: p.appSetName, Input: v1alpha1.PluginInput{Parameters: parameters}}, nil)
if err != nil {
return nil, fmt.Errorf("NewRequest returned unexpected error: %w", err)
return nil, fmt.Errorf("NewRequest returned unexpected error: %v", err)
}
var data ServiceResponse
_, err = p.client.Do(ctx, req, &data)
if err != nil {
return nil, fmt.Errorf("error get api '%s': %w", p.appSetName, err)
return nil, fmt.Errorf("error get api '%s': %v", p.appSetName, err)
}
return &data, err

View File

@@ -23,19 +23,22 @@ func TestPlugin(t *testing.T) {
return
}
_, err := w.Write([]byte(expectedJSON))
if err != nil {
assert.NoError(t, fmt.Errorf("Error Write %w", err))
assert.NoError(t, fmt.Errorf("Error Write %v", err))
}
})
ts := httptest.NewServer(handler)
defer ts.Close()
client, err := NewPluginService(context.Background(), "plugin-test", ts.URL, token, 0)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
data, err := client.List(context.Background(), nil)
if err != nil {
t.Errorf("unexpected error: %v", err)
}

View File

@@ -36,10 +36,8 @@ type AzureDevOpsService struct {
labels []string
}
var (
_ PullRequestService = (*AzureDevOpsService)(nil)
_ AzureDevOpsClientFactory = &devopsFactoryImpl{}
)
var _ PullRequestService = (*AzureDevOpsService)(nil)
var _ AzureDevOpsClientFactory = &devopsFactoryImpl{}
func NewAzureDevOpsService(ctx context.Context, token, url, organization, project, repo string, labels []string) (PullRequestService, error) {
organizationUrl := buildURL(url, organization)

View File

@@ -8,7 +8,6 @@ import (
git "github.com/microsoft/azure-devops-go-api/azuredevops/git"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
azureMock "github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider/azure_devops/git/mocks"
)
@@ -91,8 +90,8 @@ func TestListPullRequest(t *testing.T) {
}
list, err := provider.List(ctx)
require.NoError(t, err)
assert.Len(t, list, 1)
assert.NoError(t, err)
assert.Equal(t, 1, len(list))
assert.Equal(t, "feature-branch", list[0].Branch)
assert.Equal(t, pr_head_sha, list[0].HeadSHA)
assert.Equal(t, pr_id, list[0].Number)
@@ -207,16 +206,16 @@ func TestBuildURL(t *testing.T) {
},
{
name: "Provided custom URL and organization",
url: "https://azuredevops.example.com/",
url: "https://azuredevops.mycompany.com/",
organization: "myorganization",
expected: "https://azuredevops.example.com/myorganization",
expected: "https://azuredevops.mycompany.com/myorganization",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := buildURL(tc.url, tc.organization)
assert.Equal(t, tc.expected, result)
assert.Equal(t, result, tc.expected)
})
}
}

View File

@@ -60,7 +60,7 @@ func parseUrl(uri string) (*url.URL, error) {
func NewBitbucketCloudServiceBasicAuth(baseUrl, username, password, owner, repositorySlug string) (PullRequestService, error) {
url, err := parseUrl(baseUrl)
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: %v", baseUrl, owner, repositorySlug, err)
}
bitbucketClient := bitbucket.NewBasicAuth(username, password)
@@ -76,7 +76,7 @@ func NewBitbucketCloudServiceBasicAuth(baseUrl, username, password, owner, repos
func NewBitbucketCloudServiceBearerToken(baseUrl, bearerToken, owner, repositorySlug string) (PullRequestService, error) {
url, err := parseUrl(baseUrl)
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: %v", baseUrl, owner, repositorySlug, err)
}
bitbucketClient := bitbucket.NewOAuthbearerToken(bearerToken)
@@ -102,7 +102,7 @@ func (b *BitbucketCloudService) List(_ context.Context) ([]*PullRequest, error)
response, err := b.client.Repositories.PullRequests.Gets(opts)
if err != nil {
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: %v", b.owner, b.repositorySlug, err)
}
resp, ok := response.(map[string]interface{})
@@ -117,12 +117,12 @@ func (b *BitbucketCloudService) List(_ context.Context) ([]*PullRequest, error)
jsonStr, err := json.Marshal(repoArray)
if err != nil {
return nil, fmt.Errorf("error marshalling response body to json: %w", err)
return nil, fmt.Errorf("error marshalling response body to json: %v", err)
}
var pulls []BitbucketCloudPullRequest
if err := json.Unmarshal(jsonStr, &pulls); err != nil {
return nil, fmt.Errorf("error unmarshalling json to type '[]BitbucketCloudPullRequest': %w", err)
return nil, fmt.Errorf("error unmarshalling json to type '[]BitbucketCloudPullRequest': %v", err)
}
pullRequests := []*PullRequest{}

View File

@@ -9,7 +9,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
@@ -52,26 +51,26 @@ func TestParseUrlEmptyUrl(t *testing.T) {
url, err := parseUrl("")
bitbucketUrl, _ := url.Parse("https://api.bitbucket.org/2.0")
require.NoError(t, err)
assert.NoError(t, err)
assert.Equal(t, bitbucketUrl, url)
}
func TestInvalidBaseUrlBasicAuthCloud(t *testing.T) {
_, err := NewBitbucketCloudServiceBasicAuth("http:// example.org", "user", "password", "OWNER", "REPO")
require.Error(t, err)
assert.Error(t, err)
}
func TestInvalidBaseUrlBearerTokenCloud(t *testing.T) {
_, err := NewBitbucketCloudServiceBearerToken("http:// example.org", "TOKEN", "OWNER", "REPO")
require.Error(t, err)
assert.Error(t, err)
}
func TestInvalidBaseUrlNoAuthCloud(t *testing.T) {
_, err := NewBitbucketCloudServiceNoAuth("http:// example.org", "OWNER", "REPO")
require.Error(t, err)
assert.Error(t, err)
}
func TestListPullRequestBearerTokenCloud(t *testing.T) {
@@ -81,10 +80,10 @@ func TestListPullRequestBearerTokenCloud(t *testing.T) {
}))
defer ts.Close()
svc, err := NewBitbucketCloudServiceBearerToken(ts.URL, "TOKEN", "OWNER", "REPO")
require.NoError(t, err)
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.NoError(t, err)
assert.Equal(t, 1, len(pullRequests))
assert.Equal(t, 101, pullRequests[0].Number)
assert.Equal(t, "feature/foo-bar", pullRequests[0].Branch)
assert.Equal(t, "1a8dd249c04a", pullRequests[0].HeadSHA)
@@ -97,10 +96,10 @@ func TestListPullRequestNoAuthCloud(t *testing.T) {
}))
defer ts.Close()
svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
require.NoError(t, err)
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.NoError(t, err)
assert.Equal(t, 1, len(pullRequests))
assert.Equal(t, 101, pullRequests[0].Number)
assert.Equal(t, "feature/foo-bar", pullRequests[0].Branch)
assert.Equal(t, "1a8dd249c04a", pullRequests[0].HeadSHA)
@@ -113,10 +112,10 @@ func TestListPullRequestBasicAuthCloud(t *testing.T) {
}))
defer ts.Close()
svc, err := NewBitbucketCloudServiceBasicAuth(ts.URL, "user", "password", "OWNER", "REPO")
require.NoError(t, err)
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.NoError(t, err)
assert.Equal(t, 1, len(pullRequests))
assert.Equal(t, 101, pullRequests[0].Number)
assert.Equal(t, "feature/foo-bar", pullRequests[0].Branch)
assert.Equal(t, "1a8dd249c04a", pullRequests[0].HeadSHA)
@@ -190,10 +189,10 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
}))
defer ts.Close()
svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
require.NoError(t, err)
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err)
assert.Len(t, pullRequests, 3)
assert.NoError(t, err)
assert.Equal(t, 3, len(pullRequests))
assert.Equal(t, PullRequest{
Number: 101,
Branch: "feature-101",
@@ -218,7 +217,7 @@ func TestListResponseErrorCloud(t *testing.T) {
defer ts.Close()
svc, _ := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
_, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.Error(t, err)
assert.Error(t, err)
}
func TestListResponseMalformedCloud(t *testing.T) {
@@ -242,7 +241,7 @@ func TestListResponseMalformedCloud(t *testing.T) {
defer ts.Close()
svc, _ := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
_, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.Error(t, err)
assert.Error(t, err)
}
func TestListResponseMalformedValuesCloud(t *testing.T) {
@@ -266,7 +265,7 @@ func TestListResponseMalformedValuesCloud(t *testing.T) {
defer ts.Close()
svc, _ := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
_, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.Error(t, err)
assert.Error(t, err)
}
func TestListResponseEmptyCloud(t *testing.T) {
@@ -289,9 +288,9 @@ func TestListResponseEmptyCloud(t *testing.T) {
}))
defer ts.Close()
svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
require.NoError(t, err)
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err)
assert.NoError(t, err)
assert.Empty(t, pullRequests)
}
@@ -364,14 +363,14 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
defer ts.Close()
regexp := `feature-1[\d]{2}`
svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
require.NoError(t, err)
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{
BranchMatch: &regexp,
},
})
require.NoError(t, err)
assert.Len(t, pullRequests, 2)
assert.NoError(t, err)
assert.Equal(t, 2, len(pullRequests))
assert.Equal(t, PullRequest{
Number: 101,
Branch: "feature-101",
@@ -385,14 +384,14 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
regexp = `.*2$`
svc, err = NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
require.NoError(t, err)
assert.NoError(t, err)
pullRequests, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{
BranchMatch: &regexp,
},
})
require.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.NoError(t, err)
assert.Equal(t, 1, len(pullRequests))
assert.Equal(t, PullRequest{
Number: 102,
Branch: "feature-102",
@@ -401,11 +400,11 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
regexp = `[\d{2}`
svc, err = NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
require.NoError(t, err)
assert.NoError(t, err)
_, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{
BranchMatch: &regexp,
},
})
require.Error(t, err)
assert.Error(t, err)
}

View File

@@ -4,10 +4,9 @@ import (
"context"
"fmt"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
bitbucketv1 "github.com/gfleury/go-bitbucket-v1"
log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
)
type BitbucketService struct {
@@ -57,12 +56,12 @@ func (b *BitbucketService) List(_ context.Context) ([]*PullRequest, error) {
for {
response, err := b.client.DefaultApi.GetPullRequestsPage(b.projectKey, b.repositorySlug, paged)
if err != nil {
return nil, fmt.Errorf("error listing pull requests for %s/%s: %w", b.projectKey, b.repositorySlug, err)
return nil, fmt.Errorf("error listing pull requests for %s/%s: %v", b.projectKey, b.repositorySlug, err)
}
pulls, err := bitbucketv1.GetPullRequestsResponse(response)
if err != nil {
log.Errorf("error parsing pull request response '%v'", response.Values)
return nil, fmt.Errorf("error parsing pull request response for %s/%s: %w", b.projectKey, b.repositorySlug, err)
return nil, fmt.Errorf("error parsing pull request response for %s/%s: %v", b.projectKey, b.repositorySlug, err)
}
for _, pull := range pulls {

View File

@@ -7,10 +7,8 @@ import (
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/stretchr/testify/assert"
)
func defaultHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
@@ -56,10 +54,10 @@ func TestListPullRequestNoAuth(t *testing.T) {
}))
defer ts.Close()
svc, err := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
require.NoError(t, err)
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.NoError(t, err)
assert.Equal(t, 1, len(pullRequests))
assert.Equal(t, 101, pullRequests[0].Number)
assert.Equal(t, "feature-ABC-123", pullRequests[0].Branch)
assert.Equal(t, "master", pullRequests[0].TargetBranch)
@@ -137,10 +135,10 @@ func TestListPullRequestPagination(t *testing.T) {
}))
defer ts.Close()
svc, err := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
require.NoError(t, err)
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err)
assert.Len(t, pullRequests, 3)
assert.NoError(t, err)
assert.Equal(t, 3, len(pullRequests))
assert.Equal(t, PullRequest{
Number: 101,
Branch: "feature-101",
@@ -173,10 +171,10 @@ func TestListPullRequestBasicAuth(t *testing.T) {
}))
defer ts.Close()
svc, err := NewBitbucketServiceBasicAuth(context.Background(), "user", "password", ts.URL, "PROJECT", "REPO")
require.NoError(t, err)
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.NoError(t, err)
assert.Equal(t, 1, len(pullRequests))
assert.Equal(t, 101, pullRequests[0].Number)
assert.Equal(t, "feature-ABC-123", pullRequests[0].Branch)
assert.Equal(t, "cb3cf2e4d1517c83e720d2585b9402dbef71f992", pullRequests[0].HeadSHA)
@@ -189,7 +187,7 @@ func TestListResponseError(t *testing.T) {
defer ts.Close()
svc, _ := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
_, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.Error(t, err)
assert.Error(t, err)
}
func TestListResponseMalformed(t *testing.T) {
@@ -214,7 +212,7 @@ func TestListResponseMalformed(t *testing.T) {
defer ts.Close()
svc, _ := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
_, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.Error(t, err)
assert.Error(t, err)
}
func TestListResponseEmpty(t *testing.T) {
@@ -238,9 +236,9 @@ func TestListResponseEmpty(t *testing.T) {
}))
defer ts.Close()
svc, err := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
require.NoError(t, err)
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err)
assert.NoError(t, err)
assert.Empty(t, pullRequests)
}
@@ -316,14 +314,14 @@ func TestListPullRequestBranchMatch(t *testing.T) {
defer ts.Close()
regexp := `feature-1[\d]{2}`
svc, err := NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
require.NoError(t, err)
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{
BranchMatch: &regexp,
},
})
require.NoError(t, err)
assert.Len(t, pullRequests, 2)
assert.NoError(t, err)
assert.Equal(t, 2, len(pullRequests))
assert.Equal(t, PullRequest{
Number: 101,
Branch: "feature-101",
@@ -341,14 +339,14 @@ func TestListPullRequestBranchMatch(t *testing.T) {
regexp = `.*2$`
svc, err = NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
require.NoError(t, err)
assert.NoError(t, err)
pullRequests, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{
BranchMatch: &regexp,
},
})
require.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.NoError(t, err)
assert.Equal(t, 1, len(pullRequests))
assert.Equal(t, PullRequest{
Number: 102,
Branch: "feature-102",
@@ -359,11 +357,11 @@ func TestListPullRequestBranchMatch(t *testing.T) {
regexp = `[\d{2}`
svc, err = NewBitbucketServiceNoAuth(context.Background(), ts.URL, "PROJECT", "REPO")
require.NoError(t, err)
assert.NoError(t, err)
_, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{
BranchMatch: &regexp,
},
})
require.Error(t, err)
assert.Error(t, err)
}

View File

@@ -10,7 +10,6 @@ import (
"code.gitea.io/sdk/gitea"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func giteaMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
@@ -251,14 +250,14 @@ func TestGiteaList(t *testing.T) {
giteaMockHandler(t)(w, r)
}))
host, err := NewGiteaService(context.Background(), "", ts.URL, "test-argocd", "pr-test", false)
require.NoError(t, err)
assert.Nil(t, err)
prs, err := host.List(context.Background())
require.NoError(t, err)
assert.Len(t, prs, 1)
assert.Equal(t, 1, prs[0].Number)
assert.Equal(t, "test", prs[0].Branch)
assert.Equal(t, "main", prs[0].TargetBranch)
assert.Equal(t, "7bbaf62d92ddfafd9cc8b340c619abaec32bc09f", prs[0].HeadSHA)
assert.Nil(t, err)
assert.Equal(t, len(prs), 1)
assert.Equal(t, prs[0].Number, 1)
assert.Equal(t, prs[0].Branch, "test")
assert.Equal(t, prs[0].TargetBranch, "main")
assert.Equal(t, prs[0].HeadSHA, "7bbaf62d92ddfafd9cc8b340c619abaec32bc09f")
}
func TestGetGiteaPRLabelNames(t *testing.T) {

View File

@@ -6,10 +6,9 @@ import (
"net/http"
"os"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
"github.com/hashicorp/go-retryablehttp"
gitlab "github.com/xanzy/go-gitlab"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
)
type GitLabService struct {
@@ -43,7 +42,7 @@ func NewGitLabService(ctx context.Context, token, url, project string, labels []
client, err := gitlab.NewClient(token, clientOptionFns...)
if err != nil {
return nil, fmt.Errorf("error creating Gitlab client: %w", err)
return nil, fmt.Errorf("error creating Gitlab client: %v", err)
}
return &GitLabService{
@@ -55,6 +54,7 @@ func NewGitLabService(ctx context.Context, token, url, project string, labels []
}
func (g *GitLabService) List(ctx context.Context) ([]*PullRequest, error) {
// Filter the merge requests on labels, if they are specified.
var labels *gitlab.Labels
if len(g.labels) > 0 {
@@ -76,7 +76,7 @@ func (g *GitLabService) List(ctx context.Context) ([]*PullRequest, error) {
for {
mrs, resp, err := g.client.MergeRequests.ListProjectMergeRequests(g.project, opts)
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': %v", g.project, err)
}
for _, mr := range mrs {
pullRequests = append(pullRequests, &PullRequest{

View File

@@ -9,7 +9,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func writeMRListResponse(t *testing.T, w io.Writer) {
@@ -36,10 +35,10 @@ func TestGitLabServiceCustomBaseURL(t *testing.T) {
})
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", nil, "", "", false)
require.NoError(t, err)
assert.NoError(t, err)
_, err = svc.List(context.Background())
require.NoError(t, err)
assert.NoError(t, err)
}
func TestGitLabServiceToken(t *testing.T) {
@@ -55,10 +54,10 @@ func TestGitLabServiceToken(t *testing.T) {
})
svc, err := NewGitLabService(context.Background(), "token-123", server.URL, "278964", nil, "", "", false)
require.NoError(t, err)
assert.NoError(t, err)
_, err = svc.List(context.Background())
require.NoError(t, err)
assert.NoError(t, err)
}
func TestList(t *testing.T) {
@@ -74,15 +73,15 @@ func TestList(t *testing.T) {
})
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{}, "", "", false)
require.NoError(t, err)
assert.NoError(t, err)
prs, err := svc.List(context.Background())
require.NoError(t, err)
assert.NoError(t, err)
assert.Len(t, prs, 1)
assert.Equal(t, 15442, prs[0].Number)
assert.Equal(t, "use-structured-logging-for-db-load-balancer", prs[0].Branch)
assert.Equal(t, "master", prs[0].TargetBranch)
assert.Equal(t, "2fc4e8b972ff3208ec63b6143e34ad67ff343ad7", prs[0].HeadSHA)
assert.Equal(t, prs[0].Number, 15442)
assert.Equal(t, prs[0].Branch, "use-structured-logging-for-db-load-balancer")
assert.Equal(t, prs[0].TargetBranch, "master")
assert.Equal(t, prs[0].HeadSHA, "2fc4e8b972ff3208ec63b6143e34ad67ff343ad7")
}
func TestListWithLabels(t *testing.T) {
@@ -98,10 +97,10 @@ func TestListWithLabels(t *testing.T) {
})
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{"feature", "ready"}, "", "", false)
require.NoError(t, err)
assert.NoError(t, err)
_, err = svc.List(context.Background())
require.NoError(t, err)
assert.NoError(t, err)
}
func TestListWithState(t *testing.T) {
@@ -117,8 +116,8 @@ func TestListWithState(t *testing.T) {
})
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{}, "opened", "", false)
require.NoError(t, err)
assert.NoError(t, err)
_, err = svc.List(context.Background())
require.NoError(t, err)
assert.NoError(t, err)
}

View File

@@ -16,13 +16,13 @@ func compileFilters(filters []argoprojiov1alpha1.PullRequestGeneratorFilter) ([]
if filter.BranchMatch != nil {
outFilter.BranchMatch, err = regexp.Compile(*filter.BranchMatch)
if err != nil {
return nil, fmt.Errorf("error compiling BranchMatch regexp %q: %w", *filter.BranchMatch, err)
return nil, fmt.Errorf("error compiling BranchMatch regexp %q: %v", *filter.BranchMatch, err)
}
}
if filter.TargetBranchMatch != nil {
outFilter.TargetBranchMatch, err = regexp.Compile(*filter.TargetBranchMatch)
if err != nil {
return nil, fmt.Errorf("error compiling TargetBranchMatch regexp %q: %w", *filter.TargetBranchMatch, err)
return nil, fmt.Errorf("error compiling TargetBranchMatch regexp %q: %v", *filter.TargetBranchMatch, err)
}
}
outFilters = append(outFilters, outFilter)

View File

@@ -4,16 +4,13 @@ import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/stretchr/testify/assert"
)
func strp(s string) *string {
return &s
}
func TestFilterBranchMatchBadRegexp(t *testing.T) {
provider, _ := NewFakeService(
context.Background(),
@@ -33,7 +30,7 @@ func TestFilterBranchMatchBadRegexp(t *testing.T) {
},
}
_, err := ListPullRequests(context.Background(), provider, filters)
require.Error(t, err)
assert.Error(t, err)
}
func TestFilterBranchMatch(t *testing.T) {
@@ -73,7 +70,7 @@ func TestFilterBranchMatch(t *testing.T) {
},
}
pullRequests, err := ListPullRequests(context.Background(), provider, filters)
require.NoError(t, err)
assert.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.Equal(t, "two", pullRequests[0].Branch)
}
@@ -115,7 +112,7 @@ func TestFilterTargetBranchMatch(t *testing.T) {
},
}
pullRequests, err := ListPullRequests(context.Background(), provider, filters)
require.NoError(t, err)
assert.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.Equal(t, "two", pullRequests[0].Branch)
}
@@ -160,7 +157,7 @@ func TestMultiFilterOr(t *testing.T) {
},
}
pullRequests, err := ListPullRequests(context.Background(), provider, filters)
require.NoError(t, err)
assert.NoError(t, err)
assert.Len(t, pullRequests, 3)
assert.Equal(t, "two", pullRequests[0].Branch)
assert.Equal(t, "three", pullRequests[1].Branch)
@@ -209,7 +206,7 @@ func TestMultiFilterOrWithTargetBranchFilter(t *testing.T) {
},
}
pullRequests, err := ListPullRequests(context.Background(), provider, filters)
require.NoError(t, err)
assert.NoError(t, err)
assert.Len(t, pullRequests, 2)
assert.Equal(t, "two", pullRequests[0].Branch)
assert.Equal(t, "four", pullRequests[1].Branch)
@@ -236,7 +233,7 @@ func TestNoFilters(t *testing.T) {
)
filters := []argoprojiov1alpha1.PullRequestGeneratorFilter{}
repos, err := ListPullRequests(context.Background(), provider, filters)
require.NoError(t, err)
assert.NoError(t, err)
assert.Len(t, repos, 2)
assert.Equal(t, "one", repos[0].Branch)
assert.Equal(t, "two", repos[1].Branch)

View File

@@ -6,39 +6,49 @@ import (
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
"github.com/argoproj/argo-cd/v2/util/db"
"github.com/argoproj/argo-cd/v2/util/git"
"github.com/argoproj/argo-cd/v2/util/io"
)
//go:generate go run github.com/vektra/mockery/v2@v2.25.1 --name=RepositoryDB
// RepositoryDB Is a lean facade for ArgoDB,
// Using a lean interface makes it easier to test the functionality of the git generator
type RepositoryDB interface {
GetRepository(ctx context.Context, url string) (*v1alpha1.Repository, error)
}
type argoCDService struct {
getRepository func(ctx context.Context, url, project string) (*v1alpha1.Repository, error)
repositoriesDB RepositoryDB
storecreds git.CredsStore
submoduleEnabled bool
repoServerClientSet apiclient.Clientset
newFileGlobbingEnabled bool
}
//go:generate go run github.com/vektra/mockery/v2@v2.40.2 --name=Repos
//go:generate go run github.com/vektra/mockery/v2@v2.25.1 --name=Repos
type Repos interface {
// GetFiles returns content of files (not directories) within the target repo
GetFiles(ctx context.Context, repoURL string, revision string, pattern string, noRevisionCache, verifyCommit bool) (map[string][]byte, error)
GetFiles(ctx context.Context, repoURL string, revision string, pattern string, noRevisionCache bool) (map[string][]byte, error)
// GetDirectories returns a list of directories (not files) within the target repo
GetDirectories(ctx context.Context, repoURL string, revision string, noRevisionCache, verifyCommit bool) ([]string, error)
GetDirectories(ctx context.Context, repoURL string, revision string, noRevisionCache bool) ([]string, error)
}
func NewArgoCDService(getRepository func(ctx context.Context, url, project string) (*v1alpha1.Repository, error), submoduleEnabled bool, repoClientset apiclient.Clientset, newFileGlobbingEnabled bool) (Repos, error) {
func NewArgoCDService(db db.ArgoDB, submoduleEnabled bool, repoClientset apiclient.Clientset, newFileGlobbingEnabled bool) (Repos, error) {
return &argoCDService{
getRepository: getRepository,
repositoriesDB: db.(RepositoryDB),
submoduleEnabled: submoduleEnabled,
repoServerClientSet: repoClientset,
newFileGlobbingEnabled: newFileGlobbingEnabled,
}, nil
}
func (a *argoCDService) GetFiles(ctx context.Context, repoURL string, revision string, pattern string, noRevisionCache, verifyCommit bool) (map[string][]byte, error) {
repo, err := a.getRepository(ctx, repoURL, "")
func (a *argoCDService) GetFiles(ctx context.Context, repoURL string, revision string, pattern string, noRevisionCache bool) (map[string][]byte, error) {
repo, err := a.repositoriesDB.GetRepository(ctx, repoURL)
if err != nil {
return nil, fmt.Errorf("error in GetRepository: %w", err)
}
@@ -50,7 +60,6 @@ func (a *argoCDService) GetFiles(ctx context.Context, repoURL string, revision s
Path: pattern,
NewGitFileGlobbingEnabled: a.newFileGlobbingEnabled,
NoRevisionCache: noRevisionCache,
VerifyCommit: verifyCommit,
}
closer, client, err := a.repoServerClientSet.NewRepoServerClient()
if err != nil {
@@ -65,8 +74,8 @@ func (a *argoCDService) GetFiles(ctx context.Context, repoURL string, revision s
return fileResponse.GetMap(), nil
}
func (a *argoCDService) GetDirectories(ctx context.Context, repoURL string, revision string, noRevisionCache, verifyCommit bool) ([]string, error) {
repo, err := a.getRepository(ctx, repoURL, "")
func (a *argoCDService) GetDirectories(ctx context.Context, repoURL string, revision string, noRevisionCache bool) ([]string, error) {
repo, err := a.repositoriesDB.GetRepository(ctx, repoURL)
if err != nil {
return nil, fmt.Errorf("error in GetRepository: %w", err)
}
@@ -76,7 +85,6 @@ func (a *argoCDService) GetDirectories(ctx context.Context, repoURL string, revi
SubmoduleEnabled: a.submoduleEnabled,
Revision: revision,
NoRevisionCache: noRevisionCache,
VerifyCommit: verifyCommit,
}
closer, client, err := a.repoServerClientSet.NewRepoServerClient()
@@ -90,4 +98,5 @@ func (a *argoCDService) GetDirectories(ctx context.Context, repoURL string, revi
return nil, fmt.Errorf("error retrieving Git Directories: %w", err)
}
return dirResponse.GetPaths(), nil
}

View File

@@ -5,22 +5,23 @@ import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
repo_mocks "github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
db_mocks "github.com/argoproj/argo-cd/v2/util/db/mocks"
"github.com/argoproj/argo-cd/v2/util/git"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func TestGetDirectories(t *testing.T) {
type fields struct {
repositoriesDBFuncs []func(*mocks.RepositoryDB)
storecreds git.CredsStore
submoduleEnabled bool
getRepository func(ctx context.Context, url, project string) (*v1alpha1.Repository, error)
repoServerClientFuncs []func(*repo_mocks.RepoServerServiceClient)
}
type args struct {
@@ -28,7 +29,6 @@ func TestGetDirectories(t *testing.T) {
repoURL string
revision string
noRevisionCache bool
verifyCommit bool
}
tests := []struct {
name string
@@ -38,13 +38,17 @@ func TestGetDirectories(t *testing.T) {
wantErr assert.ErrorAssertionFunc
}{
{name: "ErrorGettingRepos", fields: fields{
getRepository: func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) {
return nil, fmt.Errorf("unable to get repos")
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("unable to get repos"))
},
},
}, args: args{}, want: nil, wantErr: assert.Error},
{name: "ErrorGettingDirs", fields: fields{
getRepository: func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) {
return &v1alpha1.Repository{}, nil
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(&v1alpha1.Repository{}, nil)
},
},
repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){
func(client *repo_mocks.RepoServerServiceClient) {
@@ -53,8 +57,10 @@ func TestGetDirectories(t *testing.T) {
},
}, args: args{}, want: nil, wantErr: assert.Error},
{name: "HappyCase", fields: fields{
getRepository: func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) {
return &v1alpha1.Repository{}, nil
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(&v1alpha1.Repository{}, nil)
},
},
repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){
func(client *repo_mocks.RepoServerServiceClient) {
@@ -64,32 +70,26 @@ func TestGetDirectories(t *testing.T) {
},
},
}, args: args{}, want: []string{"foo", "foo/bar", "bar/foo"}, wantErr: assert.NoError},
{name: "ErrorVerifyingCommit", fields: fields{
getRepository: func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) {
return &v1alpha1.Repository{}, nil
},
repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){
func(client *repo_mocks.RepoServerServiceClient) {
client.On("GetGitDirectories", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("revision HEAD is not signed"))
},
},
}, args: args{}, want: nil, wantErr: assert.Error},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockDb := &mocks.RepositoryDB{}
mockRepoClient := &repo_mocks.RepoServerServiceClient{}
// decorate the mocks
for i := range tt.fields.repositoriesDBFuncs {
tt.fields.repositoriesDBFuncs[i](mockDb)
}
for i := range tt.fields.repoServerClientFuncs {
tt.fields.repoServerClientFuncs[i](mockRepoClient)
}
a := &argoCDService{
getRepository: tt.fields.getRepository,
repositoriesDB: mockDb,
storecreds: tt.fields.storecreds,
submoduleEnabled: tt.fields.submoduleEnabled,
repoServerClientSet: &repo_mocks.Clientset{RepoServerServiceClient: mockRepoClient},
}
got, err := a.GetDirectories(tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.noRevisionCache, tt.args.verifyCommit)
got, err := a.GetDirectories(tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.noRevisionCache)
if !tt.wantErr(t, err, fmt.Sprintf("GetDirectories(%v, %v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.noRevisionCache)) {
return
}
@@ -100,10 +100,10 @@ func TestGetDirectories(t *testing.T) {
func TestGetFiles(t *testing.T) {
type fields struct {
repositoriesDBFuncs []func(*mocks.RepositoryDB)
storecreds git.CredsStore
submoduleEnabled bool
repoServerClientFuncs []func(*repo_mocks.RepoServerServiceClient)
getRepository func(ctx context.Context, url, project string) (*v1alpha1.Repository, error)
}
type args struct {
ctx context.Context
@@ -111,7 +111,6 @@ func TestGetFiles(t *testing.T) {
revision string
pattern string
noRevisionCache bool
verifyCommit bool
}
tests := []struct {
name string
@@ -121,13 +120,17 @@ func TestGetFiles(t *testing.T) {
wantErr assert.ErrorAssertionFunc
}{
{name: "ErrorGettingRepos", fields: fields{
getRepository: func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) {
return nil, fmt.Errorf("unable to get repos")
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("unable to get repos"))
},
},
}, args: args{}, want: nil, wantErr: assert.Error},
{name: "ErrorGettingFiles", fields: fields{
getRepository: func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) {
return &v1alpha1.Repository{}, nil
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(&v1alpha1.Repository{}, nil)
},
},
repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){
func(client *repo_mocks.RepoServerServiceClient) {
@@ -136,8 +139,10 @@ func TestGetFiles(t *testing.T) {
},
}, args: args{}, want: nil, wantErr: assert.Error},
{name: "HappyCase", fields: fields{
getRepository: func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) {
return &v1alpha1.Repository{}, nil
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(&v1alpha1.Repository{}, nil)
},
},
repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){
func(client *repo_mocks.RepoServerServiceClient) {
@@ -153,32 +158,26 @@ func TestGetFiles(t *testing.T) {
"foo.json": []byte("hello: world!"),
"bar.yaml": []byte("yay: appsets"),
}, wantErr: assert.NoError},
{name: "ErrorVerifyingCommit", fields: fields{
getRepository: func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) {
return &v1alpha1.Repository{}, nil
},
repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){
func(client *repo_mocks.RepoServerServiceClient) {
client.On("GetGitFiles", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("revision HEAD is not signed"))
},
},
}, args: args{}, want: nil, wantErr: assert.Error},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockDb := &mocks.RepositoryDB{}
mockRepoClient := &repo_mocks.RepoServerServiceClient{}
// decorate the mocks
for i := range tt.fields.repositoriesDBFuncs {
tt.fields.repositoriesDBFuncs[i](mockDb)
}
for i := range tt.fields.repoServerClientFuncs {
tt.fields.repoServerClientFuncs[i](mockRepoClient)
}
a := &argoCDService{
getRepository: tt.fields.getRepository,
repositoriesDB: mockDb,
storecreds: tt.fields.storecreds,
submoduleEnabled: tt.fields.submoduleEnabled,
repoServerClientSet: &repo_mocks.Clientset{RepoServerServiceClient: mockRepoClient},
}
got, err := a.GetFiles(tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern, tt.args.noRevisionCache, tt.args.verifyCommit)
got, err := a.GetFiles(tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern, tt.args.noRevisionCache)
if !tt.wantErr(t, err, fmt.Sprintf("GetFiles(%v, %v, %v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern, tt.args.noRevisionCache)) {
return
}
@@ -188,9 +187,7 @@ func TestGetFiles(t *testing.T) {
}
func TestNewArgoCDService(t *testing.T) {
service, err := NewArgoCDService(func(ctx context.Context, url, project string) (*v1alpha1.Repository, error) {
return &v1alpha1.Repository{}, nil
}, false, &repo_mocks.Clientset{}, false)
require.NoError(t, err)
service, err := NewArgoCDService(&db_mocks.ArgoDB{}, false, &repo_mocks.Clientset{}, false)
assert.NoError(t, err, err)
assert.NotNil(t, service)
}

View File

@@ -2,14 +2,13 @@ package scm_provider
import (
"context"
"errors"
"fmt"
"github.com/aws/aws-sdk-go/aws/request"
pathpkg "path"
"path/filepath"
"strings"
"github.com/aws/aws-sdk-go/aws/request"
application "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/aws/awserr"
@@ -20,8 +19,6 @@ import (
log "github.com/sirupsen/logrus"
"golang.org/x/exp/maps"
"k8s.io/utils/strings/slices"
application "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
const (
@@ -328,8 +325,7 @@ func getCodeCommitFIPSEndpoint(repoUrl string) (string, error) {
}
func hasAwsError(err error, codes ...string) bool {
var awsErr awserr.Error
if errors.As(err, &awsErr) {
if awsErr, ok := err.(awserr.Error); ok {
return slices.Contains(codes, awsErr.Code())
}
return false
@@ -358,7 +354,7 @@ func createAWSDiscoveryClients(_ context.Context, role string, region string) (*
Credentials: assumeRoleCreds,
})
if err != nil {
return nil, nil, fmt.Errorf("error creating new AWS discovery session: %w", err)
return nil, nil, fmt.Errorf("error creating new AWS discovery session: %s", err)
}
} else {
log.Debugf("role is not provided for AWS CodeCommit discovery, using pod role")

View File

@@ -6,15 +6,14 @@ import (
"sort"
"testing"
"github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider/aws_codecommit/mocks"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/codecommit"
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider/aws_codecommit/mocks"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
type awsCodeCommitTestRepository struct {

View File

@@ -2,7 +2,6 @@ package scm_provider
import (
"context"
"errors"
"fmt"
netUrl "net/url"
"strings"
@@ -52,10 +51,8 @@ type AzureDevOpsProvider struct {
allBranches bool
}
var (
_ SCMProviderService = &AzureDevOpsProvider{}
_ AzureDevOpsClientFactory = &devopsFactoryImpl{}
)
var _ SCMProviderService = &AzureDevOpsProvider{}
var _ AzureDevOpsClientFactory = &devopsFactoryImpl{}
func NewAzureDevOpsProvider(ctx context.Context, accessToken string, org string, url string, project string, allBranches bool) (*AzureDevOpsProvider, error) {
if accessToken == "" {
@@ -63,6 +60,7 @@ func NewAzureDevOpsProvider(ctx context.Context, accessToken string, org string,
}
devOpsURL, err := getValidDevOpsURL(url, org)
if err != nil {
return nil, err
}
@@ -79,6 +77,7 @@ func (g *AzureDevOpsProvider) ListRepos(ctx context.Context, cloneProtocol strin
}
getRepoArgs := azureGit.GetRepositoriesArgs{Project: &g.teamProject}
azureRepos, err := gitClient.GetRepositories(ctx, getRepoArgs)
if err != nil {
return nil, err
}
@@ -107,7 +106,7 @@ func (g *AzureDevOpsProvider) RepoHasPath(ctx context.Context, repo *Repository,
}
var repoId string
if uuid, isUuid := repo.RepositoryId.(uuid.UUID); isUuid { // most likely an UUID, but do type-safe check anyway. Do %v fallback if not expected type.
if uuid, isUuid := repo.RepositoryId.(uuid.UUID); isUuid { //most likely an UUID, but do type-safe check anyway. Do %v fallback if not expected type.
repoId = uuid.String()
} else {
repoId = fmt.Sprintf("%v", repo.RepositoryId)
@@ -116,9 +115,9 @@ func (g *AzureDevOpsProvider) RepoHasPath(ctx context.Context, repo *Repository,
branchName := repo.Branch
getItemArgs := azureGit.GetItemArgs{RepositoryId: &repoId, Project: &g.teamProject, Path: &path, VersionDescriptor: &azureGit.GitVersionDescriptor{Version: &branchName}}
_, err = gitClient.GetItem(ctx, getItemArgs)
if err != nil {
var wrappedError azuredevops.WrappedError
if errors.As(err, &wrappedError) && wrappedError.TypeKey != nil {
if wrappedError, isWrappedError := err.(azuredevops.WrappedError); isWrappedError && wrappedError.TypeKey != nil {
if *wrappedError.TypeKey == AzureDevOpsErrorsTypeKeyValues.GitItemNotFound {
return false, nil
}
@@ -139,12 +138,11 @@ func (g *AzureDevOpsProvider) GetBranches(ctx context.Context, repo *Repository)
repos := []*Repository{}
if !g.allBranches {
defaultBranchName := strings.Replace(repo.Branch, "refs/heads/", "", 1) // Azure DevOps returns default branch info like 'refs/heads/main', but does not support branch lookup of this format.
defaultBranchName := strings.Replace(repo.Branch, "refs/heads/", "", 1) //Azure DevOps returns default branch info like 'refs/heads/main', but does not support branch lookup of this format.
getBranchArgs := azureGit.GetBranchArgs{RepositoryId: &repo.Repository, Project: &g.teamProject, Name: &defaultBranchName}
branchResult, err := gitClient.GetBranch(ctx, getBranchArgs)
if err != nil {
var wrappedError azuredevops.WrappedError
if errors.As(err, &wrappedError) && wrappedError.TypeKey != nil {
if wrappedError, isWrappedError := err.(azuredevops.WrappedError); isWrappedError && wrappedError.TypeKey != nil {
if *wrappedError.TypeKey == AzureDevOpsErrorsTypeKeyValues.GitRepositoryNotFound {
return repos, nil
}
@@ -172,8 +170,7 @@ func (g *AzureDevOpsProvider) GetBranches(ctx context.Context, repo *Repository)
getBranchesRequest := azureGit.GetBranchesArgs{RepositoryId: &repo.Repository, Project: &g.teamProject}
branches, err := gitClient.GetBranches(ctx, getBranchesRequest)
if err != nil {
var wrappedError azuredevops.WrappedError
if errors.As(err, &wrappedError) && wrappedError.TypeKey != nil {
if wrappedError, isWrappedError := err.(azuredevops.WrappedError); isWrappedError && wrappedError.TypeKey != nil {
if *wrappedError.TypeKey == AzureDevOpsErrorsTypeKeyValues.GitRepositoryNotFound {
return repos, nil
}
@@ -212,6 +209,7 @@ func getValidDevOpsURL(url string, org string) (string, error) {
devOpsURL := fmt.Sprintf("%s%s%s", url, separator, org)
urlCheck, err := netUrl.ParseRequestURI(devOpsURL)
if err != nil {
return "", fmt.Errorf("got an invalid URL for the Azure SCM generator: %w", err)
}

View File

@@ -8,19 +8,15 @@ import (
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"k8s.io/utils/ptr"
"github.com/microsoft/azure-devops-go-api/azuredevops"
azureGit "github.com/microsoft/azure-devops-go-api/azuredevops/git"
"k8s.io/utils/pointer"
azureMock "github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider/azure_devops/git/mocks"
"github.com/microsoft/azure-devops-go-api/azuredevops"
azureGit "github.com/microsoft/azure-devops-go-api/azuredevops/git"
)
//go:generate go run github.com/vektra/mockery/v2@v2.40.2 --srcpkg=github.com/microsoft/azure-devops-go-api/azuredevops/git --name=Client --output=azure_devops/git/mocks --outpkg=mocks
func s(input string) *string {
return ptr.To(input)
return pointer.String(input)
}
func TestAzureDevopsRepoHasPath(t *testing.T) {
@@ -93,24 +89,26 @@ func TestAzureDevopsRepoHasPath(t *testing.T) {
hasPath, err := provider.RepoHasPath(ctx, repo, path)
if testCase.clientError != nil {
require.ErrorContains(t, err, testCase.clientError.Error())
assert.ErrorContains(t, err, testCase.clientError.Error())
gitClientMock.AssertNotCalled(t, "GetItem", ctx, azureGit.GetItemArgs{Project: &teamProject, Path: &path, VersionDescriptor: &azureGit.GitVersionDescriptor{Version: &branchName}, RepositoryId: repoId})
return
}
if testCase.returnError {
require.ErrorContains(t, err, testCase.errorMessage)
assert.ErrorContains(t, err, testCase.errorMessage)
}
assert.Equal(t, testCase.pathFound, hasPath)
gitClientMock.AssertCalled(t, "GetItem", ctx, azureGit.GetItemArgs{Project: &teamProject, Path: &path, VersionDescriptor: &azureGit.GitVersionDescriptor{Version: &branchName}, RepositoryId: repoId})
})
}
}
func TestGetDefaultBranchOnDisabledRepo(t *testing.T) {
organization := "myorg"
teamProject := "myorg_project"
repoName := "myorg_project_repo"
@@ -157,9 +155,9 @@ func TestGetDefaultBranchOnDisabledRepo(t *testing.T) {
branches, err := provider.GetBranches(ctx, repo)
if testCase.shouldReturnError {
require.Error(t, err)
assert.Error(t, err)
} else {
require.NoError(t, err)
assert.NoError(t, err)
}
assert.Empty(t, branches)
@@ -170,6 +168,7 @@ func TestGetDefaultBranchOnDisabledRepo(t *testing.T) {
}
func TestGetAllBranchesOnDisabledRepo(t *testing.T) {
organization := "myorg"
teamProject := "myorg_project"
repoName := "myorg_project_repo"
@@ -216,9 +215,9 @@ func TestGetAllBranchesOnDisabledRepo(t *testing.T) {
branches, err := provider.GetBranches(ctx, repo)
if testCase.shouldReturnError {
require.Error(t, err)
assert.Error(t, err)
} else {
require.NoError(t, err)
assert.NoError(t, err)
}
assert.Empty(t, branches)
@@ -229,7 +228,9 @@ func TestGetAllBranchesOnDisabledRepo(t *testing.T) {
}
func TestAzureDevOpsGetDefaultBranchStripsRefsName(t *testing.T) {
t.Run("Get branches only default branch removes characters before querying azure devops", func(t *testing.T) {
organization := "myorg"
teamProject := "myorg_project"
repoName := "myorg_project_repo"
@@ -252,7 +253,7 @@ func TestAzureDevOpsGetDefaultBranchStripsRefsName(t *testing.T) {
provider := AzureDevOpsProvider{organization: organization, teamProject: teamProject, clientFactory: clientFactoryMock, allBranches: false}
branches, err := provider.GetBranches(ctx, repo)
require.NoError(t, err)
assert.NoError(t, err)
assert.Len(t, branches, 1)
assert.Equal(t, strippedBranchName, branches[0].Branch)
@@ -309,7 +310,7 @@ func TestAzureDevOpsGetBranchesDefultBranchOnly(t *testing.T) {
branches, err := provider.GetBranches(ctx, repo)
if testCase.clientError != nil {
require.ErrorContains(t, err, testCase.clientError.Error())
assert.ErrorContains(t, err, testCase.clientError.Error())
gitClientMock.AssertNotCalled(t, "GetBranch", ctx, azureGit.GetBranchArgs{RepositoryId: &repoName, Project: &teamProject, Name: &defaultBranch})
return
@@ -317,7 +318,7 @@ func TestAzureDevOpsGetBranchesDefultBranchOnly(t *testing.T) {
if testCase.getBranchesApiError != nil {
assert.Empty(t, branches)
require.ErrorContains(t, err, testCase.getBranchesApiError.Error())
assert.ErrorContains(t, err, testCase.getBranchesApiError.Error())
} else {
if testCase.expectedBranch != nil {
assert.NotEmpty(t, branches)
@@ -393,20 +394,21 @@ func TestAzureDevopsGetBranches(t *testing.T) {
branches, err := provider.GetBranches(ctx, repo)
if testCase.expectedProcessingErrorMsg != "" {
require.ErrorContains(t, err, testCase.expectedProcessingErrorMsg)
assert.ErrorContains(t, err, testCase.expectedProcessingErrorMsg)
assert.Nil(t, branches)
return
}
if testCase.clientError != nil {
require.ErrorContains(t, err, testCase.clientError.Error())
assert.ErrorContains(t, err, testCase.clientError.Error())
gitClientMock.AssertNotCalled(t, "GetBranches", ctx, azureGit.GetBranchesArgs{RepositoryId: &repoName, Project: &teamProject})
return
}
if testCase.getBranchesApiError != nil {
assert.Empty(t, branches)
require.ErrorContains(t, err, testCase.getBranchesApiError.Error())
assert.ErrorContains(t, err, testCase.getBranchesApiError.Error())
} else {
if len(*testCase.expectedBranches) > 0 {
assert.NotEmpty(t, branches)
@@ -470,14 +472,14 @@ func TestGetAzureDevopsRepositories(t *testing.T) {
{Name: s("missing_default_branch"), RemoteUrl: s("https://remoteurl.u"), Id: repoId},
{DefaultBranch: s("missing_name"), RemoteUrl: s("https://remoteurl.u"), Id: repoId},
{Name: s("missing_remote_url"), DefaultBranch: s("main"), Id: repoId},
{Name: s("missing_id"), DefaultBranch: s("main"), RemoteUrl: s("https://remoteurl.u")},
},
{Name: s("missing_id"), DefaultBranch: s("main"), RemoteUrl: s("https://remoteurl.u")}},
expectedNumberOfRepos: 1,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
gitClientMock := azureMock.Client{}
gitClientMock.On("GetRepositories", ctx, azureGit.GetRepositoriesArgs{Project: s(teamProject)}).Return(&testCase.repositories, testCase.getRepositoriesError)
@@ -489,7 +491,7 @@ func TestGetAzureDevopsRepositories(t *testing.T) {
repositories, err := provider.ListRepos(ctx, "https")
if testCase.getRepositoriesError != nil {
require.Error(t, err, "Expected an error from test case %v", testCase.name)
assert.Error(t, err, "Expected an error from test case %v", testCase.name)
}
if testCase.expectedNumberOfRepos == 0 {

View File

@@ -52,6 +52,7 @@ func (c *ExtendedClient) GetContents(repo *Repository, path string) (bool, error
var _ SCMProviderService = &BitBucketCloudProvider{}
func NewBitBucketCloudProvider(ctx context.Context, owner string, user string, password string, allBranches bool) (*BitBucketCloudProvider, error) {
client := &ExtendedClient{
bitbucket.NewBasicAuth(user, password),
user,
@@ -65,13 +66,13 @@ func (g *BitBucketCloudProvider) GetBranches(ctx context.Context, repo *Reposito
repos := []*Repository{}
branches, err := g.listBranches(repo)
if err != nil {
return nil, fmt.Errorf("error listing branches for %s/%s: %w", repo.Organization, repo.Repository, err)
return nil, fmt.Errorf("error listing branches for %s/%s: %v", repo.Organization, repo.Repository, err)
}
for _, branch := range branches {
hash, ok := branch.Target["hash"].(string)
if !ok {
return nil, fmt.Errorf("error getting SHA for branch for %s/%s/%s: %w", g.owner, repo.Repository, branch.Name, err)
return nil, fmt.Errorf("error getting SHA for branch for %s/%s/%s: %v", g.owner, repo.Repository, branch.Name, err)
}
repos = append(repos, &Repository{
Organization: repo.Organization,
@@ -97,12 +98,12 @@ func (g *BitBucketCloudProvider) ListRepos(ctx context.Context, cloneProtocol st
repos := []*Repository{}
accountReposResp, err := g.client.Repositories.ListForAccount(opt)
if err != nil {
return nil, fmt.Errorf("error listing repositories for %s: %w", g.owner, err)
return nil, fmt.Errorf("error listing repositories for %s: %v", g.owner, err)
}
for _, bitBucketRepo := range accountReposResp.Items {
cloneUrl, err := findCloneURL(cloneProtocol, &bitBucketRepo)
if err != nil {
return nil, fmt.Errorf("error fetching clone url for repo %s: %w", bitBucketRepo.Slug, err)
return nil, fmt.Errorf("error fetching clone url for repo %s: %v", bitBucketRepo.Slug, err)
}
repos = append(repos, &Repository{
Organization: g.owner,
@@ -150,9 +151,11 @@ func (g *BitBucketCloudProvider) listBranches(repo *Repository) ([]bitbucket.Rep
return nil, err
}
return branches.Branches, nil
}
func findCloneURL(cloneProtocol string, repo *bitbucket.Repository) (*string, error) {
cloneLinks, ok := repo.Links["clone"].([]interface{})
if !ok {
return nil, fmt.Errorf("unknown type returned from repo links")

View File

@@ -8,7 +8,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
@@ -19,7 +18,7 @@ func TestBitbucketHasRepo(t *testing.T) {
res.WriteHeader(http.StatusNotFound)
_, err := res.Write([]byte(""))
if err != nil {
require.NoError(t, fmt.Errorf("Error in mock response %w", err))
assert.NoError(t, fmt.Errorf("Error in mock response %v", err))
}
}
if req.URL.Path == "/repositories/test-owner/testmike/src/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798/.gitignore" {
@@ -56,7 +55,7 @@ func TestBitbucketHasRepo(t *testing.T) {
"size": 624
}`))
if err != nil {
require.NoError(t, fmt.Errorf("Error in mock response %w", err))
assert.NoError(t, fmt.Errorf("Error in mock response %v", err))
}
}
}))
@@ -96,7 +95,7 @@ func TestBitbucketHasRepo(t *testing.T) {
}
hasPath, err := provider.RepoHasPath(context.Background(), repo, c.path)
if err != nil {
require.Error(t, fmt.Errorf("Error in test %w", err))
assert.Error(t, fmt.Errorf("Error in test %v", err))
}
if c.status != http.StatusOK {
assert.False(t, hasPath)
@@ -209,7 +208,7 @@ func TestBitbucketListRepos(t *testing.T) {
"size": 1
}`))
if err != nil {
require.NoError(t, fmt.Errorf("Error in mock response %w", err))
assert.NoError(t, fmt.Errorf("Error in mock response %v", err))
}
}
if req.URL.Path == "/repositories/test-owner/testmike/refs/branches/main" {
@@ -304,7 +303,7 @@ func TestBitbucketListRepos(t *testing.T) {
}
}`))
if err != nil {
require.NoError(t, fmt.Errorf("Error in mock response %w", err))
assert.NoError(t, fmt.Errorf("Error in mock response %v", err))
}
}
if req.URL.Path == "/repositories/test-owner" {
@@ -443,7 +442,7 @@ func TestBitbucketListRepos(t *testing.T) {
"size": 1
}`))
if err != nil {
require.NoError(t, fmt.Errorf("Error in mock response %w", err))
assert.NoError(t, fmt.Errorf("Error in mock response %v", err))
}
}
}))
@@ -490,9 +489,9 @@ func TestBitbucketListRepos(t *testing.T) {
provider, _ := NewBitBucketCloudProvider(context.Background(), c.owner, "user", "password", c.allBranches)
rawRepos, err := ListRepos(context.Background(), provider, c.filters, c.proto)
if c.hasError {
require.Error(t, err)
assert.Error(t, err)
} else {
require.NoError(t, err)
assert.NoError(t, err)
repos := []*Repository{}
branches := []string{}
for _, r := range rawRepos {

View File

@@ -2,14 +2,12 @@ package scm_provider
import (
"context"
"errors"
"fmt"
"io"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
bitbucketv1 "github.com/gfleury/go-bitbucket-v1"
log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
)
type BitbucketServerProvider struct {
@@ -56,12 +54,12 @@ func (b *BitbucketServerProvider) ListRepos(_ context.Context, cloneProtocol str
for {
response, err := b.client.DefaultApi.GetRepositoriesWithOptions(b.projectKey, paged)
if err != nil {
return nil, fmt.Errorf("error listing repositories for %s: %w", b.projectKey, err)
return nil, fmt.Errorf("error listing repositories for %s: %v", b.projectKey, err)
}
repositories, err := bitbucketv1.GetRepositoriesResponse(response)
if err != nil {
log.Errorf("error parsing repositories response '%v'", response.Values)
return nil, fmt.Errorf("error parsing repositories response %s: %w", b.projectKey, err)
return nil, fmt.Errorf("error parsing repositories response %s: %v", b.projectKey, err)
}
for _, bitbucketRepo := range repositories {
var url string
@@ -128,7 +126,7 @@ func (b *BitbucketServerProvider) GetBranches(_ context.Context, repo *Repositor
repos := []*Repository{}
branches, err := b.listBranches(repo)
if err != nil {
return nil, fmt.Errorf("error listing branches for %s/%s: %w", repo.Organization, repo.Repository, err)
return nil, fmt.Errorf("error listing branches for %s/%s: %v", repo.Organization, repo.Repository, err)
}
for _, branch := range branches {
@@ -165,12 +163,12 @@ func (b *BitbucketServerProvider) listBranches(repo *Repository) ([]bitbucketv1.
for {
response, err := b.client.DefaultApi.GetBranches(repo.Organization, repo.Repository, paged)
if err != nil {
return nil, fmt.Errorf("error listing branches for %s/%s: %w", repo.Organization, repo.Repository, err)
return nil, fmt.Errorf("error listing branches for %s/%s: %v", repo.Organization, repo.Repository, err)
}
bitbucketBranches, err := bitbucketv1.GetBranchesResponse(response)
if err != nil {
log.Errorf("error parsing branches response '%v'", response.Values)
return nil, fmt.Errorf("error parsing branches response for %s/%s: %w", repo.Organization, repo.Repository, err)
return nil, fmt.Errorf("error parsing branches response for %s/%s: %v", repo.Organization, repo.Repository, err)
}
branches = append(branches, bitbucketBranches...)
@@ -188,7 +186,7 @@ func (b *BitbucketServerProvider) getDefaultBranch(org string, repo string) (*bi
response, err := b.client.DefaultApi.GetDefaultBranch(org, repo)
// The API will return 404 if a default branch is set but doesn't exist. In case the repo is empty and default branch is unset,
// we will get an EOF and a nil response.
if (response != nil && response.StatusCode == 404) || (response == nil && err != nil && errors.Is(err, io.EOF)) {
if (response != nil && response.StatusCode == 404) || (response == nil && err == io.EOF) {
return nil, nil
}
if err != nil {

View File

@@ -8,7 +8,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func defaultHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
@@ -80,8 +79,8 @@ func defaultHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
}
func verifyDefaultRepo(t *testing.T, err error, repos []*Repository) {
require.NoError(t, err)
assert.Len(t, repos, 1)
assert.NoError(t, err)
assert.Equal(t, 1, len(repos))
assert.Equal(t, Repository{
Organization: "PROJECT",
Repository: "REPO",
@@ -100,7 +99,7 @@ func TestListReposNoAuth(t *testing.T) {
}))
defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", true)
require.NoError(t, err)
assert.NoError(t, err)
repos, err := provider.ListRepos(context.Background(), "ssh")
verifyDefaultRepo(t, err, repos)
}
@@ -192,10 +191,10 @@ func TestListReposPagination(t *testing.T) {
}))
defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", true)
require.NoError(t, err)
assert.NoError(t, err)
repos, err := provider.ListRepos(context.Background(), "ssh")
require.NoError(t, err)
assert.Len(t, repos, 2)
assert.NoError(t, err)
assert.Equal(t, 2, len(repos))
assert.Equal(t, Repository{
Organization: "PROJECT",
Repository: "REPO",
@@ -269,7 +268,7 @@ func TestGetBranchesBranchPagination(t *testing.T) {
}))
defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", true)
require.NoError(t, err)
assert.NoError(t, err)
repos, err := provider.GetBranches(context.Background(), &Repository{
Organization: "PROJECT",
Repository: "REPO",
@@ -277,8 +276,8 @@ func TestGetBranchesBranchPagination(t *testing.T) {
Labels: []string{},
RepositoryId: 1,
})
require.NoError(t, err)
assert.Len(t, repos, 2)
assert.NoError(t, err)
assert.Equal(t, 2, len(repos))
assert.Equal(t, Repository{
Organization: "PROJECT",
Repository: "REPO",
@@ -322,7 +321,7 @@ func TestGetBranchesDefaultOnly(t *testing.T) {
}))
defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false)
require.NoError(t, err)
assert.NoError(t, err)
repos, err := provider.GetBranches(context.Background(), &Repository{
Organization: "PROJECT",
Repository: "REPO",
@@ -330,8 +329,8 @@ func TestGetBranchesDefaultOnly(t *testing.T) {
Labels: []string{},
RepositoryId: 1,
})
require.NoError(t, err)
assert.Len(t, repos, 1)
assert.NoError(t, err)
assert.Equal(t, 1, len(repos))
assert.Equal(t, Repository{
Organization: "PROJECT",
Repository: "REPO",
@@ -354,7 +353,7 @@ func TestGetBranchesMissingDefault(t *testing.T) {
}))
defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false)
require.NoError(t, err)
assert.NoError(t, err)
repos, err := provider.GetBranches(context.Background(), &Repository{
Organization: "PROJECT",
Repository: "REPO",
@@ -362,7 +361,7 @@ func TestGetBranchesMissingDefault(t *testing.T) {
Labels: []string{},
RepositoryId: 1,
})
require.NoError(t, err)
assert.NoError(t, err)
assert.Empty(t, repos)
}
@@ -376,7 +375,7 @@ func TestGetBranchesEmptyRepo(t *testing.T) {
}))
defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false)
require.NoError(t, err)
assert.NoError(t, err)
repos, err := provider.GetBranches(context.Background(), &Repository{
Organization: "PROJECT",
Repository: "REPO",
@@ -385,7 +384,7 @@ func TestGetBranchesEmptyRepo(t *testing.T) {
RepositoryId: 1,
})
assert.Empty(t, repos)
require.NoError(t, err)
assert.NoError(t, err)
}
func TestGetBranchesErrorDefaultBranch(t *testing.T) {
@@ -399,7 +398,7 @@ func TestGetBranchesErrorDefaultBranch(t *testing.T) {
}))
defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false)
require.NoError(t, err)
assert.NoError(t, err)
_, err = provider.GetBranches(context.Background(), &Repository{
Organization: "PROJECT",
Repository: "REPO",
@@ -407,7 +406,7 @@ func TestGetBranchesErrorDefaultBranch(t *testing.T) {
Labels: []string{},
RepositoryId: 1,
})
require.Error(t, err)
assert.Error(t, err)
}
func TestListReposBasicAuth(t *testing.T) {
@@ -418,7 +417,7 @@ func TestListReposBasicAuth(t *testing.T) {
}))
defer ts.Close()
provider, err := NewBitbucketServerProviderBasicAuth(context.Background(), "user", "password", ts.URL, "PROJECT", true)
require.NoError(t, err)
assert.NoError(t, err)
repos, err := provider.ListRepos(context.Background(), "ssh")
verifyDefaultRepo(t, err, repos)
}
@@ -445,10 +444,10 @@ func TestListReposDefaultBranch(t *testing.T) {
}))
defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false)
require.NoError(t, err)
assert.NoError(t, err)
repos, err := provider.ListRepos(context.Background(), "ssh")
require.NoError(t, err)
assert.Len(t, repos, 1)
assert.NoError(t, err)
assert.Equal(t, 1, len(repos))
assert.Equal(t, Repository{
Organization: "PROJECT",
Repository: "REPO",
@@ -471,9 +470,9 @@ func TestListReposMissingDefaultBranch(t *testing.T) {
}))
defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false)
require.NoError(t, err)
assert.NoError(t, err)
repos, err := provider.ListRepos(context.Background(), "ssh")
require.NoError(t, err)
assert.NoError(t, err)
assert.Empty(t, repos)
}
@@ -488,9 +487,9 @@ func TestListReposErrorDefaultBranch(t *testing.T) {
}))
defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false)
require.NoError(t, err)
assert.NoError(t, err)
_, err = provider.ListRepos(context.Background(), "ssh")
require.Error(t, err)
assert.Error(t, err)
}
func TestListReposCloneProtocol(t *testing.T) {
@@ -500,10 +499,10 @@ func TestListReposCloneProtocol(t *testing.T) {
}))
defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", true)
require.NoError(t, err)
assert.NoError(t, err)
repos, err := provider.ListRepos(context.Background(), "https")
require.NoError(t, err)
assert.Len(t, repos, 1)
assert.NoError(t, err)
assert.Equal(t, 1, len(repos))
assert.Equal(t, Repository{
Organization: "PROJECT",
Repository: "REPO",
@@ -522,9 +521,9 @@ func TestListReposUnknownProtocol(t *testing.T) {
}))
defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", true)
require.NoError(t, err)
assert.NoError(t, err)
_, errProtocol := provider.ListRepos(context.Background(), "http")
require.Error(t, errProtocol)
assert.NotNil(t, errProtocol)
}
func TestBitbucketServerHasPath(t *testing.T) {
@@ -560,36 +559,36 @@ func TestBitbucketServerHasPath(t *testing.T) {
}))
defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", true)
require.NoError(t, err)
assert.NoError(t, err)
repo := &Repository{
Organization: "PROJECT",
Repository: "REPO",
Branch: "main",
}
ok, err := provider.RepoHasPath(context.Background(), repo, "pkg")
require.NoError(t, err)
assert.NoError(t, err)
assert.True(t, ok)
ok, err = provider.RepoHasPath(context.Background(), repo, "pkg/")
require.NoError(t, err)
assert.NoError(t, err)
assert.True(t, ok)
ok, err = provider.RepoHasPath(context.Background(), repo, "anotherpkg/file.txt")
require.NoError(t, err)
assert.NoError(t, err)
assert.True(t, ok)
ok, err = provider.RepoHasPath(context.Background(), repo, "anotherpkg/missing.txt")
require.NoError(t, err)
assert.NoError(t, err)
assert.False(t, ok)
ok, err = provider.RepoHasPath(context.Background(), repo, "notathing")
require.NoError(t, err)
assert.NoError(t, err)
assert.False(t, ok)
ok, err = provider.RepoHasPath(context.Background(), repo, "return-redirect")
require.NoError(t, err)
assert.NoError(t, err)
assert.True(t, ok)
_, err = provider.RepoHasPath(context.Background(), repo, "unauthorized-response")
require.Error(t, err)
assert.Error(t, err)
}

View File

@@ -258,7 +258,6 @@ func giteaMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
}
}
}
func TestGiteaListRepos(t *testing.T) {
cases := []struct {
name, proto, url string
@@ -306,9 +305,9 @@ func TestGiteaListRepos(t *testing.T) {
provider, _ := NewGiteaProvider(context.Background(), "test-argocd", "", ts.URL, c.allBranches, false)
rawRepos, err := ListRepos(context.Background(), provider, c.filters, c.proto)
if c.hasError {
require.Error(t, err)
assert.NotNil(t, err)
} else {
require.NoError(t, err)
assert.Nil(t, err)
// Just check that this one project shows up. Not a great test but better thing nothing?
repos := []*Repository{}
branches := []string{}
@@ -342,19 +341,19 @@ func TestGiteaHasPath(t *testing.T) {
t.Run("file exists", func(t *testing.T) {
ok, err := host.RepoHasPath(context.Background(), repo, "README.md")
require.NoError(t, err)
assert.Nil(t, err)
assert.True(t, ok)
})
t.Run("directory exists", func(t *testing.T) {
ok, err := host.RepoHasPath(context.Background(), repo, "gitea")
require.NoError(t, err)
assert.Nil(t, err)
assert.True(t, ok)
})
t.Run("does not exists", func(t *testing.T) {
ok, err := host.RepoHasPath(context.Background(), repo, "notathing")
require.NoError(t, err)
assert.Nil(t, err)
assert.False(t, ok)
})
}

View File

@@ -8,7 +8,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
@@ -245,9 +244,9 @@ func TestGithubListRepos(t *testing.T) {
provider, _ := NewGithubProvider(context.Background(), "argoproj", "", ts.URL, c.allBranches)
rawRepos, err := ListRepos(context.Background(), provider, c.filters, c.proto)
if c.hasError {
require.Error(t, err)
assert.Error(t, err)
} else {
require.NoError(t, err)
assert.NoError(t, err)
// Just check that this one project shows up. Not a great test but better thing nothing?
repos := []*Repository{}
branches := []string{}
@@ -279,11 +278,11 @@ func TestGithubHasPath(t *testing.T) {
Branch: "master",
}
ok, err := host.RepoHasPath(context.Background(), repo, "pkg/")
require.NoError(t, err)
assert.Nil(t, err)
assert.True(t, ok)
ok, err = host.RepoHasPath(context.Background(), repo, "notathing/")
require.NoError(t, err)
assert.Nil(t, err)
assert.False(t, ok)
}
@@ -300,26 +299,26 @@ func TestGithubGetBranches(t *testing.T) {
}
repos, err := host.GetBranches(context.Background(), repo)
if err != nil {
require.NoError(t, err)
assert.NoError(t, err)
} else {
assert.Equal(t, "master", repos[0].Branch)
assert.Equal(t, repos[0].Branch, "master")
}
// Branch Doesn't exists instead of error will return no error
//Branch Doesn't exists instead of error will return no error
repo2 := &Repository{
Organization: "argoproj",
Repository: "applicationset",
Branch: "main",
}
_, err = host.GetBranches(context.Background(), repo2)
require.NoError(t, err)
assert.NoError(t, err)
// Get all branches
host.allBranches = true
repos, err = host.GetBranches(context.Background(), repo)
if err != nil {
require.NoError(t, err)
assert.NoError(t, err)
} else {
// considering master branch to exist.
assert.Len(t, repos, 1)
assert.Equal(t, len(repos), 1)
}
}

View File

@@ -7,10 +7,9 @@ import (
"os"
pathpkg "path"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
"github.com/hashicorp/go-retryablehttp"
"github.com/xanzy/go-gitlab"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
)
type GitlabProvider struct {
@@ -58,7 +57,7 @@ func (g *GitlabProvider) GetBranches(ctx context.Context, repo *Repository) ([]*
repos := []*Repository{}
branches, err := g.listBranches(ctx, repo)
if err != nil {
return nil, fmt.Errorf("error listing branches for %s/%s: %w", repo.Organization, repo.Repository, err)
return nil, fmt.Errorf("error listing branches for %s/%s: %v", repo.Organization, repo.Repository, err)
}
for _, branch := range branches {
@@ -87,7 +86,7 @@ func (g *GitlabProvider) ListRepos(ctx context.Context, cloneProtocol string) ([
for {
gitlabRepos, resp, err := g.client.Groups.ListGroupProjects(g.organization, opt)
if err != nil {
return nil, fmt.Errorf("error listing projects for %s: %w", g.organization, err)
return nil, fmt.Errorf("error listing projects for %s: %v", g.organization, err)
}
for _, gitlabRepo := range gitlabRepos {
var url string
@@ -101,20 +100,12 @@ func (g *GitlabProvider) ListRepos(ctx context.Context, cloneProtocol string) ([
return nil, fmt.Errorf("unknown clone protocol for Gitlab %v", cloneProtocol)
}
var repoLabels []string
if len(gitlabRepo.Topics) == 0 {
// fallback to for gitlab prior to 14.5
repoLabels = gitlabRepo.TagList
} else {
repoLabels = gitlabRepo.Topics
}
repos = append(repos, &Repository{
Organization: gitlabRepo.Namespace.FullPath,
Repository: gitlabRepo.Path,
URL: url,
Branch: gitlabRepo.DefaultBranch,
Labels: repoLabels,
Labels: gitlabRepo.TagList,
RepositoryId: gitlabRepo.ID,
})
}

View File

@@ -9,7 +9,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
@@ -1047,7 +1046,6 @@ func gitlabMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
}
}
}
func TestGitlabListRepos(t *testing.T) {
cases := []struct {
name, proto, url, topic string
@@ -1065,16 +1063,6 @@ func TestGitlabListRepos(t *testing.T) {
proto: "ssh",
url: "git@gitlab.com:test-argocd-proton/argocd.git",
},
{
name: "labelmatch",
proto: "ssh",
url: "git@gitlab.com:test-argocd-proton/argocd.git",
filters: []v1alpha1.SCMProviderGeneratorFilter{
{
LabelMatch: strp("test-topic"),
},
},
},
{
name: "https protocol",
proto: "https",
@@ -1124,9 +1112,9 @@ func TestGitlabListRepos(t *testing.T) {
provider, _ := NewGitlabProvider(context.Background(), "test-argocd-proton", "", ts.URL, c.allBranches, c.includeSubgroups, c.includeSharedProjects, c.insecure, "", c.topic)
rawRepos, err := ListRepos(context.Background(), provider, c.filters, c.proto)
if c.hasError {
require.Error(t, err)
assert.NotNil(t, err)
} else {
require.NoError(t, err)
assert.Nil(t, err)
// Just check that this one project shows up. Not a great test but better than nothing?
repos := []*Repository{}
uniqueRepos := map[string]int{}
@@ -1145,11 +1133,11 @@ func TestGitlabListRepos(t *testing.T) {
}
// In case of listing subgroups, validate the number of returned projects
if c.includeSubgroups || c.includeSharedProjects {
assert.Len(t, uniqueRepos, 2)
assert.Equal(t, 2, len(uniqueRepos))
}
// In case we filter on the topic, ensure we got only one repo returned
if c.topic != "" {
assert.Len(t, uniqueRepos, 1)
assert.Equal(t, 1, len(uniqueRepos))
}
}
})
@@ -1196,7 +1184,7 @@ func TestGitlabHasPath(t *testing.T) {
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
ok, err := host.RepoHasPath(context.Background(), repo, c.path)
require.NoError(t, err)
assert.Nil(t, err)
assert.Equal(t, c.exists, ok)
})
}
@@ -1214,8 +1202,8 @@ func TestGitlabGetBranches(t *testing.T) {
}
t.Run("branch exists", func(t *testing.T) {
repos, err := host.GetBranches(context.Background(), repo)
require.NoError(t, err)
assert.Equal(t, "master", repos[0].Branch)
assert.Nil(t, err)
assert.Equal(t, repos[0].Branch, "master")
})
repo2 := &Repository{
@@ -1224,6 +1212,6 @@ func TestGitlabGetBranches(t *testing.T) {
}
t.Run("unknown branch", func(t *testing.T) {
_, err := host.GetBranches(context.Background(), repo2)
require.NoError(t, err)
assert.NoError(t, err)
})
}

View File

@@ -44,6 +44,7 @@ func (m *MockProvider) GetBranches(_ context.Context, repo *Repository) ([]*Repo
branchRepos = append(branchRepos, candidateRepo)
}
}
}
return branchRepos, nil
}

View File

@@ -17,14 +17,14 @@ func compileFilters(filters []argoprojiov1alpha1.SCMProviderGeneratorFilter) ([]
if filter.RepositoryMatch != nil {
outFilter.RepositoryMatch, err = regexp.Compile(*filter.RepositoryMatch)
if err != nil {
return nil, fmt.Errorf("error compiling RepositoryMatch regexp %q: %w", *filter.RepositoryMatch, err)
return nil, fmt.Errorf("error compiling RepositoryMatch regexp %q: %v", *filter.RepositoryMatch, err)
}
outFilter.FilterType = FilterTypeRepo
}
if filter.LabelMatch != nil {
outFilter.LabelMatch, err = regexp.Compile(*filter.LabelMatch)
if err != nil {
return nil, fmt.Errorf("error compiling LabelMatch regexp %q: %w", *filter.LabelMatch, err)
return nil, fmt.Errorf("error compiling LabelMatch regexp %q: %v", *filter.LabelMatch, err)
}
outFilter.FilterType = FilterTypeRepo
}
@@ -39,7 +39,7 @@ func compileFilters(filters []argoprojiov1alpha1.SCMProviderGeneratorFilter) ([]
if filter.BranchMatch != nil {
outFilter.BranchMatch, err = regexp.Compile(*filter.BranchMatch)
if err != nil {
return nil, fmt.Errorf("error compiling BranchMatch regexp %q: %w", *filter.BranchMatch, err)
return nil, fmt.Errorf("error compiling BranchMatch regexp %q: %v", *filter.BranchMatch, err)
}
outFilter.FilterType = FilterTypeBranch
}

View File

@@ -6,7 +6,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
@@ -38,7 +37,7 @@ func TestFilterRepoMatch(t *testing.T) {
},
}
repos, err := ListRepos(context.Background(), provider, filters, "")
require.NoError(t, err)
assert.Nil(t, err)
assert.Len(t, repos, 2)
assert.Equal(t, "one", repos[0].Repository)
assert.Equal(t, "three", repos[1].Repository)
@@ -67,7 +66,7 @@ func TestFilterLabelMatch(t *testing.T) {
},
}
repos, err := ListRepos(context.Background(), provider, filters, "")
require.NoError(t, err)
assert.Nil(t, err)
assert.Len(t, repos, 2)
assert.Equal(t, "one", repos[0].Repository)
assert.Equal(t, "two", repos[1].Repository)
@@ -93,7 +92,7 @@ func TestFilterPathExists(t *testing.T) {
},
}
repos, err := ListRepos(context.Background(), provider, filters, "")
require.NoError(t, err)
assert.Nil(t, err)
assert.Len(t, repos, 1)
assert.Equal(t, "two", repos[0].Repository)
}
@@ -118,10 +117,9 @@ func TestFilterPathDoesntExists(t *testing.T) {
},
}
repos, err := ListRepos(context.Background(), provider, filters, "")
require.NoError(t, err)
assert.Nil(t, err)
assert.Len(t, repos, 2)
}
func TestFilterRepoMatchBadRegexp(t *testing.T) {
provider := &MockProvider{
Repos: []*Repository{
@@ -136,7 +134,7 @@ func TestFilterRepoMatchBadRegexp(t *testing.T) {
},
}
_, err := ListRepos(context.Background(), provider, filters, "")
require.Error(t, err)
assert.NotNil(t, err)
}
func TestFilterLabelMatchBadRegexp(t *testing.T) {
@@ -153,7 +151,7 @@ func TestFilterLabelMatchBadRegexp(t *testing.T) {
},
}
_, err := ListRepos(context.Background(), provider, filters, "")
require.Error(t, err)
assert.NotNil(t, err)
}
func TestFilterBranchMatch(t *testing.T) {
@@ -187,7 +185,7 @@ func TestFilterBranchMatch(t *testing.T) {
},
}
repos, err := ListRepos(context.Background(), provider, filters, "")
require.NoError(t, err)
assert.Nil(t, err)
assert.Len(t, repos, 2)
assert.Equal(t, "one", repos[0].Repository)
assert.Equal(t, "two", repos[0].Branch)
@@ -219,7 +217,7 @@ func TestMultiFilterAnd(t *testing.T) {
},
}
repos, err := ListRepos(context.Background(), provider, filters, "")
require.NoError(t, err)
assert.Nil(t, err)
assert.Len(t, repos, 1)
assert.Equal(t, "two", repos[0].Repository)
}
@@ -250,7 +248,7 @@ func TestMultiFilterOr(t *testing.T) {
},
}
repos, err := ListRepos(context.Background(), provider, filters, "")
require.NoError(t, err)
assert.Nil(t, err)
assert.Len(t, repos, 3)
assert.Equal(t, "one", repos[0].Repository)
assert.Equal(t, "two", repos[1].Repository)
@@ -276,7 +274,7 @@ func TestNoFilters(t *testing.T) {
}
filters := []argoprojiov1alpha1.SCMProviderGeneratorFilter{}
repos, err := ListRepos(context.Background(), provider, filters, "")
require.NoError(t, err)
assert.Nil(t, err)
assert.Len(t, repos, 3)
assert.Equal(t, "one", repos[0].Repository)
assert.Equal(t, "two", repos[1].Repository)
@@ -313,10 +311,8 @@ func TestApplicableFilterMap(t *testing.T) {
BranchMatch: &regexp.Regexp{},
FilterType: FilterTypeBranch,
}
filterMap := getApplicableFilters([]*Filter{
&branchFilter, &repoFilter,
&pathExistsFilter, &labelMatchFilter, &unsetFilter, &additionalBranchFilter, &pathDoesntExistsFilter,
})
filterMap := getApplicableFilters([]*Filter{&branchFilter, &repoFilter,
&pathExistsFilter, &labelMatchFilter, &unsetFilter, &additionalBranchFilter, &pathDoesntExistsFilter})
assert.Len(t, filterMap[FilterTypeRepo], 2)
assert.Len(t, filterMap[FilterTypeBranch], 4)

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