Compare commits

...

1 Commits

Author SHA1 Message Date
Zadkiel Aharonian
3aba0a4c6e sibling of 9eca44b4b7 2023-11-01 14:52:26 +00:00
1125 changed files with 29763 additions and 119051 deletions

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

@@ -1,15 +0,0 @@
{
"LABEL": {
"name": "title needs formatting",
"color": "EEEEEE"
},
"CHECKS": {
"prefixes": ["[Bot] docs: "],
"regexp": "^(feat|fix|docs|test|ci|chore)!?(\\(.*\\))?!?:.*"
},
"MESSAGES": {
"success": "PR title is valid",
"failure": "PR title is invalid",
"notice": "PR Title needs to pass regex '^(feat|fix|docs|test|ci|chore)!?(\\(.*\\))?!?:.*"
}
}

View File

@@ -1,14 +1,11 @@
<!--
Note on DCO:
If the DCO action in the integration test fails, one or more of your commits are not signed off. Please click on the *Details* link next to the DCO action for instructions on how to resolve this.
-->
Checklist:
* [ ] Either (a) I've created an [enhancement proposal](https://github.com/argoproj/argo-cd/issues/new/choose) and discussed it with the community, (b) this is a bug fix, or (c) this does not need to be in the release notes.
* [ ] The title of the PR states what changed and the related issues number (used for the release note).
* [ ] The title of the PR conforms to the [Toolchain Guide](https://argo-cd.readthedocs.io/en/latest/developer-guide/toolchain-guide/#title-of-the-pr)
* [ ] I've included "Closes [ISSUE #]" or "Fixes [ISSUE #]" in the description to automatically close the associated issue.
* [ ] 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?
@@ -16,8 +13,8 @@ Checklist:
* [ ] 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 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.
<!-- Please see [Contribution FAQs](https://argo-cd.readthedocs.io/en/latest/developer-guide/faq/) if you have questions about your pull-request. -->
Please see [Contribution FAQs](https://argo-cd.readthedocs.io/en/latest/developer-guide/faq/) if you have questions about your pull-request.

View File

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

View File

@@ -13,7 +13,7 @@ on:
env:
# Golang version to use across CI steps
GOLANG_VERSION: '1.21'
GOLANG_VERSION: '1.20'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -28,9 +28,9 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Setup Golang
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Download all Go modules
@@ -46,13 +46,13 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Setup Golang
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Restore go build cache
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -70,16 +70,16 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Setup Golang
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Run golangci-lint
uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0
uses: golangci/golangci-lint-action@0ad9a0988b3973e851ab0a07adf248ec2e100376 # v3.3.1
with:
version: v1.54.0
args: --enable gofmt --timeout 10m --exclude SA5011 --verbose --max-issues-per-linter 0 --max-same-issues 0
version: v1.51.0
args: --timeout 10m --exclude SA5011 --verbose
test-go:
name: Run unit tests for Go packages
@@ -93,11 +93,11 @@ jobs:
- name: Create checkout directory
run: mkdir -p ~/go/src/github.com/argoproj
- name: Checkout code
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Create symlink in GOPATH
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
- name: Setup Golang
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Install required packages
@@ -117,7 +117,7 @@ jobs:
run: |
echo "/usr/local/bin" >> $GITHUB_PATH
- name: Restore go build cache
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -138,18 +138,18 @@ jobs:
- name: Run all unit tests
run: make test-local
- name: Generate code coverage artifacts
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
name: code-coverage
path: coverage.out
- name: Generate test results artifacts
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
name: test-results
path: test-results/
test-go-race:
name: Run unit tests with -race for Go packages
name: Run unit tests with -race, for Go packages
runs-on: ubuntu-22.04
needs:
- build-go
@@ -160,11 +160,11 @@ jobs:
- name: Create checkout directory
run: mkdir -p ~/go/src/github.com/argoproj
- name: Checkout code
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Create symlink in GOPATH
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
- name: Setup Golang
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Install required packages
@@ -184,7 +184,7 @@ jobs:
run: |
echo "/usr/local/bin" >> $GITHUB_PATH
- name: Restore go build cache
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -205,7 +205,7 @@ jobs:
- name: Run all unit tests
run: make test-race-local
- name: Generate test results artifacts
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
name: race-results
path: test-results/
@@ -215,9 +215,9 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Setup Golang
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Create symlink in GOPATH
@@ -263,14 +263,14 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Setup NodeJS
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
with:
node-version: '20.7.0'
node-version: '18.15.0'
- name: Restore node dependency cache
id: cache-dependencies
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with:
path: ui/node_modules
key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }}
@@ -300,12 +300,12 @@ jobs:
sonar_secret: ${{ secrets.SONAR_TOKEN }}
steps:
- name: Checkout code
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
with:
fetch-depth: 0
- name: Restore node dependency cache
id: cache-dependencies
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with:
path: ui/node_modules
key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }}
@@ -325,7 +325,7 @@ jobs:
name: test-results
path: test-results
- name: Upload code coverage information to codecov.io
uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4
uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1
with:
file: coverage.out
- name: Perform static code analysis using SonarCloud
@@ -360,9 +360,8 @@ jobs:
name: Run end-to-end tests
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
k3s-version: [v1.28.2, v1.27.6, v1.26.9, v1.25.14]
k3s-version: [v1.26.0, v1.25.4, v1.24.3, v1.23.3]
needs:
- build-go
env:
@@ -380,9 +379,9 @@ jobs:
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
steps:
- name: Checkout code
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Setup Golang
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: GH actions workaround - Kill XSP4 process
@@ -398,10 +397,9 @@ jobs:
sudo mkdir -p $HOME/.kube && sudo chown -R runner $HOME/.kube
sudo k3s kubectl config view --raw > $HOME/.kube/config
sudo chown runner $HOME/.kube/config
sudo chmod go-r $HOME/.kube/config
kubectl version
- name: Restore go build cache
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -457,7 +455,7 @@ jobs:
set -x
make test-e2e-local
- name: Upload e2e-server logs
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
name: e2e-server-k8s${{ matrix.k3s-version }}.log
path: /tmp/e2e-server.log

View File

@@ -30,7 +30,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL

View File

@@ -58,28 +58,28 @@ jobs:
image-digest: ${{ steps.image.outputs.digest }}
steps:
- name: Checkout code
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.3.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
if: ${{ github.ref_type == 'tag'}}
- name: Checkout code
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
if: ${{ github.ref_type != 'tag'}}
- name: Setup Golang
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
with:
go-version: ${{ inputs.go-version }}
- name: Install cosign
uses: sigstore/cosign-installer@11086d25041f77fe8fe7b9ea4e48e3b9192b8f19 # v3.1.2
uses: sigstore/cosign-installer@c3667d99424e7e6047999fb6246c0da843953c65 # v3.0.1
with:
cosign-release: 'v2.0.2'
cosign-release: 'v2.0.0'
- uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
- uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
- uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0
- uses: docker/setup-buildx-action@4b4e9c3e2d4531116a6f8ba8e71fc6e2cb6e6c8c # v2.5.0
- name: Setup tags for container image as a CSV type
run: |
@@ -106,7 +106,7 @@ jobs:
echo 'EOF' >> $GITHUB_ENV
- name: Login to Quay.io
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
with:
registry: quay.io
username: ${{ secrets.quay_username }}
@@ -114,7 +114,7 @@ jobs:
if: ${{ inputs.quay_image_name && inputs.push }}
- name: Login to GitHub Container Registry
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
with:
registry: ghcr.io
username: ${{ secrets.ghcr_username }}
@@ -122,30 +122,15 @@ jobs:
if: ${{ inputs.ghcr_image_name && inputs.push }}
- name: Login to dockerhub Container Registry
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
with:
username: ${{ secrets.docker_username }}
password: ${{ secrets.docker_password }}
if: ${{ inputs.docker_image_name && inputs.push }}
- name: Set up build args for container image
run: |
echo "GIT_TAG=$(if [ -z "`git status --porcelain`" ]; then git describe --exact-match --tags HEAD 2>/dev/null; fi)" >> $GITHUB_ENV
echo "GIT_COMMIT=$(git rev-parse HEAD)" >> $GITHUB_ENV
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $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@4d9e71b726748f254fe64fa44d273194bd18ec91
with:
large-packages: false
docker-images: false
swap-storage: false
tool-cache: false
- name: Build and push container image
id: image
uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 #v4.1.1
uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 #v4.0.0
with:
context: .
platforms: ${{ inputs.platforms }}
@@ -154,12 +139,7 @@ jobs:
target: ${{ inputs.target }}
provenance: false
sbom: false
build-args: |
GIT_TAG=${{env.GIT_TAG}}
GIT_COMMIT=${{env.GIT_COMMIT}}
BUILD_DATE=${{env.BUILD_DATE}}
GIT_TREE_STATE=${{env.GIT_TREE_STATE}}
- name: Sign container images
run: |
for signing_tag in $SIGNING_TAGS; do

View File

@@ -25,7 +25,7 @@ jobs:
image-tag: ${{ steps.image.outputs.tag}}
platforms: ${{ steps.platforms.outputs.platforms }}
steps:
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.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.21
go-version: 1.20
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.21
go-version: 1.20
platforms: ${{ needs.set-vars.outputs.platforms }}
push: true
secrets:
@@ -77,22 +77,21 @@ jobs:
ghcr_username: ${{ github.actor }}
ghcr_password: ${{ secrets.GITHUB_TOKEN }}
build-and-publish-provenance: # Push attestations to GHCR, latest image is polluting quay.io
needs:
- build-and-publish
build-and-publish-provenance:
needs: [build-and-publish]
permissions:
actions: read # for detecting the Github Actions environment.
id-token: write # for creating OIDC tokens for signing.
packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.7.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.8.0
with:
image: ghcr.io/argoproj/argo-cd/argocd
image: quay.io/argoproj/argocd
digest: ${{ needs.build-and-publish.outputs.image-digest }}
registry-username: ${{ github.actor }}
secrets:
registry-password: ${{ secrets.GITHUB_TOKEN }}
registry-username: ${{ secrets.RELEASE_QUAY_USERNAME }}
registry-password: ${{ secrets.RELEASE_QUAY_TOKEN }}
Deploy:
needs:
@@ -104,7 +103,7 @@ jobs:
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.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@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
@@ -56,15 +56,8 @@ jobs:
make manifests-local VERSION=${{ inputs.TARGET_VERSION }}
git diff
- name: Generate version compatibility table
run: |
git stash
bash hack/update-supported-versions.sh
git add -u .
git stash pop
- name: Create pull request
uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 # v5.0.2
uses: peter-evans/create-pull-request@38e0b6e68b4c852a5500a94740f0e535e0d7ba54 # v4.2.4
with:
commit-message: "Bump version to ${{ inputs.TARGET_VERSION }}"
title: "Bump version to ${{ inputs.TARGET_VERSION }} on ${{ inputs.TARGET_BRANCH }} branch"

View File

@@ -2,11 +2,15 @@ name: "Lint PR"
on:
pull_request_target:
types: [opened, edited, reopened, synchronize]
types:
- opened
- edited
- synchronize
# IMPORTANT: No checkout actions, scripts, or builds should be added to this workflow. Permissions should always be used
# with extreme caution. https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target
permissions: {}
# with extreme caution.
permissions:
contents: read
# PR updates can happen in quick succession leading to this
# workflow being trigger a number of times. This limits it
@@ -14,16 +18,24 @@ permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
validate:
main:
permissions:
contents: read
pull-requests: read
name: Validate PR Title
pull-requests: read # for amannn/action-semantic-pull-request to analyze PRs
statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR
name: Validate PR title
runs-on: ubuntu-latest
steps:
- uses: thehanimo/pr-title-checker@0cf5902181e78341bb97bb06646396e5bd354b3f # v1.4.0
# IMPORTANT: Carefully review changes when updating this action. Using the pull_request_target event requires caution.
- uses: amannn/action-semantic-pull-request@b6bca70dcd3e56e896605356ce09b76f7e1e0d39 # v5.1.0
with:
types: |
feat
fix
docs
test
ci
chore
[Bot] docs
env:
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.21' # Note: go-version must also be set in job argocd-image.with.go-version
GOLANG_VERSION: '1.20' # 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.21
go-version: 1.20
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@v1.9.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.8.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@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
@@ -77,7 +77,7 @@ jobs:
fi
- name: Setup Golang
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.0.0
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
@@ -88,7 +88,7 @@ jobs:
echo "GIT_TREE_STATE=$(if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)" >> $GITHUB_ENV
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0
uses: goreleaser/goreleaser-action@f82d6c1c344bcacabba2c841718984797f664a6b # v4.2.0
id: run-goreleaser
with:
version: latest
@@ -120,35 +120,39 @@ jobs:
contents: write # Needed for release uploads
if: github.repository == 'argoproj/argo-cd'
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.8.0
with:
base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
provenance-name: "argocd-cli.intoto.jsonl"
upload-assets: true
generate-sbom:
name: Create SBOM and generate hash
name: Create Sbom and sign assets
needs:
- argocd-image
- goreleaser
permissions:
contents: write # Needed for release uploads
outputs:
hashes: ${{ steps.sbom-hash.outputs.hashes}}
id-token: write # Needed for signing Sbom
if: github.repository == 'argoproj/argo-cd'
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Golang
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Install cosign
uses: sigstore/cosign-installer@c3667d99424e7e6047999fb6246c0da843953c65 # v3.0.1
with:
cosign-release: 'v2.0.0'
- name: Generate SBOM (spdx)
id: spdx-builder
env:
@@ -178,38 +182,23 @@ jobs:
fi
cd /tmp && tar -zcf sbom.tar.gz *.spdx
- name: Generate SBOM hash
shell: bash
id: sbom-hash
- name: Sign SBOM
run: |
# sha256sum generates sha256 hash for sbom.
# base64 -w0 encodes to base64 and outputs on a single line.
# sha256sum /tmp/sbom.tar.gz ... | base64 -w0
echo "hashes=$(sha256sum /tmp/sbom.tar.gz | base64 -w0)" >> "$GITHUB_OUTPUT"
- name: Upload SBOM
cosign sign-blob \
--output-certificate=/tmp/sbom.tar.gz.pem \
--output-signature=/tmp/sbom.tar.gz.sig \
-y \
/tmp/sbom.tar.gz
- name: Upload SBOM and signature assets
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: |
/tmp/sbom.tar.gz
sbom-provenance:
needs: [generate-sbom]
permissions:
actions: read # for detecting the Github Actions environment
id-token: write # Needed for provenance signing and ID
contents: write # Needed for release uploads
if: github.repository == 'argoproj/argo-cd'
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0
with:
base64-subjects: "${{ needs.generate-sbom.outputs.hashes }}"
provenance-name: "argocd-sbom.intoto.jsonl"
upload-assets: true
/tmp/sbom.tar.*
post-release:
needs:
- argocd-image
@@ -222,7 +211,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
@@ -265,8 +254,8 @@ jobs:
set -xue
SOURCE_TAG=${{ github.ref_name }}
VERSION_REF="${SOURCE_TAG#*v}"
if echo "$VERSION_REF" | grep -E -- '^[0-9]+\.[0-9]+\.0-rc1';then
VERSION=$(awk 'BEGIN {FS=OFS="."} {$2++; print}' <<< "${VERSION_REF%-rc1}")
if echo "$VERSION_REF" | grep -E -- '^[0-9]+\.[0-9]+\.0$';then
VERSION=$(awk 'BEGIN {FS=OFS="."} {$2++; print}' <<< "${VERSION_REF}")
echo "Updating VERSION to: $VERSION"
echo "UPDATE_VERSION=true" >> $GITHUB_ENV
echo "NEW_VERSION=$VERSION" >> $GITHUB_ENV
@@ -281,7 +270,7 @@ jobs:
if: ${{ env.UPDATE_VERSION == 'true' }}
- name: Create PR to update VERSION on master branch
uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 # v5.0.2
uses: peter-evans/create-pull-request@38e0b6e68b4c852a5500a94740f0e535e0d7ba54 # v4.2.4
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@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031 # v2.2.0
uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2
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@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
name: SARIF file
path: results.sarif

View File

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

1
.gitignore vendored
View File

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

4
.gitpod.Dockerfile vendored
View File

@@ -1,4 +1,4 @@
FROM gitpod/workspace-full@sha256:511cecde4dc129ca9eb4cc4c479d61f95e5485ebe320a07f5b902f11899956a3
FROM gitpod/workspace-full@sha256:d5787229cd062aceae91109f1690013d3f25062916492fb7f444d13de3186178
USER root
@@ -13,8 +13,6 @@ ENV GOCACHE=/go-build-cache
RUN apt-get install redis-server -y
RUN go install github.com/mattn/goreman@latest
RUN chown -R gitpod:gitpod /go-build-cache
USER gitpod
ENV ARGOCD_REDIS_LOCAL=true

View File

@@ -114,7 +114,6 @@ changelog:
exclude:
- '^test:'
- '^.*?Bump(\([[:word:]]+\))?.+$'
- '^.*?[Bot](\([[:word:]]+\))?.+$'
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json

View File

@@ -1,11 +0,0 @@
# All
** @argoproj/argocd-approvers
# Docs
/docs/** @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
/USERS.md @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
/mkdocs.yml @argoproj/argocd-approvers @argoproj/argocd-approvers-docs
# 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

@@ -4,7 +4,7 @@ ARG BASE_IMAGE=docker.io/library/ubuntu:22.04@sha256:0bced47fffa3361afa981854fca
# 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.21.3@sha256:02d7116222536a5cf0fcf631f90b507758b669648e0f20186d2dc94a9b419a9b AS builder
FROM docker.io/library/golang:1.20.10@sha256:077ff85b374b23916b4b41835e242e5a3ddad9fc537ea7e980f230431747d245 AS builder
RUN echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list
@@ -83,7 +83,7 @@ WORKDIR /home/argocd
####################################################################################################
# Argo CD UI stage
####################################################################################################
FROM --platform=$BUILDPLATFORM docker.io/library/node:20.6.1@sha256:14bd39208dbc0eb171cbfb26ccb9ac09fa1b2eba04ccd528ab5d12983fd9ee24 AS argocd-ui
FROM --platform=$BUILDPLATFORM docker.io/library/node:18.15.0@sha256:8d9a875ee427897ef245302e31e2319385b092f1c3368b497e89790f240368f5 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.21.3@sha256:02d7116222536a5cf0fcf631f90b507758b669648e0f20186d2dc94a9b419a9b AS argocd-build
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.20.10@sha256:077ff85b374b23916b4b41835e242e5a3ddad9fc537ea7e980f230431747d245 AS argocd-build
WORKDIR /go/src/github.com/argoproj/argo-cd
@@ -113,18 +113,7 @@ COPY . .
COPY --from=argocd-ui /src/dist/app /go/src/github.com/argoproj/argo-cd/ui/dist/app
ARG TARGETOS
ARG TARGETARCH
# These build args are optional; if not specified the defaults will be taken from the Makefile
ARG GIT_TAG
ARG BUILD_DATE
ARG GIT_TREE_STATE
ARG GIT_COMMIT
RUN GIT_COMMIT=$GIT_COMMIT \
GIT_TREE_STATE=$GIT_TREE_STATE \
GIT_TAG=$GIT_TAG \
BUILD_DATE=$BUILD_DATE \
GOOS=$TARGETOS \
GOARCH=$TARGETARCH \
make argocd-all
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH make argocd-all
####################################################################################################
# Final image

View File

@@ -9,13 +9,11 @@ GEN_RESOURCES_CLI_NAME=argocd-resources-gen
HOST_OS:=$(shell go env GOOS)
HOST_ARCH:=$(shell go env GOARCH)
TARGET_ARCH?=linux/amd64
VERSION=$(shell cat ${CURRENT_DIR}/VERSION)
BUILD_DATE:=$(if $(BUILD_DATE),$(BUILD_DATE),$(shell date -u +'%Y-%m-%dT%H:%M:%SZ'))
GIT_COMMIT:=$(if $(GIT_COMMIT),$(GIT_COMMIT),$(shell git rev-parse HEAD))
GIT_TAG:=$(if $(GIT_TAG),$(GIT_TAG),$(shell if [ -z "`git status --porcelain`" ]; then git describe --exact-match --tags HEAD 2>/dev/null; fi))
GIT_TREE_STATE:=$(if $(GIT_TREE_STATE),$(GIT_TREE_STATE),$(shell if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi))
BUILD_DATE=$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')
GIT_COMMIT=$(shell git rev-parse HEAD)
GIT_TAG=$(shell if [ -z "`git status --porcelain`" ]; then git describe --exact-match --tags HEAD 2>/dev/null; fi)
GIT_TREE_STATE=$(shell if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi)
VOLUME_MOUNT=$(shell if test "$(go env GOOS)" = "darwin"; then echo ":delegated"; elif test selinuxenabled; then echo ":delegated"; else echo ""; fi)
KUBECTL_VERSION=$(shell go list -m k8s.io/client-go | head -n 1 | rev | cut -d' ' -f1 | rev)
@@ -49,7 +47,7 @@ ARGOCD_E2E_DEX_PORT?=5556
ARGOCD_E2E_YARN_HOST?=localhost
ARGOCD_E2E_DISABLE_AUTH?=
ARGOCD_E2E_TEST_TIMEOUT?=60m
ARGOCD_E2E_TEST_TIMEOUT?=45m
ARGOCD_IN_CI?=false
ARGOCD_TEST_E2E?=true
@@ -148,8 +146,7 @@ override LDFLAGS += \
-X ${PACKAGE}.buildDate=${BUILD_DATE} \
-X ${PACKAGE}.gitCommit=${GIT_COMMIT} \
-X ${PACKAGE}.gitTreeState=${GIT_TREE_STATE}\
-X ${PACKAGE}.kubectlVersion=${KUBECTL_VERSION}\
-X "${PACKAGE}.extraBuildInfo=${EXTRA_BUILD_INFO}"
-X ${PACKAGE}.kubectlVersion=${KUBECTL_VERSION}
ifeq (${STATIC_BUILD}, true)
override LDFLAGS += -extldflags "-static"
@@ -285,7 +282,7 @@ controller:
.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 --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/'
@@ -296,7 +293,7 @@ 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 .
DOCKER_BUILDKIT=1 docker build --platform=linux/amd64 -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
@@ -304,10 +301,10 @@ image: build-ui
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=linux/amd64 -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) .
endif
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) ; fi
@@ -352,7 +349,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 --enable gofmt --fix --verbose --timeout 3000s --max-issues-per-linter 0 --max-same-issues 0
GOGC=$(ARGOCD_LINT_GOGC) GOMAXPROCS=2 golangci-lint run --fix --verbose --timeout 3000s
.PHONY: lint-ui
lint-ui: test-tools-image
@@ -386,9 +383,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 -coverprofile=coverage.out; \
./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 -coverprofile=coverage.out "$(TEST_MODULE)"; \
./hack/test.sh -coverprofile=coverage.out "$(TEST_MODULE)"; \
fi
.PHONY: test-race
@@ -400,9 +397,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 -coverprofile=coverage.out; \
./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 -coverprofile=coverage.out; \
./hack/test.sh -race -coverprofile=coverage.out "$(TEST_MODULE)"; \
fi
# Run the E2E test suite. E2E test servers (see start-e2e target) must be
@@ -416,7 +413,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
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
@@ -459,8 +456,6 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
ARGOCD_IN_CI=$(ARGOCD_IN_CI) \
BIN_MODE=$(ARGOCD_BIN_MODE) \
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}
@@ -557,7 +552,6 @@ install-tools-local: install-test-tools-local install-codegen-tools-local instal
install-test-tools-local:
./hack/install.sh kustomize
./hack/install.sh helm-linux
./hack/install.sh gotestsum
# Installs all tools required for running codegen (Linux packages)
.PHONY: install-codegen-tools-local
@@ -653,4 +647,4 @@ help:
@echo 'codegen:'
@echo ' codegen(-local) -- if using -local, run the following targets first'
@echo ' install-codegen-tools-local -- run this to install the codegen tools'
@echo ' install-go-tools-local -- run this to install go libraries for codegen'
@echo ' install-go-tools-local -- run this to install go libraries for codegen'

1
OWNERS
View File

@@ -30,4 +30,3 @@ 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 "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"
dex: sh -c "ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/v2/cmd gendexcfg -o `pwd`/dist/dex.yaml && 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} 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 "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 "FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-applicationset-controller $COMMAND --loglevel debug --metrics-addr localhost:12345 --probe-addr localhost:12346 --argocd-repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
applicationset-controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_ASK_PASS_SOCK=/tmp/applicationset-ask-pass.sock 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

@@ -1,7 +1,6 @@
**Releases:**
[![Release Version](https://img.shields.io/github/v/release/argoproj/argo-cd?label=argo-cd)](https://github.com/argoproj/argo-cd/releases/latest)
[![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/argo-cd)](https://artifacthub.io/packages/helm/argo/argo-cd)
[![SLSA 3](https://slsa.dev/images/gh-badge-level3.svg)](https://slsa.dev)
**Code:**
[![Integration tests](https://github.com/argoproj/argo-cd/workflows/Integration%20tests/badge.svg?branch=master)](https://github.com/argoproj/argo-cd/actions?query=workflow%3A%22Integration+tests%22)
@@ -56,7 +55,7 @@ Participation in the Argo CD project is governed by the [CNCF Code of Conduct](h
### Blogs and Presentations
1. [Awesome-Argo: A Curated List of Awesome Projects and Resources Related to Argo](https://github.com/terrytangyuan/awesome-argo)
1. [Unveil the Secret Ingredients of Continuous Delivery at Enterprise Scale with Argo CD](https://akuity.io/blog/unveil-the-secret-ingredients-of-continuous-delivery-at-enterprise-scale-with-argocd-kubecon-china-2021/)
1. [Unveil the Secret Ingredients of Continuous Delivery at Enterprise Scale with Argo CD](https://blog.akuity.io/unveil-the-secret-ingredients-of-continuous-delivery-at-enterprise-scale-with-argo-cd-7c5b4057ee49)
1. [GitOps Without Pipelines With ArgoCD Image Updater](https://youtu.be/avPUQin9kzU)
1. [Combining Argo CD (GitOps), Crossplane (Control Plane), And KubeVela (OAM)](https://youtu.be/eEcgn_gU3SM)
1. [How to Apply GitOps to Everything - Combining Argo CD and Crossplane](https://youtu.be/yrj4lmScKHQ)

View File

@@ -50,7 +50,7 @@ of releasing it within a patch branch for the currently supported releases.
## Reporting a Vulnerability
If you find a security related bug in Argo CD, we kindly ask you for responsible
If you find a security related bug in ArgoCD, we kindly ask you for responsible
disclosure and for giving us appropriate time to react, analyze and develop a
fix to mitigate the found security vulnerability.
@@ -63,10 +63,9 @@ We will publish security advisories using the
feature to keep our community well-informed, and will credit you for your
findings (unless you prefer to stay anonymous, of course).
There are two ways to report a vulnerability to the Argo CD team:
Please report vulnerabilities by e-mail to the following address:
* By opening a draft GitHub security advisory: https://github.com/argoproj/argo-cd/security/advisories/new
* By e-mail to the following address: cncf-argo-security@lists.cncf.io
* cncf-argo-security@lists.cncf.io
## Internet Bug Bounty collaboration

View File

@@ -7,7 +7,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [127Labs](https://127labs.com/)
1. [3Rein](https://www.3rein.com/)
1. [4data](https://4data.ch/)
1. [7shifts](https://www.7shifts.com/)
1. [Adevinta](https://www.adevinta.com/)
1. [Adfinis](https://adfinis.com)
@@ -15,7 +14,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Adyen](https://www.adyen.com)
1. [AirQo](https://airqo.net/)
1. [Akuity](https://akuity.io/)
1. [Albert Heijn](https://ah.nl/)
1. [Alibaba Group](https://www.alibabagroup.com/)
1. [Allianz Direct](https://www.allianzdirect.de/)
1. [Amadeus IT Group](https://amadeus.com/)
@@ -25,7 +23,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [AppDirect](https://www.appdirect.com)
1. [Arctiq Inc.](https://www.arctiq.ca)
1. [ARZ Allgemeines Rechenzentrum GmbH](https://www.arz.at/)
1. [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)
@@ -36,37 +33,33 @@ Currently, the following organizations are **officially** using Argo CD:
1. [BigPanda](https://bigpanda.io)
1. [BioBox Analytics](https://biobox.io)
1. [BMW Group](https://www.bmwgroup.com/)
1. [PT Boer Technology (Btech)](https://btech.id/)
1. [Boozt](https://www.booztgroup.com/)
1. [Boticario](https://www.boticario.com.br/)
1. [Bulder Bank](https://bulderbank.no)
1. [Camptocamp](https://camptocamp.com)
1. [Candis](https://www.candis.io)
1. [Capital One](https://www.capitalone.com)
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. [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. [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. [Coralogix](https://coralogix.com/)
1. [Crédit Agricole CIB](https://www.ca-cib.com)
1. [CROZ d.o.o.](https://croz.net/)
1. [Crédit Agricole CIB](https://www.ca-cib.com)
1. [CyberAgent](https://www.cyberagent.co.jp/en/)
1. [Cybozu](https://cybozu-global.com)
1. [D2iQ](https://www.d2iq.com)
1. [DaoCloud](https://daocloud.io/)
1. [Datarisk](https://www.datarisk.io/)
1. [Deloitte](https://www.deloitte.com/)
1. [Deutsche Telekom AG](https://telekom.com)
@@ -74,7 +67,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Devtron Labs](https://github.com/devtron-labs/devtron)
1. [DigitalOcean](https://www.digitalocean.com)
1. [Divistant](https://divistant.com)
1. [Dott](https://ridedott.com)
1. [Doximity](https://www.doximity.com/)
1. [EDF Renewables](https://www.edf-re.com/)
1. [edX](https://edx.org)
@@ -86,11 +78,9 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Energisme](https://energisme.com/)
1. [enigmo](https://enigmo.co.jp/)
1. [Envoy](https://envoy.com/)
1. [Factorial](https://factorialhr.com/)
1. [Farfetch](https://www.farfetch.com)
1. [Faro](https://www.faro.com/)
1. [Fave](https://myfave.com)
1. [Flexport](https://www.flexport.com/)
1. [Flip](https://flip.id)
1. [Fonoa](https://www.fonoa.com/)
1. [freee](https://corp.freee.co.jp/en/company/)
@@ -109,8 +99,6 @@ 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](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)
@@ -122,7 +110,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [hipages](https://hipages.com.au/)
1. [Hiya](https://hiya.com)
1. [Honestbank](https://honestbank.com)
1. [Hostinger](https://www.hostinger.com)
1. [IBM](https://www.ibm.com/)
1. [Ibotta](https://home.ibotta.com)
1. [IITS-Consulting](https://iits-consulting.de)
@@ -132,31 +119,26 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Info Support](https://www.infosupport.com/)
1. [InsideBoard](https://www.insideboard.com)
1. [Intuit](https://www.intuit.com/)
1. [Jellysmack](https://www.jellysmack.com)
1. [Joblift](https://joblift.com/)
1. [JovianX](https://www.jovianx.com/)
1. [Kaltura](https://corp.kaltura.com/)
1. [Kandji](https://www.kandji.io/)
1. [Karrot](https://www.daangn.com/)
1. [KarrotPay](https://www.daangnpay.com/)
1. [Karrot](https://www.daangn.com/)
1. [Kasa](https://kasa.co.kr/)
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. [KPMG](https://kpmg.com/uk)
1. [KubeSphere](https://github.com/kubesphere)
1. [Kurly](https://www.kurly.com/)
1. [Kvist](https://kvistsolutions.com)
1. [LexisNexis](https://www.lexisnexis.com/)
1. [Lian Chu Securities](https://lczq.com)
1. [Liatrio](https://www.liatrio.com)
1. [Lightricks](https://www.lightricks.com/)
1. [LINE](https://linecorp.com/en/)
1. [Loom](https://www.loom.com/)
1. [Lucid Motors](https://www.lucidmotors.com/)
1. [Lytt](https://www.lytt.co/)
1. [Magic Leap](https://www.magicleap.com/)
1. [Majid Al Futtaim](https://www.majidalfuttaim.com/)
@@ -167,13 +149,10 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Max Kelsen](https://www.maxkelsen.com/)
1. [MeDirect](https://medirect.com.mt/)
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. [Metanet](http://www.metanet.co.kr/en/)
1. [MindSpore](https://mindspore.cn)
1. [Mirantis](https://mirantis.com/)
1. [Mission Lane](https://missionlane.com)
1. [mixi Group](https://mixi.co.jp/)
1. [Moengage](https://www.moengage.com/)
1. [Money Forward](https://corp.moneyforward.com/en/)
@@ -189,7 +168,6 @@ 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. [Olfeo](https://www.olfeo.com/)
1. [omegaUp](https://omegaUp.com)
1. [Omni](https://omni.se/)
1. [openEuler](https://openeuler.org)
@@ -202,15 +180,13 @@ Currently, the following organizations are **officially** using Argo CD:
1. [OpsVerse](https://opsverse.io)
1. [Optoro](https://www.optoro.com/)
1. [Orbital Insight](https://orbitalinsight.com/)
1. [Oscar Health Insurance](https://hioscar.com/)
1. [p3r](https://www.p3r.one/)
1. [Packlink](https://www.packlink.com/)
1. [PagerDuty](https://www.pagerduty.com/)
1. [Pandosearch](https://www.pandosearch.com/en/home)
1. [PagerDuty](https://www.pagerduty.com/)
1. [Patreon](https://www.patreon.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. [Pipefy](https://www.pipefy.com/)
@@ -219,13 +195,9 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Polarpoint.io](https://polarpoint.io)
1. [PostFinance](https://github.com/postfinance)
1. [Preferred Networks](https://preferred.jp/en/)
1. [Previder BV](https://previder.nl)
1. [Procore](https://www.procore.com)
1. [Productboard](https://www.productboard.com/)
1. [Prudential](https://prudential.com.sg)
1. [PT Boer Technology (Btech)](https://btech.id/)
1. [PUBG](https://www.pubg.com)
1. [Puzzle ITC](https://www.puzzle.ch/)
1. [Qonto](https://qonto.com)
1. [QuintoAndar](https://quintoandar.com.br)
1. [Quipper](https://www.quipper.com/)
@@ -245,9 +217,6 @@ Currently, the following organizations are **officially** using Argo CD:
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. [SI Analytics](https://si-analytics.ai)
1. [Skit](https://skit.ai/)
1. [Skyscanner](https://www.skyscanner.net/)
@@ -262,7 +231,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. [StreamNative](https://streamnative.io)
1. [Stuart](https://stuart.com/)
1. [Sumo Logic](https://sumologic.com/)
1. [Sutpc](http://www.sutpc.com/)
@@ -276,7 +244,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Techcombank](https://www.techcombank.com.vn/trang-chu)
1. [Technacy](https://www.technacy.it/)
1. [Tesla](https://tesla.com/)
1. [The Scale Factory](https://www.scalefactory.com/)
1. [ThousandEyes](https://www.thousandeyes.com/)
1. [Ticketmaster](https://ticketmaster.com)
1. [Tiger Analytics](https://www.tigeranalytics.com/)
@@ -285,21 +252,16 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Trendyol](https://www.trendyol.com/)
1. [tru.ID](https://tru.id)
1. [Trusting Social](https://trustingsocial.com/)
1. [Twilio Segment](https://segment.com/)
1. [Twilio SendGrid](https://sendgrid.com)
1. [tZERO](https://www.tzero.com/)
1. [U.S. Veterans Affairs Department](https://www.va.gov/)
1. [UBIO](https://ub.io/)
1. [UFirstGroup](https://www.ufirstgroup.com/en/)
1. [ungleich.ch](https://ungleich.ch/)
1. [Unifonic Inc](https://www.unifonic.com/)
1. [Universidad Mesoamericana](https://www.umes.edu.gt/)
1. [Upsider Inc.](https://up-sider.com/lp/)
1. [Urbantz](https://urbantz.com/)
1. [Vectra](https://www.vectra.ai)
1. [Veepee](https://www.veepee.com)
1. [Viaduct](https://www.viaduct.ai/)
1. [VietMoney](https://vietmoney.vn/)
1. [Vinted](https://vinted.com/)
1. [Virtuo](https://www.govirtuo.com/)
1. [VISITS Technologies](https://visits.world/en)
@@ -320,6 +282,5 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Yieldlab](https://www.yieldlab.de/)
1. [Youverify](https://youverify.co/)
1. [Yubo](https://www.yubo.live/)
1. [ZDF](https://www.zdf.de/)
1. [Zimpler](https://www.zimpler.com/)
1. [ZOZO](https://corp.zozo.com/)

View File

@@ -1 +1 @@
2.9.0
2.7.14

View File

@@ -28,13 +28,10 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
k8scache "k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/handler"
@@ -45,7 +42,6 @@ import (
"github.com/argoproj/argo-cd/v2/applicationset/utils"
"github.com/argoproj/argo-cd/v2/common"
"github.com/argoproj/argo-cd/v2/util/db"
"github.com/argoproj/argo-cd/v2/util/glob"
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
@@ -72,22 +68,16 @@ var (
// ApplicationSetReconciler reconciles a ApplicationSet object
type ApplicationSetReconciler struct {
client.Client
Scheme *runtime.Scheme
Recorder record.EventRecorder
Generators map[string]generators.Generator
ArgoDB db.ArgoDB
ArgoAppClientset appclientset.Interface
KubeClientset kubernetes.Interface
Policy argov1alpha1.ApplicationsSyncPolicy
EnablePolicyOverride bool
Scheme *runtime.Scheme
Recorder record.EventRecorder
Generators map[string]generators.Generator
ArgoDB db.ArgoDB
ArgoAppClientset appclientset.Interface
KubeClientset kubernetes.Interface
utils.Policy
utils.Renderer
ArgoCDNamespace string
ApplicationSetNamespaces []string
EnableProgressiveSyncs bool
SCMRootCAPath string
GlobalPreservedAnnotations []string
GlobalPreservedLabels []string
Cache cache.Cache
EnableProgressiveSyncs bool
}
// +kubebuilder:rbac:groups=argoproj.io,resources=applicationsets,verbs=get;list;watch;create;update;patch;delete
@@ -114,7 +104,7 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
// 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, err := r.generateApplications(logCtx, applicationSetInfo)
desiredApplications, applicationSetReason, err := r.generateApplications(applicationSetInfo)
if err != nil {
_ = r.setApplicationSetStatusCondition(ctx,
&applicationSetInfo,
@@ -130,7 +120,7 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
parametersGenerated = true
validateErrors, err := r.validateGeneratedApplications(ctx, desiredApplications, applicationSetInfo)
validateErrors, err := r.validateGeneratedApplications(ctx, desiredApplications, applicationSetInfo, req.Namespace)
if err != nil {
// While some generators may return an error that requires user intervention,
// other generators reference external resources that may change to cause
@@ -161,9 +151,9 @@ 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)
}
@@ -178,7 +168,7 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
appMap[app.Name] = app
}
appSyncMap, err = r.performProgressiveSyncs(ctx, logCtx, applicationSetInfo, applications, 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)
}
@@ -216,7 +206,7 @@ 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,
@@ -233,8 +223,8 @@ 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)
if r.Policy.Update() {
err = r.createOrUpdateInCluster(ctx, applicationSetInfo, validApps)
if err != nil {
_ = r.setApplicationSetStatusCondition(ctx,
&applicationSetInfo,
@@ -248,7 +238,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,
@@ -263,8 +253,8 @@ 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)
if r.Policy.Delete() {
err = r.deleteInCluster(ctx, applicationSetInfo, desiredApplications)
if err != nil {
_ = r.setApplicationSetStatusCondition(ctx,
&applicationSetInfo,
@@ -427,7 +417,7 @@ func (r *ApplicationSetReconciler) setApplicationSetStatusCondition(ctx context.
// validateGeneratedApplications uses the Argo CD validation functions to verify the correctness of the
// generated applications.
func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Context, desiredApplications []argov1alpha1.Application, applicationSetInfo argov1alpha1.ApplicationSet) (map[int]error, error) {
func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Context, desiredApplications []argov1alpha1.Application, applicationSetInfo argov1alpha1.ApplicationSet, namespace string) (map[int]error, error) {
errorsByIndex := map[int]error{}
namesSet := map[string]bool{}
for i, app := range desiredApplications {
@@ -438,7 +428,8 @@ func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Con
errorsByIndex[i] = fmt.Errorf("ApplicationSet %s contains applications with duplicate name: %s", applicationSetInfo.Name, app.Name)
continue
}
_, err := r.ArgoAppClientset.ArgoprojV1alpha1().AppProjects(r.ArgoCDNamespace).Get(ctx, app.Spec.GetProject(), metav1.GetOptions{})
proj, err := r.ArgoAppClientset.ArgoprojV1alpha1().AppProjects(namespace).Get(ctx, app.Spec.GetProject(), metav1.GetOptions{})
if err != nil {
if apierr.IsNotFound(err) {
errorsByIndex[i] = fmt.Errorf("application references project %s which does not exist", app.Spec.Project)
@@ -447,11 +438,20 @@ func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Con
return nil, err
}
if err := utils.ValidateDestination(ctx, &app.Spec.Destination, r.KubeClientset, r.ArgoCDNamespace); err != nil {
if err := utils.ValidateDestination(ctx, &app.Spec.Destination, r.KubeClientset, namespace); err != nil {
errorsByIndex[i] = fmt.Errorf("application destination spec is invalid: %s", err.Error())
continue
}
conditions, err := argoutil.ValidatePermissions(ctx, &app.Spec, proj, r.ArgoDB)
if err != nil {
return nil, err
}
if len(conditions) > 0 {
errorsByIndex[i] = fmt.Errorf("application spec is invalid: %s", argoutil.FormatAppConditions(conditions))
continue
}
}
return errorsByIndex, nil
@@ -489,7 +489,7 @@ func getTempApplication(applicationSetTemplate argov1alpha1.ApplicationSetTempla
return &tmplApplication
}
func (r *ApplicationSetReconciler) generateApplications(logCtx *log.Entry, applicationSetInfo argov1alpha1.ApplicationSet) ([]argov1alpha1.Application, argov1alpha1.ApplicationSetReasonType, error) {
func (r *ApplicationSetReconciler) generateApplications(applicationSetInfo argov1alpha1.ApplicationSet) ([]argov1alpha1.Application, argov1alpha1.ApplicationSetReasonType, error) {
var res []argov1alpha1.Application
var firstError error
@@ -498,7 +498,7 @@ func (r *ApplicationSetReconciler) generateApplications(logCtx *log.Entry, appli
for _, requestedGenerator := range applicationSetInfo.Spec.Generators {
t, err := generators.Transform(requestedGenerator, r.Generators, applicationSetInfo.Spec.Template, &applicationSetInfo, map[string]interface{}{})
if err != nil {
logCtx.WithError(err).WithField("generator", requestedGenerator).
log.WithError(err).WithField("generator", requestedGenerator).
Error("error generating application from params")
if firstError == nil {
firstError = err
@@ -511,9 +511,9 @@ func (r *ApplicationSetReconciler) generateApplications(logCtx *log.Entry, appli
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)
app, err := r.Renderer.RenderTemplateParams(tmplApplication, applicationSetInfo.Spec.SyncPolicy, p, applicationSetInfo.Spec.GoTemplate)
if err != nil {
logCtx.WithError(err).WithField("params", a.Params).WithField("generator", requestedGenerator).
log.WithError(err).WithField("params", a.Params).WithField("generator", requestedGenerator).
Error("error generating application from params")
if firstError == nil {
@@ -526,49 +526,37 @@ func (r *ApplicationSetReconciler) generateApplications(logCtx *log.Entry, appli
}
}
logCtx.WithField("generator", requestedGenerator).Infof("generated %d applications", len(res))
logCtx.WithField("generator", requestedGenerator).Debugf("apps from generator: %+v", res)
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 {
return glob.MatchStringInList(namespaces, e.Object.GetNamespace(), false)
},
}
}
func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager, enableProgressiveSyncs bool) error {
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
}
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 {
// ...and if so, return it
return []string{owner.Name}
}); err != nil {
return fmt.Errorf("error setting up with manager: %w", err)
}
ownsHandler := getOwnsHandlerPredicates(enableProgressiveSyncs)
return ctrl.NewControllerManagedBy(mgr).WithOptions(controller.Options{
MaxConcurrentReconciles: maxConcurrentReconciliations,
}).For(&argov1alpha1.ApplicationSet{}).
return ctrl.NewControllerManagedBy(mgr).
For(&argov1alpha1.ApplicationSet{}).
Owns(&argov1alpha1.Application{}, builder.WithPredicates(ownsHandler)).
WithEventFilter(ignoreNotAllowedNamespaces(r.ApplicationSetNamespaces)).
Watches(
&source.Kind{Type: &corev1.Secret{}},
&clusterSecretEventHandler{
@@ -579,37 +567,18 @@ func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager, enableProg
Complete(r)
}
func (r *ApplicationSetReconciler) updateCache(ctx context.Context, obj client.Object, logger *log.Entry) {
informer, err := r.Cache.GetInformer(ctx, obj)
if err != nil {
logger.Errorf("failed to get informer: %v", err)
return
}
// The controller runtime abstract away informers creation
// so unfortunately could not find any other way to access informer store.
k8sInformer, ok := informer.(k8scache.SharedInformer)
if !ok {
logger.Error("informer is not a kubernetes informer")
return
}
if err := k8sInformer.GetStore().Update(obj); err != nil {
logger.Errorf("failed to update cache: %v", err)
return
}
}
// createOrUpdateInCluster will create / update application resources in the cluster.
// - 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 {
generatedApp.Namespace = applicationSet.Namespace
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)
@@ -625,7 +594,7 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
},
}
action, err := utils.CreateOrUpdate(ctx, appLog, r.Client, applicationSet.Spec.IgnoreApplicationDifferences, found, func() error {
action, err := utils.CreateOrUpdate(ctx, r.Client, found, func() error {
// Copy only the Application/ObjectMeta fields that are significant, from the generatedApp
found.Spec = generatedApp.Spec
@@ -635,21 +604,9 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
}
preservedAnnotations := make([]string, 0)
preservedLabels := make([]string, 0)
if applicationSet.Spec.PreservedFields != nil {
preservedAnnotations = append(preservedAnnotations, applicationSet.Spec.PreservedFields.Annotations...)
preservedLabels = append(preservedLabels, applicationSet.Spec.PreservedFields.Labels...)
}
if len(r.GlobalPreservedAnnotations) > 0 {
preservedAnnotations = append(preservedAnnotations, r.GlobalPreservedAnnotations...)
}
if len(r.GlobalPreservedLabels) > 0 {
preservedLabels = append(preservedLabels, r.GlobalPreservedLabels...)
}
// Preserve specially treated argo cd annotations:
// * https://github.com/argoproj/applicationset/issues/180
// * https://github.com/argoproj/argo-cd/issues/10500
@@ -663,21 +620,10 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
generatedApp.Annotations[key] = state
}
}
for _, key := range preservedLabels {
if state, exists := found.ObjectMeta.Labels[key]; exists {
if generatedApp.Labels == nil {
generatedApp.Labels = map[string]string{}
}
generatedApp.Labels[key] = state
}
}
found.ObjectMeta.Annotations = generatedApp.Annotations
found.ObjectMeta.Finalizers = generatedApp.Finalizers
found.ObjectMeta.Labels = generatedApp.Labels
return controllerutil.SetControllerReference(&applicationSet, found, r.Scheme)
})
@@ -688,24 +634,16 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
}
continue
}
r.updateCache(ctx, found, appLog)
if action != controllerutil.OperationResultNone {
// Don't pollute etcd with "unchanged Application" events
r.Recorder.Eventf(&applicationSet, corev1.EventTypeNormal, fmt.Sprint(action), "%s Application %q", action, generatedApp.Name)
appLog.Logf(log.InfoLevel, "%s Application", action)
} else {
// "unchanged Application" can be inferred by Reconcile Complete with no action being listed
// Or enable debug logging
appLog.Logf(log.DebugLevel, "%s Application", action)
}
r.Recorder.Eventf(&applicationSet, corev1.EventTypeNormal, fmt.Sprint(action), "%s Application %q", action, generatedApp.Name)
appLog.Logf(log.InfoLevel, "%s Application", action)
}
return firstError
}
// 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)
@@ -728,7 +666,7 @@ 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(_ context.Context, applicationSet argov1alpha1.ApplicationSet) ([]argov1alpha1.Application, error) {
@@ -737,7 +675,7 @@ func (r *ApplicationSetReconciler) getCurrentApplications(_ context.Context, app
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)
return nil, err
}
return current.Items, nil
@@ -745,11 +683,11 @@ func (r *ApplicationSetReconciler) getCurrentApplications(_ context.Context, app
// 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)
clusterList, err := utils.ListClusters(ctx, r.KubeClientset, r.ArgoCDNamespace)
clusterList, err := utils.ListClusters(ctx, r.KubeClientset, applicationSet.Namespace)
if err != nil {
return fmt.Errorf("error listing clusters: %w", err)
}
@@ -769,15 +707,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
}
@@ -786,14 +724,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
@@ -810,7 +748,7 @@ func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx conte
var validDestination bool
// Detect if the destination is invalid (name doesn't correspond to a matching cluster)
if err := utils.ValidateDestination(ctx, &app.Spec.Destination, r.KubeClientset, r.ArgoCDNamespace); err != nil {
if err := utils.ValidateDestination(ctx, &app.Spec.Destination, r.KubeClientset, applicationSet.Namespace); err != nil {
appLog.Warnf("The destination cluster for %s couldn't be found: %v", app.Name, err)
validDestination = false
} else {
@@ -854,42 +792,36 @@ func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx conte
// If the finalizer length changed (due to filtering out an Argo finalizer), update the finalizer list on the app
if len(newFinalizers) != len(app.Finalizers) {
updated := app.DeepCopy()
updated.Finalizers = newFinalizers
patch := client.MergeFrom(app)
if log.IsLevelEnabled(log.DebugLevel) {
utils.LogPatch(appLog, patch, updated)
}
if err := r.Client.Patch(ctx, updated, patch); err != nil {
return fmt.Errorf("error updating finalizers: %w", err)
}
r.updateCache(ctx, updated, appLog)
// Application must have updated list of finalizers
updated.DeepCopyInto(app)
app.Finalizers = newFinalizers
r.Recorder.Eventf(&applicationSet, corev1.EventTypeNormal, "Updated", "Updated Application %q finalizer before deletion, because application has an invalid destination", app.Name)
appLog.Log(log.InfoLevel, "Updating application finalizer before deletion, because application has an invalid destination")
err := r.Client.Update(ctx, app, &client.UpdateOptions{})
if err != nil {
return fmt.Errorf("error updating finalizers: %w", 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) {
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) {
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)
@@ -897,9 +829,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)
}
@@ -913,7 +845,7 @@ 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
@@ -940,9 +872,9 @@ func (r *ApplicationSetReconciler) buildAppDependencyList(logCtx *log.Entry, app
for _, matchExpression := range step.MatchExpressions {
if val, ok := app.Labels[matchExpression.Key]; ok {
valueMatched := labelMatchedExpression(logCtx, val, matchExpression)
valueMatched := labelMatchedExpression(val, matchExpression)
if !valueMatched { // none of the matchExpression values was a match with the Application's labels
if !valueMatched { // none of the matchExpression values was a match with the Application'ss labels
selected = false
break
}
@@ -955,7 +887,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
}
@@ -966,9 +898,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
}
@@ -1072,7 +1004,7 @@ 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))
@@ -1105,7 +1037,7 @@ 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."
@@ -1117,15 +1049,15 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
// 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) {
logCtx.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.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)
}
logCtx.Infof("Application %v has completed a sync successfully, updating its ApplicationSet 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."
@@ -1134,7 +1066,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."
@@ -1142,7 +1074,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."
@@ -1152,7 +1084,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)
}
@@ -1161,7 +1093,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))
@@ -1203,7 +1135,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
@@ -1213,13 +1145,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."
@@ -1232,7 +1164,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)
}
@@ -1294,7 +1226,7 @@ func findApplicationStatusIndex(appStatuses []argov1alpha1.ApplicationSetApplica
// 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,7 +1260,7 @@ func (r *ApplicationSetReconciler) setAppSetApplicationStatus(ctx context.Contex
err := r.Client.Status().Update(ctx, applicationSet)
if err != nil {
logCtx.Errorf("unable to set application set status: %v", err)
log.Errorf("unable to set application set status: %v", err)
return fmt.Errorf("unable to set application set status: %v", err)
}
@@ -1343,7 +1275,7 @@ func (r *ApplicationSetReconciler) setAppSetApplicationStatus(ctx context.Contex
return nil
}
func (r *ApplicationSetReconciler) syncValidApplications(logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appMap map[string]argov1alpha1.Application, validApps []argov1alpha1.Application) ([]argov1alpha1.Application, 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 +1295,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])
@@ -1407,51 +1339,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
},
}

File diff suppressed because it is too large Load Diff

View File

@@ -139,11 +139,7 @@ func nestedGeneratorHasClusterGenerator(nested argoprojiov1alpha1.ApplicationSet
return false, fmt.Errorf("unable to get nested matrix generator: %w", err)
}
if nestedMatrix != nil {
hasClusterGenerator, err := nestedGeneratorsHaveClusterGenerator(nestedMatrix.ToMatrixGenerator().Generators)
if err != nil {
return false, fmt.Errorf("error evaluating nested matrix generator: %w", err)
}
return hasClusterGenerator, nil
return nestedGeneratorsHaveClusterGenerator(nestedMatrix.ToMatrixGenerator().Generators)
}
}
@@ -153,11 +149,7 @@ func nestedGeneratorHasClusterGenerator(nested argoprojiov1alpha1.ApplicationSet
return false, fmt.Errorf("unable to get nested merge generator: %w", err)
}
if nestedMerge != nil {
hasClusterGenerator, err := nestedGeneratorsHaveClusterGenerator(nestedMerge.ToMergeGenerator().Generators)
if err != nil {
return false, fmt.Errorf("error evaluating nested merge generator: %w", err)
}
return hasClusterGenerator, nil
return nestedGeneratorsHaveClusterGenerator(nestedMerge.ToMergeGenerator().Generators)
}
}

View File

@@ -573,68 +573,3 @@ type mockAddRateLimitingInterface struct {
errorOccurred bool
addedItems []ctrl.Request
}
func TestNestedGeneratorHasClusterGenerator_NestedClusterGenerator(t *testing.T) {
nested := argov1alpha1.ApplicationSetNestedGenerator{
Clusters: &argov1alpha1.ClusterGenerator{},
}
hasClusterGenerator, err := nestedGeneratorHasClusterGenerator(nested)
assert.Nil(t, err)
assert.True(t, hasClusterGenerator)
}
func TestNestedGeneratorHasClusterGenerator_NestedMergeGenerator(t *testing.T) {
nested := argov1alpha1.ApplicationSetNestedGenerator{
Merge: &apiextensionsv1.JSON{
Raw: []byte(
`{
"generators": [
{
"clusters": {
"selector": {
"matchLabels": {
"argocd.argoproj.io/secret-type": "cluster"
}
}
}
}
]
}`,
),
},
}
hasClusterGenerator, err := nestedGeneratorHasClusterGenerator(nested)
assert.Nil(t, err)
assert.True(t, hasClusterGenerator)
}
func TestNestedGeneratorHasClusterGenerator_NestedMergeGeneratorWithInvalidJSON(t *testing.T) {
nested := argov1alpha1.ApplicationSetNestedGenerator{
Merge: &apiextensionsv1.JSON{
Raw: []byte(
`{
"generators": [
{
"clusters": {
"selector": {
"matchLabels": {
"argocd.argoproj.io/secret-type": "cluster"
}
}
}
}
]
`,
),
},
}
hasClusterGenerator, err := nestedGeneratorHasClusterGenerator(nested)
assert.NotNil(t, err)
assert.False(t, hasClusterGenerator)
}

View File

@@ -5,7 +5,10 @@ import (
"testing"
"time"
"github.com/argoproj/argo-cd/v2/applicationset/generators"
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
@@ -14,14 +17,10 @@ import (
kubefake "k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/tools/record"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/argoproj/argo-cd/v2/applicationset/generators"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func TestRequeueAfter(t *testing.T) {
mockServer := &mocks.Repos{}
mockServer := argoCDServiceMock{}
ctx := context.Background()
scheme := runtime.NewScheme()
err := argov1alpha1.AddToScheme(scheme)
@@ -60,9 +59,9 @@ func TestRequeueAfter(t *testing.T) {
"List": generators.NewListGenerator(),
"Clusters": generators.NewClusterGenerator(k8sClient, ctx, appClientset, "argocd"),
"Git": generators.NewGitGenerator(mockServer),
"SCMProvider": generators.NewSCMProviderGenerator(fake.NewClientBuilder().WithObjects(&corev1.Secret{}).Build(), generators.SCMAuthProviders{}, "", []string{""}, true),
"SCMProvider": generators.NewSCMProviderGenerator(fake.NewClientBuilder().WithObjects(&corev1.Secret{}).Build(), generators.SCMAuthProviders{}),
"ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, fakeDynClient, appClientset, "argocd"),
"PullRequest": generators.NewPullRequestGenerator(k8sClient, generators.SCMAuthProviders{}, "", []string{""}, true),
"PullRequest": generators.NewPullRequestGenerator(k8sClient, generators.SCMAuthProviders{}),
}
nestedGenerators := map[string]generators.Generator{
@@ -151,3 +150,30 @@ func TestRequeueAfter(t *testing.T) {
})
}
}
type argoCDServiceMock struct {
mock *mock.Mock
}
func (a argoCDServiceMock) GetApps(ctx context.Context, repoURL string, revision string) ([]string, error) {
args := a.mock.Called(ctx, repoURL, revision)
return args.Get(0).([]string), args.Error(1)
}
func (a argoCDServiceMock) GetFiles(ctx context.Context, repoURL string, revision string, pattern string) (map[string][]byte, error) {
args := a.mock.Called(ctx, repoURL, revision, pattern)
return args.Get(0).(map[string][]byte), args.Error(1)
}
func (a argoCDServiceMock) GetFileContent(ctx context.Context, repoURL string, revision string, path string) ([]byte, error) {
args := a.mock.Called(ctx, repoURL, revision, path)
return args.Get(0).([]byte), args.Error(1)
}
func (a argoCDServiceMock) GetDirectories(ctx context.Context, repoURL string, revision string) ([]string, error) {
args := a.mock.Called(ctx, repoURL, revision)
return args.Get(0).([]string), args.Error(1)
}

View File

@@ -1,35 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: guestbook
spec:
goTemplate: true
generators:
- list:
elements:
- cluster: engineering-dev
url: https://kubernetes.default.svc
foo: bar
# Update foo value with foo: bar
# Application engineering-prod-guestbook labels will still be baz
# Delete this element
# Application engineering-prod-guestbook will be kept
- cluster: engineering-prod
url: https://kubernetes.default.svc
foo: baz
template:
metadata:
name: '{{.cluster}}-guestbook'
labels:
foo: '{{.foo}}'
spec:
project: default
source:
repoURL: https://github.com/argoproj/argo-cd.git
targetRevision: HEAD
path: applicationset/examples/list-generator/guestbook/{{.cluster}}
destination:
server: '{{.url}}'
namespace: guestbook
syncPolicy:
applicationsSync: create-only

View File

@@ -1,35 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: guestbook
spec:
goTemplate: true
generators:
- list:
elements:
- cluster: engineering-dev
url: https://kubernetes.default.svc
foo: bar
# Update foo value with foo: bar
# Application engineering-prod-guestbook labels will change to foo: bar
# Delete this element
# Application engineering-prod-guestbook will be kept
- cluster: engineering-prod
url: https://kubernetes.default.svc
foo: baz
template:
metadata:
name: '{{.cluster}}-guestbook'
labels:
foo: '{{.foo}}'
spec:
project: default
source:
repoURL: https://github.com/argoproj/argo-cd.git
targetRevision: HEAD
path: applicationset/examples/list-generator/guestbook/{{.cluster}}
destination:
server: '{{.url}}'
namespace: guestbook
syncPolicy:
applicationsSync: create-update

View File

@@ -1,20 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: guestbook-ui
spec:
replicas: 1
revisionHistoryLimit: 3
selector:
matchLabels:
app: guestbook-ui
template:
metadata:
labels:
app: guestbook-ui
spec:
containers:
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
name: guestbook-ui
ports:
- containerPort: 80

View File

@@ -1,10 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: guestbook-ui
spec:
ports:
- port: 80
targetPort: 80
selector:
app: guestbook-ui

View File

@@ -1,20 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: guestbook-ui
spec:
replicas: 1
revisionHistoryLimit: 3
selector:
matchLabels:
app: guestbook-ui
template:
metadata:
labels:
app: guestbook-ui
spec:
containers:
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
name: guestbook-ui
ports:
- containerPort: 80

View File

@@ -1,10 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: guestbook-ui
spec:
ports:
- port: 80
targetPort: 80
selector:
app: guestbook-ui

View File

@@ -4,7 +4,6 @@ metadata:
name: guestbook
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- clusters: {}
template:

View File

@@ -4,7 +4,6 @@ metadata:
name: book-import
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- clusterDecisionResource:
configMapRef: ocm-placement

View File

@@ -8,7 +8,6 @@ metadata:
name: guestbook
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- clusters: {}
template:

View File

@@ -27,7 +27,6 @@ metadata:
name: cluster-addons
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- git:
repoURL: https://github.com/infra-team/cluster-deployments.git

View File

@@ -38,7 +38,6 @@ metadata:
name: guestbook
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- git:
repoURL: https://github.com/infra-team/cluster-deployments.git

View File

@@ -51,7 +51,6 @@ metadata:
name: guestbook
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- git:
repoURL: https://github.com/infra-team/cluster-deployments.git

View File

@@ -5,7 +5,6 @@ metadata:
name: guestbook
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- list:
elements:

View File

@@ -8,7 +8,6 @@ metadata:
name: guestbook
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- list:
elements:

View File

@@ -5,7 +5,6 @@ metadata:
namespace: argocd
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- git:
repoURL: https://github.com/argoproj/argo-cd.git

View File

@@ -5,7 +5,6 @@ metadata:
namespace: argocd
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- git:
repoURL: https://github.com/argoproj/argo-cd.git

View File

@@ -4,7 +4,6 @@ metadata:
name: guestbook
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- git:
repoURL: https://github.com/argoproj/argo-cd.git

View File

@@ -4,7 +4,6 @@ metadata:
name: guestbook
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- list:
elements:

View File

@@ -8,7 +8,6 @@ metadata:
name: cluster-git
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- matrix:
generators:

View File

@@ -8,7 +8,6 @@ metadata:
name: list-git
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- matrix:
generators:

View File

@@ -5,7 +5,6 @@ metadata:
namespace: argocd
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- matrix:
generators:

View File

@@ -13,7 +13,6 @@ metadata:
name: matrix-and-union-in-matrix
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- matrix:
generators:

View File

@@ -4,7 +4,6 @@ metadata:
name: merge-clusters-and-list
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- merge:
mergeKeys:

View File

@@ -4,7 +4,6 @@ metadata:
name: merge-two-matrixes
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- merge:
mergeKeys:

View File

@@ -4,7 +4,6 @@ metadata:
name: myapp
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- pullRequest:
github:

View File

@@ -1,26 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: guestbook
spec:
generators:
- scmProvider:
gitlab:
api: https://gitlab.com
group: test-argocd-proton
includeSubgroups: true
cloneProtocol: https
filters:
- repositoryMatch: test-app
template:
metadata:
name: '{{ repository }}-guestbook'
spec:
project: "default"
source:
repoURL: '{{ url }}'
targetRevision: '{{ branch }}'
path: guestbook
destination:
server: https://kubernetes.default.svc
namespace: guestbook

View File

@@ -4,7 +4,6 @@ metadata:
name: guestbook
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- scmProvider:
github:

View File

@@ -8,7 +8,6 @@ metadata:
name: guestbook
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- list:
elements:

View File

@@ -61,7 +61,8 @@ func (g *ClusterGenerator) GetTemplate(appSetGenerator *argoappsetv1alpha1.Appli
return &appSetGenerator.Clusters.Template
}
func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator, appSet *argoappsetv1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
func (g *ClusterGenerator) GenerateParams(
appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator, appSet *argoappsetv1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil {
return nil, EmptyAppSetGeneratorError
@@ -78,7 +79,7 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
// ListCluster from Argo CD's util/db package will include the local cluster in the list of clusters
clustersFromArgoCD, err := utils.ListClusters(g.ctx, g.clientset, g.namespace)
if err != nil {
return nil, fmt.Errorf("error listing clusters: %w", err)
return nil, err
}
if clustersFromArgoCD == nil {
@@ -108,7 +109,7 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
params["nameNormalized"] = cluster.Name
params["server"] = cluster.Server
err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet)
if err != nil {
return nil, err
}
@@ -148,7 +149,7 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
}
}
err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet)
if err != nil {
return nil, err
}
@@ -161,6 +162,44 @@ func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.Ap
return res, nil
}
func appendTemplatedValues(clusterValues map[string]string, params map[string]interface{}, appSet *argoappsetv1alpha1.ApplicationSet) error {
// We create a local map to ensure that we do not fall victim to a billion-laughs attack. We iterate through the
// cluster values map and only replace values in said map if it has already been whitelisted in the params map.
// Once we iterate through all the cluster values we can then safely merge the `tmp` map into the main params map.
tmp := map[string]interface{}{}
for key, value := range clusterValues {
result, err := replaceTemplatedString(value, params, appSet)
if err != nil {
return fmt.Errorf("error replacing templated String: %w", err)
}
if appSet.Spec.GoTemplate {
if tmp["values"] == nil {
tmp["values"] = map[string]string{}
}
tmp["values"].(map[string]string)[key] = result
} else {
tmp[fmt.Sprintf("values.%s", key)] = result
}
}
for key, value := range tmp {
params[key] = value
}
return nil
}
func replaceTemplatedString(value string, params map[string]interface{}, appSet *argoappsetv1alpha1.ApplicationSet) (string, error) {
replacedTmplStr, err := render.Replace(value, params, appSet.Spec.GoTemplate)
if err != nil {
return "", err
}
return replacedTmplStr, nil
}
func (g *ClusterGenerator) getSecretsByClusterName(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator) (map[string]corev1.Secret, error) {
// List all Clusters:
clusterSecretList := &corev1.SecretList{}

View File

@@ -74,7 +74,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
// ListCluster from Argo CD's util/db package will include the local cluster in the list of clusters
clustersFromArgoCD, err := utils.ListClusters(g.ctx, g.clientset, g.namespace)
if err != nil {
return nil, fmt.Errorf("error listing clusters: %w", err)
return nil, err
}
if clustersFromArgoCD == nil {
@@ -85,7 +85,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
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)
return nil, err
}
// Extract GVK data for the dynamic client to use

View File

@@ -3,7 +3,6 @@ package generators
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
@@ -16,6 +15,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"testing"
)
const resourceApiVersion = "mallard.io/v1"

View File

@@ -4,9 +4,8 @@ import (
"fmt"
"reflect"
"github.com/jeremywohl/flatten"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
"github.com/jeremywohl/flatten"
"k8s.io/apimachinery/pkg/labels"
@@ -53,7 +52,7 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
}
var params []map[string]interface{}
if len(genParams) != 0 {
tempInterpolatedGenerator, err := InterpolateGenerator(&requestedGenerator, genParams, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
tempInterpolatedGenerator, err := InterpolateGenerator(&requestedGenerator, genParams, appSet.Spec.GoTemplate)
interpolatedGenerator = &tempInterpolatedGenerator
if err != nil {
log.WithError(err).WithField("genParams", genParams).
@@ -125,7 +124,7 @@ func GetRelevantGenerators(requestedGenerator *argoprojiov1alpha1.ApplicationSet
func flattenParameters(in map[string]interface{}) (map[string]string, error) {
flat, err := flatten.Flatten(in, "", flatten.DotStyle)
if err != nil {
return nil, fmt.Errorf("error flatenning parameters: %w", err)
return nil, err
}
out := make(map[string]string, len(flat))
@@ -148,26 +147,13 @@ func mergeGeneratorTemplate(g Generator, requestedGenerator *argoprojiov1alpha1.
// InterpolateGenerator allows interpolating the matrix's 2nd child generator with values from the 1st child generator
// "params" parameter is an array, where each index corresponds to a generator. Each index contains a map w/ that generator's parameters.
func InterpolateGenerator(requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, params map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (argoprojiov1alpha1.ApplicationSetGenerator, error) {
func InterpolateGenerator(requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, params map[string]interface{}, useGoTemplate bool) (argoprojiov1alpha1.ApplicationSetGenerator, error) {
render := utils.Render{}
interpolatedGenerator, err := render.RenderGeneratorParams(requestedGenerator, params, useGoTemplate, goTemplateOptions)
interpolatedGenerator, err := render.RenderGeneratorParams(requestedGenerator, params, useGoTemplate)
if err != nil {
log.WithError(err).WithField("interpolatedGenerator", interpolatedGenerator).Error("error interpolating generator with other generator's parameter")
return argoprojiov1alpha1.ApplicationSetGenerator{}, err
return *interpolatedGenerator, err
}
return *interpolatedGenerator, nil
}
// Fixes https://github.com/argoproj/argo-cd/issues/11982 while ensuring backwards compatibility.
// This is only a short-term solution and should be removed in a future major version.
func dropDisabledNestedSelectors(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator) bool {
var foundSelector bool
for i := range generators {
if generators[i].Selector != nil {
foundSelector = true
generators[i].Selector = nil
}
}
return foundSelector
}

View File

@@ -10,8 +10,7 @@ import (
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
testutils "github.com/argoproj/argo-cd/v2/applicationset/utils/test"
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/stretchr/testify/mock"
@@ -20,6 +19,8 @@ import (
kubefake "k8s.io/client-go/kubernetes/fake"
crtclient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func TestMatchValues(t *testing.T) {
@@ -70,18 +71,18 @@ func TestMatchValues(t *testing.T) {
"List": listGenerator,
}
applicationSetInfo := argov1alpha1.ApplicationSet{
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argov1alpha1.ApplicationSetSpec{
Spec: argoprojiov1alpha1.ApplicationSetSpec{
GoTemplate: false,
},
}
results, err := Transform(argov1alpha1.ApplicationSetGenerator{
results, err := Transform(argoprojiov1alpha1.ApplicationSetGenerator{
Selector: testCase.selector,
List: &argov1alpha1.ListGenerator{
List: &argoprojiov1alpha1.ListGenerator{
Elements: testCase.elements,
Template: emptyTemplate(),
}},
@@ -171,7 +172,6 @@ func TestMatchValuesGoTemplate(t *testing.T) {
data,
emptyTemplate(),
&applicationSetInfo, nil)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, results[0].Params)
})
@@ -341,9 +341,9 @@ 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)
var gitGenerator = NewGitGenerator(&argoCDServiceMock)
argoCDServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
argoCDServiceMock.Mock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return([]string{"app1", "app2", "app_3", "p1/app4"}, nil)
var gitGenerator = NewGitGenerator(argoCDServiceMock)
return gitGenerator
}
@@ -358,8 +358,8 @@ func TestGetRelevantGenerators(t *testing.T) {
testGenerators["Merge"] = NewMergeGenerator(testGenerators)
testGenerators["List"] = NewListGenerator()
requestedGenerator := &argov1alpha1.ApplicationSetGenerator{
List: &argov1alpha1.ListGenerator{
requestedGenerator := &argoprojiov1alpha1.ApplicationSetGenerator{
List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}},
}}
@@ -367,10 +367,10 @@ func TestGetRelevantGenerators(t *testing.T) {
assert.Len(t, relevantGenerators, 1)
assert.IsType(t, &ListGenerator{}, relevantGenerators[0])
requestedGenerator = &argov1alpha1.ApplicationSetGenerator{
Clusters: &argov1alpha1.ClusterGenerator{
requestedGenerator = &argoprojiov1alpha1.ApplicationSetGenerator{
Clusters: &argoprojiov1alpha1.ClusterGenerator{
Selector: metav1.LabelSelector{},
Template: argov1alpha1.ApplicationSetTemplate{},
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
Values: nil,
},
}
@@ -379,14 +379,14 @@ func TestGetRelevantGenerators(t *testing.T) {
assert.Len(t, relevantGenerators, 1)
assert.IsType(t, &ClusterGenerator{}, relevantGenerators[0])
requestedGenerator = &argov1alpha1.ApplicationSetGenerator{
Git: &argov1alpha1.GitGenerator{
requestedGenerator = &argoprojiov1alpha1.ApplicationSetGenerator{
Git: &argoprojiov1alpha1.GitGenerator{
RepoURL: "",
Directories: nil,
Files: nil,
Revision: "",
RequeueAfterSeconds: nil,
Template: argov1alpha1.ApplicationSetTemplate{},
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
},
}
@@ -396,8 +396,8 @@ func TestGetRelevantGenerators(t *testing.T) {
}
func TestInterpolateGenerator(t *testing.T) {
requestedGenerator := &argov1alpha1.ApplicationSetGenerator{
Clusters: &argov1alpha1.ClusterGenerator{
requestedGenerator := &argoprojiov1alpha1.ApplicationSetGenerator{
Clusters: &argoprojiov1alpha1.ClusterGenerator{
Selector: metav1.LabelSelector{
MatchLabels: map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
@@ -414,7 +414,7 @@ func TestInterpolateGenerator(t *testing.T) {
"path[1]": "p2",
"path.basenameNormalized": "app3",
}
interpolatedGenerator, err := InterpolateGenerator(requestedGenerator, gitGeneratorParams, false, nil)
interpolatedGenerator, err := InterpolateGenerator(requestedGenerator, gitGeneratorParams, false)
if err != nil {
log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator")
return
@@ -423,23 +423,23 @@ func TestInterpolateGenerator(t *testing.T) {
assert.Equal(t, "p1", interpolatedGenerator.Clusters.Selector.MatchLabels["path-zero"])
assert.Equal(t, "p1/p2/app3", interpolatedGenerator.Clusters.Selector.MatchLabels["path-full"])
fileNamePath := argov1alpha1.GitFileGeneratorItem{
fileNamePath := argoprojiov1alpha1.GitFileGeneratorItem{
Path: "{{name}}",
}
fileServerPath := argov1alpha1.GitFileGeneratorItem{
fileServerPath := argoprojiov1alpha1.GitFileGeneratorItem{
Path: "{{server}}",
}
requestedGenerator = &argov1alpha1.ApplicationSetGenerator{
Git: &argov1alpha1.GitGenerator{
Files: append([]argov1alpha1.GitFileGeneratorItem{}, fileNamePath, fileServerPath),
Template: argov1alpha1.ApplicationSetTemplate{},
requestedGenerator = &argoprojiov1alpha1.ApplicationSetGenerator{
Git: &argoprojiov1alpha1.GitGenerator{
Files: append([]argoprojiov1alpha1.GitFileGeneratorItem{}, fileNamePath, fileServerPath),
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
},
}
clusterGeneratorParams := map[string]interface{}{
"name": "production_01/west", "server": "https://production-01.example.com",
}
interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, false, nil)
interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, false)
if err != nil {
log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator")
return
@@ -449,8 +449,8 @@ func TestInterpolateGenerator(t *testing.T) {
}
func TestInterpolateGenerator_go(t *testing.T) {
requestedGenerator := &argov1alpha1.ApplicationSetGenerator{
Clusters: &argov1alpha1.ClusterGenerator{
requestedGenerator := &argoprojiov1alpha1.ApplicationSetGenerator{
Clusters: &argoprojiov1alpha1.ClusterGenerator{
Selector: metav1.LabelSelector{
MatchLabels: map[string]string{
"argocd.argoproj.io/secret-type": "cluster",
@@ -467,7 +467,7 @@ func TestInterpolateGenerator_go(t *testing.T) {
"segments": []string{"p1", "p2", "app3"},
},
}
interpolatedGenerator, err := InterpolateGenerator(requestedGenerator, gitGeneratorParams, true, nil)
interpolatedGenerator, err := InterpolateGenerator(requestedGenerator, gitGeneratorParams, true)
require.NoError(t, err)
if err != nil {
log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator")
@@ -477,23 +477,23 @@ func TestInterpolateGenerator_go(t *testing.T) {
assert.Equal(t, "p1", interpolatedGenerator.Clusters.Selector.MatchLabels["path-zero"])
assert.Equal(t, "p1/p2/app3", interpolatedGenerator.Clusters.Selector.MatchLabels["path-full"])
fileNamePath := argov1alpha1.GitFileGeneratorItem{
fileNamePath := argoprojiov1alpha1.GitFileGeneratorItem{
Path: "{{.name}}",
}
fileServerPath := argov1alpha1.GitFileGeneratorItem{
fileServerPath := argoprojiov1alpha1.GitFileGeneratorItem{
Path: "{{.server}}",
}
requestedGenerator = &argov1alpha1.ApplicationSetGenerator{
Git: &argov1alpha1.GitGenerator{
Files: append([]argov1alpha1.GitFileGeneratorItem{}, fileNamePath, fileServerPath),
Template: argov1alpha1.ApplicationSetTemplate{},
requestedGenerator = &argoprojiov1alpha1.ApplicationSetGenerator{
Git: &argoprojiov1alpha1.GitGenerator{
Files: append([]argoprojiov1alpha1.GitFileGeneratorItem{}, fileNamePath, fileServerPath),
Template: argoprojiov1alpha1.ApplicationSetTemplate{},
},
}
clusterGeneratorParams := map[string]interface{}{
"name": "production_01/west", "server": "https://production-01.example.com",
}
interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, true, nil)
interpolatedGenerator, err = InterpolateGenerator(requestedGenerator, clusterGeneratorParams, true)
if err != nil {
log.WithError(err).WithField("requestedGenerator", requestedGenerator).Error("error interpolating Generator")
return
@@ -501,60 +501,3 @@ func TestInterpolateGenerator_go(t *testing.T) {
assert.Equal(t, "production_01/west", interpolatedGenerator.Git.Files[0].Path)
assert.Equal(t, "https://production-01.example.com", interpolatedGenerator.Git.Files[1].Path)
}
func TestInterpolateGeneratorError(t *testing.T) {
type args struct {
requestedGenerator *argov1alpha1.ApplicationSetGenerator
params map[string]interface{}
useGoTemplate bool
goTemplateOptions []string
}
tests := []struct {
name string
args args
want argov1alpha1.ApplicationSetGenerator
expectedErrStr string
}{
{name: "Empty Gen", args: args{
requestedGenerator: nil,
params: nil,
useGoTemplate: false,
goTemplateOptions: nil,
}, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: "generator is empty"},
{name: "No Params", args: args{
requestedGenerator: &argov1alpha1.ApplicationSetGenerator{},
params: map[string]interface{}{},
useGoTemplate: false,
goTemplateOptions: nil,
}, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: ""},
{name: "Error templating", args: args{
requestedGenerator: &argov1alpha1.ApplicationSetGenerator{Git: &argov1alpha1.GitGenerator{
RepoURL: "foo",
Files: []argov1alpha1.GitFileGeneratorItem{{Path: "bar/"}},
Revision: "main",
Values: map[string]string{
"git_test": "{{ toPrettyJson . }}",
"selection": "{{ default .override .test }}",
"resolved": "{{ index .rmap (default .override .test) }}",
},
}},
params: map[string]interface{}{
"name": "in-cluster",
"override": "foo",
},
useGoTemplate: true,
goTemplateOptions: []string{},
}, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: "failed to replace parameters in generator: failed to execute go template {{ index .rmap (default .override .test) }}: template: :1:3: executing \"\" at <index .rmap (default .override .test)>: error calling index: index of untyped nil"},
}
for _, tt := range tests {
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 != "" {
assert.EqualError(t, err, tt.expectedErrStr)
} else {
require.NoError(t, err)
}
assert.Equalf(t, tt.want, got, "InterpolateGenerator(%v, %v, %v, %v)", tt.args.requestedGenerator, tt.args.params, tt.args.useGoTemplate, tt.args.goTemplateOptions)
})
}
}

View File

@@ -59,25 +59,25 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
var err error
var res []map[string]interface{}
if len(appSetGenerator.Git.Directories) != 0 {
res, err = g.generateParamsForGitDirectories(appSetGenerator, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
res, err = g.generateParamsForGitDirectories(appSetGenerator, appSet.Spec.GoTemplate)
} else if len(appSetGenerator.Git.Files) != 0 {
res, err = g.generateParamsForGitFiles(appSetGenerator, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
res, err = g.generateParamsForGitFiles(appSetGenerator, appSet.Spec.GoTemplate)
} else {
return nil, EmptyAppSetGeneratorError
}
if err != nil {
return nil, fmt.Errorf("error generating params from git: %w", err)
return nil, err
}
return res, nil
}
func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool) ([]map[string]interface{}, error) {
// Directories, not files
allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision)
if err != nil {
return nil, fmt.Errorf("error getting directories from repo: %w", err)
return nil, err
}
log.WithFields(log.Fields{
@@ -90,15 +90,12 @@ func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoproj
requestedApps := g.filterApps(appSetGenerator.Git.Directories, allPaths)
res, err := g.generateParamsFromApps(requestedApps, appSetGenerator, useGoTemplate, goTemplateOptions)
if err != nil {
return nil, fmt.Errorf("error generating params from apps: %w", err)
}
res := g.generateParamsFromApps(requestedApps, appSetGenerator, useGoTemplate)
return res, nil
}
func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool) ([]map[string]interface{}, error) {
// Get all files that match the requested path string, removing duplicates
allFiles := make(map[string][]byte)
@@ -125,7 +122,7 @@ func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1al
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)
paramsArray, err := g.generateParamsFromGitFile(path, allFiles[path], useGoTemplate, appSetGenerator.Git.PathParamPrefix)
if err != nil {
return nil, fmt.Errorf("unable to process file '%s': %v", path, err)
}
@@ -135,7 +132,7 @@ func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1al
return res, nil
}
func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []byte, values map[string]string, useGoTemplate bool, goTemplateOptions []string, pathParamPrefix string) ([]map[string]interface{}, error) {
func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []byte, useGoTemplate bool, pathParamPrefix string) ([]map[string]interface{}, error) {
objectsFound := []map[string]interface{}{}
// First, we attempt to parse as an array
@@ -148,9 +145,6 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
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{}{}
@@ -180,7 +174,7 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
} else {
flat, err := flatten.Flatten(objectFound, "", flatten.DotStyle)
if err != nil {
return nil, fmt.Errorf("error flattening object: %w", err)
return nil, err
}
for k, v := range flat {
params[k] = fmt.Sprintf("%v", v)
@@ -201,11 +195,6 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
}
}
err := appendTemplatedValues(values, params, useGoTemplate, goTemplateOptions)
if err != nil {
return nil, fmt.Errorf("failed to append templated values: %w", err)
}
res = append(res, params)
}
@@ -240,7 +229,7 @@ func (g *GitGenerator) filterApps(Directories []argoprojiov1alpha1.GitDirectoryG
return res
}
func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool, goTemplateOptions []string) ([]map[string]interface{}, error) {
func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool) []map[string]interface{} {
res := make([]map[string]interface{}, len(requestedApps))
for i, a := range requestedApps {
@@ -272,13 +261,8 @@ func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGene
}
}
err := appendTemplatedValues(appSetGenerator.Git.Values, params, useGoTemplate, goTemplateOptions)
if err != nil {
return nil, fmt.Errorf("failed to append templated values: %w", err)
}
res[i] = params
}
return res, nil
return res
}

View File

@@ -4,173 +4,122 @@ import (
"fmt"
"testing"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
testutils "github.com/argoproj/argo-cd/v2/applicationset/utils/test"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
// type clientSet struct {
// RepoServerServiceClient apiclient.RepoServerServiceClient
// }
// func (c *clientSet) NewRepoServerClient() (io.Closer, apiclient.RepoServerServiceClient, error) {
// return io.NewCloser(func() error { return nil }), c.RepoServerServiceClient, nil
// }
func Test_generateParamsFromGitFile(t *testing.T) {
defaultContent := []byte(`
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
`), false, "")
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) {
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
foo:
bar: baz
`), false, "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) {
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
foo:
bar: baz
`), true, "")
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) {
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
foo:
bar: baz
`), true, "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) {
@@ -181,7 +130,6 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
pathParamPrefix string
repoApps []string
repoError error
values map[string]string
expected []map[string]interface{}
expectedError error
}{
@@ -272,25 +220,6 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
},
expectedError: nil,
},
{
name: "Value variable interpolation",
directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}, {Path: "*/*"}},
repoApps: []string{
"app1",
"p1/app2",
},
repoError: nil,
values: map[string]string{
"foo": "bar",
"aaa": "{{ path[0] }}",
"no-op": "{{ this-does-not-exist }}",
},
expected: []map[string]interface{}{
{"values.foo": "bar", "values.no-op": "{{ this-does-not-exist }}", "values.aaa": "app1", "path": "app1", "path.basename": "app1", "path[0]": "app1", "path.basenameNormalized": "app1"},
{"values.foo": "bar", "values.no-op": "{{ this-does-not-exist }}", "values.aaa": "p1", "path": "p1/app2", "path.basename": "app2", "path[0]": "p1", "path[1]": "app2", "path.basenameNormalized": "app2"},
},
expectedError: nil,
},
{
name: "handles empty response from repo server",
directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
@@ -305,7 +234,7 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
repoApps: []string{},
repoError: fmt.Errorf("error"),
expected: []map[string]interface{}{},
expectedError: fmt.Errorf("error generating params from git: error getting directories from repo: error"),
expectedError: fmt.Errorf("error"),
},
}
@@ -315,11 +244,11 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel()
argoCDServiceMock := mocks.Repos{}
argoCDServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
argoCDServiceMock.Mock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
var gitGenerator = NewGitGenerator(&argoCDServiceMock)
var gitGenerator = NewGitGenerator(argoCDServiceMock)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -331,7 +260,6 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
Revision: "Revision",
Directories: testCaseCopy.directories,
PathParamPrefix: testCaseCopy.pathParamPrefix,
Values: testCaseCopy.values,
},
}},
},
@@ -346,7 +274,7 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
assert.Equal(t, testCaseCopy.expected, got)
}
argoCDServiceMock.AssertExpectations(t)
argoCDServiceMock.Mock.AssertExpectations(t)
})
}
}
@@ -601,7 +529,7 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
repoApps: []string{},
repoError: fmt.Errorf("error"),
expected: []map[string]interface{}{},
expectedError: fmt.Errorf("error generating params from git: error getting directories from repo: error"),
expectedError: fmt.Errorf("error"),
},
}
@@ -611,11 +539,11 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel()
argoCDServiceMock := mocks.Repos{}
argoCDServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
argoCDServiceMock.Mock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
var gitGenerator = NewGitGenerator(&argoCDServiceMock)
var gitGenerator = NewGitGenerator(argoCDServiceMock)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -642,7 +570,7 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
assert.Equal(t, testCaseCopy.expected, got)
}
argoCDServiceMock.AssertExpectations(t)
argoCDServiceMock.Mock.AssertExpectations(t)
})
}
@@ -658,7 +586,6 @@ func TestGitGenerateParamsFromFiles(t *testing.T) {
repoFileContents map[string][]byte
// if repoPathsError is non-nil, the call to GetPaths(...) will return this error value
repoPathsError error
values map[string]string
expected []map[string]interface{}
expectedError error
}{
@@ -722,81 +649,13 @@ func TestGitGenerateParamsFromFiles(t *testing.T) {
},
expectedError: nil,
},
{
name: "Value variable interpolation",
files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.json"}},
repoFileContents: map[string][]byte{
"cluster-config/production/config.json": []byte(`{
"cluster": {
"owner": "john.doe@example.com",
"name": "production",
"address": "https://kubernetes.default.svc"
},
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123
}`),
"cluster-config/staging/config.json": []byte(`{
"cluster": {
"owner": "foo.bar@example.com",
"name": "staging",
"address": "https://kubernetes.default.svc"
}
}`),
},
repoPathsError: nil,
values: map[string]string{
"aaa": "{{ cluster.owner }}",
"no-op": "{{ this-does-not-exist }}",
},
expected: []map[string]interface{}{
{
"cluster.owner": "john.doe@example.com",
"cluster.name": "production",
"cluster.address": "https://kubernetes.default.svc",
"key1": "val1",
"key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123",
"path": "cluster-config/production",
"path.basename": "production",
"path[0]": "cluster-config",
"path[1]": "production",
"path.basenameNormalized": "production",
"path.filename": "config.json",
"path.filenameNormalized": "config.json",
"values.aaa": "john.doe@example.com",
"values.no-op": "{{ this-does-not-exist }}",
},
{
"cluster.owner": "foo.bar@example.com",
"cluster.name": "staging",
"cluster.address": "https://kubernetes.default.svc",
"path": "cluster-config/staging",
"path.basename": "staging",
"path[0]": "cluster-config",
"path[1]": "staging",
"path.basenameNormalized": "staging",
"path.filename": "config.json",
"path.filenameNormalized": "config.json",
"values.aaa": "foo.bar@example.com",
"values.no-op": "{{ this-does-not-exist }}",
},
},
expectedError: nil,
},
{
name: "handles error during getting repo paths",
files: []argoprojiov1alpha1.GitFileGeneratorItem{{Path: "**/config.json"}},
repoFileContents: map[string][]byte{},
repoPathsError: fmt.Errorf("paths error"),
expected: []map[string]interface{}{},
expectedError: fmt.Errorf("error generating params from git: paths error"),
expectedError: fmt.Errorf("paths error"),
},
{
name: "test invalid JSON file returns error",
@@ -806,7 +665,7 @@ func TestGitGenerateParamsFromFiles(t *testing.T) {
},
repoPathsError: nil,
expected: []map[string]interface{}{},
expectedError: fmt.Errorf("error generating params from git: unable to process file 'cluster-config/production/config.json': unable to parse file: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type map[string]interface {}"),
expectedError: fmt.Errorf("unable to process file 'cluster-config/production/config.json': unable to parse file: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type map[string]interface {}"),
},
{
name: "test JSON array",
@@ -971,11 +830,11 @@ cluster:
t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel()
argoCDServiceMock := mocks.Repos{}
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
argoCDServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
argoCDServiceMock.Mock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
var gitGenerator = NewGitGenerator(&argoCDServiceMock)
var gitGenerator = NewGitGenerator(argoCDServiceMock)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -986,7 +845,6 @@ cluster:
RepoURL: "RepoURL",
Revision: "Revision",
Files: testCaseCopy.files,
Values: testCaseCopy.values,
},
}},
},
@@ -1002,7 +860,7 @@ cluster:
assert.ElementsMatch(t, testCaseCopy.expected, got)
}
argoCDServiceMock.AssertExpectations(t)
argoCDServiceMock.Mock.AssertExpectations(t)
})
}
}
@@ -1102,7 +960,7 @@ func TestGitGenerateParamsFromFilesGoTemplate(t *testing.T) {
repoFileContents: map[string][]byte{},
repoPathsError: fmt.Errorf("paths error"),
expected: []map[string]interface{}{},
expectedError: fmt.Errorf("error generating params from git: paths error"),
expectedError: fmt.Errorf("paths error"),
},
{
name: "test invalid JSON file returns error",
@@ -1112,7 +970,7 @@ func TestGitGenerateParamsFromFilesGoTemplate(t *testing.T) {
},
repoPathsError: nil,
expected: []map[string]interface{}{},
expectedError: fmt.Errorf("error generating params from git: unable to process file 'cluster-config/production/config.json': unable to parse file: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type map[string]interface {}"),
expectedError: fmt.Errorf("unable to process file 'cluster-config/production/config.json': unable to parse file: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type map[string]interface {}"),
},
{
name: "test JSON array",
@@ -1321,11 +1179,11 @@ cluster:
t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel()
argoCDServiceMock := mocks.Repos{}
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
argoCDServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
argoCDServiceMock.Mock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
var gitGenerator = NewGitGenerator(&argoCDServiceMock)
var gitGenerator = NewGitGenerator(argoCDServiceMock)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -1352,7 +1210,7 @@ cluster:
assert.ElementsMatch(t, testCaseCopy.expected, got)
}
argoCDServiceMock.AssertExpectations(t)
argoCDServiceMock.Mock.AssertExpectations(t)
})
}
}

View File

@@ -5,9 +5,8 @@ import (
"fmt"
"time"
"sigs.k8s.io/yaml"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"sigs.k8s.io/yaml"
)
var _ Generator = (*ListGenerator)(nil)
@@ -83,7 +82,7 @@ func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appli
if err != nil {
return nil, fmt.Errorf("error unmarshling decoded ElementsYaml %v", err)
}
res = append(res, yamlElements...)
res = append(res, yamlElements...)
}
return res, nil

View File

@@ -8,8 +8,6 @@ import (
"github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
log "github.com/sirupsen/logrus"
)
var _ Generator = (*MatrixGenerator)(nil)
@@ -50,7 +48,7 @@ func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.App
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)
return nil, err
}
for _, a := range g0 {
g1, err := m.getParams(appSetGenerator.Matrix.Generators[1], appSet, a)
@@ -61,11 +59,11 @@ func (m *MatrixGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.App
if appSet.Spec.GoTemplate {
tmp := map[string]interface{}{}
if err := mergo.Merge(&tmp, b, mergo.WithOverride); err != nil {
return nil, fmt.Errorf("failed to merge params from the second generator in the matrix generator with temp map: %w", err)
if err := mergo.Merge(&tmp, a); err != nil {
return nil, fmt.Errorf("failed to merge params from the first generator in the matrix generator with temp map: %w", err)
}
if err := mergo.Merge(&tmp, a, mergo.WithOverride); err != nil {
return nil, fmt.Errorf("failed to merge params from the second generator in the matrix generator with the first: %w", err)
if err := mergo.Merge(&tmp, b); err != nil {
return nil, fmt.Errorf("failed to merge params from the first generator in the matrix generator with the second: %w", err)
}
res = append(res, tmp)
} else {
@@ -86,21 +84,9 @@ func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Appli
if err != nil {
return nil, err
}
if matrixGen != nil && !appSet.Spec.ApplyNestedSelectors {
foundSelector := dropDisabledNestedSelectors(matrixGen.Generators)
if foundSelector {
log.Warnf("AppSet '%v' defines selector on nested matrix generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selectors", appSet.Name)
}
}
mergeGen, err := getMergeGenerator(appSetBaseGenerator)
if err != nil {
return nil, fmt.Errorf("error retrieving merge generator: %w", err)
}
if mergeGen != nil && !appSet.Spec.ApplyNestedSelectors {
foundSelector := dropDisabledNestedSelectors(mergeGen.Generators)
if foundSelector {
log.Warnf("AppSet '%v' defines selector on nested merge generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selectors", appSet.Name)
}
return nil, err
}
t, err := Transform(
@@ -111,7 +97,6 @@ func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Appli
SCMProvider: appSetBaseGenerator.SCMProvider,
ClusterDecisionResource: appSetBaseGenerator.ClusterDecisionResource,
PullRequest: appSetBaseGenerator.PullRequest,
Plugin: appSetBaseGenerator.Plugin,
Matrix: matrixGen,
Merge: mergeGen,
Selector: appSetBaseGenerator.Selector,
@@ -146,15 +131,12 @@ func (m *MatrixGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Ap
matrixGen, _ := getMatrixGenerator(r)
mergeGen, _ := getMergeGenerator(r)
base := &argoprojiov1alpha1.ApplicationSetGenerator{
List: r.List,
Clusters: r.Clusters,
Git: r.Git,
PullRequest: r.PullRequest,
Plugin: r.Plugin,
SCMProvider: r.SCMProvider,
ClusterDecisionResource: r.ClusterDecisionResource,
Matrix: matrixGen,
Merge: mergeGen,
List: r.List,
Clusters: r.Clusters,
Git: r.Git,
PullRequest: r.PullRequest,
Matrix: matrixGen,
Merge: mergeGen,
}
generators := GetRelevantGenerators(base, m.supportedGenerators)

View File

@@ -13,12 +13,11 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
testutils "github.com/argoproj/argo-cd/v2/applicationset/utils/test"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
@@ -271,28 +270,6 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
{"a": "2", "b": "2"},
},
},
{
name: "parameter override: first list elements take precedence",
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{
{Raw: []byte(`{"booleanFalse": false, "booleanTrue": true, "stringFalse": "false", "stringTrue": "true"}`)},
},
},
},
{
List: &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{
{Raw: []byte(`{"booleanFalse": true, "booleanTrue": false, "stringFalse": "true", "stringTrue": "false"}`)},
},
},
},
},
expected: []map[string]interface{}{
{"booleanFalse": false, "booleanTrue": true, "stringFalse": "false", "stringTrue": "true"},
},
},
{
name: "returns error if there is less than two base generators",
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
@@ -426,10 +403,6 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
pullRequestGenerator := &argoprojiov1alpha1.PullRequestGenerator{}
scmGenerator := &argoprojiov1alpha1.SCMProviderGenerator{}
duckTypeGenerator := &argoprojiov1alpha1.DuckTypeGenerator{}
testCases := []struct {
name string
baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
@@ -487,30 +460,6 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
},
expected: time.Duration(30 * time.Minute),
},
{
name: "returns the default time for duck type generator",
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
Git: gitGenerator,
},
{
ClusterDecisionResource: duckTypeGenerator,
},
},
expected: time.Duration(3 * time.Minute),
},
{
name: "returns the default time for scm generator",
baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
Git: gitGenerator,
},
{
SCMProvider: scmGenerator,
},
},
expected: time.Duration(30 * time.Minute),
},
}
for _, testCase := range testCases {
@@ -521,22 +470,18 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
for _, g := range testCaseCopy.baseGenerators {
gitGeneratorSpec := argoprojiov1alpha1.ApplicationSetGenerator{
Git: g.Git,
List: g.List,
PullRequest: g.PullRequest,
SCMProvider: g.SCMProvider,
ClusterDecisionResource: g.ClusterDecisionResource,
Git: g.Git,
List: g.List,
PullRequest: g.PullRequest,
}
mock.On("GetRequeueAfter", &gitGeneratorSpec).Return(testCaseCopy.gitGetRequeueAfter, nil)
}
var matrixGenerator = NewMatrixGenerator(
map[string]Generator{
"Git": mock,
"List": &ListGenerator{},
"PullRequest": &PullRequestGenerator{},
"SCMProvider": &SCMProviderGenerator{},
"ClusterDecisionResource": &DuckTypeGenerator{},
"Git": mock,
"List": &ListGenerator{},
"PullRequest": &PullRequestGenerator{},
},
)
@@ -903,7 +848,7 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
}
listGenerator := &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{},
Elements: []apiextensionsv1.JSON{},
ElementsYaml: "{{ .foo.bar | toJson }}",
}
@@ -925,59 +870,60 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
},
expected: []map[string]interface{}{
{
"chart": "a",
"version": "1",
"chart": "a",
"version": "1",
"foo": map[string]interface{}{
"bar": []interface{}{
map[string]interface{}{
"chart": "a",
"chart": "a",
"version": "1",
},
map[string]interface{}{
"chart": "b",
"chart": "b",
"version": "2",
},
},
},
"path": map[string]interface{}{
"basename": "dir",
"basename": "dir",
"basenameNormalized": "dir",
"filename": "file_name.yaml",
"filename": "file_name.yaml",
"filenameNormalized": "file-name.yaml",
"path": "path/dir",
"segments": []string{
"path": "path/dir",
"segments": []string {
"path",
"dir",
},
},
},
{
"chart": "b",
"version": "2",
"chart": "b",
"version": "2",
"foo": map[string]interface{}{
"bar": []interface{}{
map[string]interface{}{
"chart": "a",
"chart": "a",
"version": "1",
},
map[string]interface{}{
"chart": "b",
"chart": "b",
"version": "2",
},
},
},
"path": map[string]interface{}{
"basename": "dir",
"basename": "dir",
"basenameNormalized": "dir",
"filename": "file_name.yaml",
"filename": "file_name.yaml",
"filenameNormalized": "file-name.yaml",
"path": "path/dir",
"segments": []string{
"path": "path/dir",
"segments": []string {
"path",
"dir",
},
},
},
},
},
}
@@ -1006,26 +952,27 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
"foo": map[string]interface{}{
"bar": []interface{}{
map[string]interface{}{
"chart": "a",
"chart": "a",
"version": "1",
},
map[string]interface{}{
"chart": "b",
"chart": "b",
"version": "2",
},
},
},
"path": map[string]interface{}{
"basename": "dir",
"basename": "dir",
"basenameNormalized": "dir",
"filename": "file_name.yaml",
"filename": "file_name.yaml",
"filenameNormalized": "file-name.yaml",
"path": "path/dir",
"segments": []string{
"path": "path/dir",
"segments": []string {
"path",
"dir",
},
},
}}, nil)
genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&argoprojiov1alpha1.ApplicationSetTemplate{})
@@ -1107,8 +1054,8 @@ 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).Return(map[string][]byte{
repoServiceMock := testutils.ArgoCDServiceMock{Mock: &mock.Mock{}}
repoServiceMock.Mock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(map[string][]byte{
"some/path.json": []byte("test: content"),
}, nil)
gitGenerator := NewGitGenerator(repoServiceMock)

View File

@@ -9,8 +9,6 @@ import (
"github.com/argoproj/argo-cd/v2/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
log "github.com/sirupsen/logrus"
)
var _ Generator = (*MergeGenerator)(nil)
@@ -38,10 +36,10 @@ func NewMergeGenerator(supportedGenerators map[string]Generator) Generator {
// in slices ordered according to the order of the given generators.
func (m *MergeGenerator) getParamSetsForAllGenerators(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([][]map[string]interface{}, error) {
var paramSets [][]map[string]interface{}
for i, generator := range generators {
for _, generator := range generators {
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)
return nil, err
}
// concatenate param lists produced by each generator
paramSets = append(paramSets, generatorParamSets)
@@ -61,18 +59,18 @@ func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appl
paramSetsFromGenerators, err := m.getParamSetsForAllGenerators(appSetGenerator.Merge.Generators, appSet)
if err != nil {
return nil, fmt.Errorf("error getting param sets from generators: %w", err)
return nil, err
}
baseParamSetsByMergeKey, err := getParamSetsByMergeKey(appSetGenerator.Merge.MergeKeys, paramSetsFromGenerators[0])
if err != nil {
return nil, fmt.Errorf("error getting param sets by merge key: %w", err)
return nil, err
}
for _, paramSets := range paramSetsFromGenerators[1:] {
paramSetsByMergeKey, err := getParamSetsByMergeKey(appSetGenerator.Merge.MergeKeys, paramSets)
if err != nil {
return nil, fmt.Errorf("error getting param sets by merge key: %w", err)
return nil, err
}
for mergeKeyValue, baseParamSet := range baseParamSetsByMergeKey {
@@ -80,13 +78,13 @@ func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appl
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)
return nil, fmt.Errorf("failed to merge base param set with override param set: %w", err)
}
baseParamSetsByMergeKey[mergeKeyValue] = baseParamSet
} else {
overriddenParamSet, err := utils.CombineStringMapsAllowDuplicates(baseParamSet, overrideParamSet)
if err != nil {
return nil, fmt.Errorf("error combining string maps: %w", err)
return nil, err
}
baseParamSetsByMergeKey[mergeKeyValue] = utils.ConvertToMapStringInterface(overriddenParamSet)
}
@@ -125,7 +123,7 @@ func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]interface
}
paramSetKeyJson, err := json.Marshal(paramSetKey)
if err != nil {
return nil, fmt.Errorf("error marshalling param set key json: %w", err)
return nil, err
}
paramSetKeyString := string(paramSetKeyJson)
if _, exists := paramSetsByMergeKey[paramSetKeyString]; exists {
@@ -143,22 +141,10 @@ func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Applic
if err != nil {
return nil, err
}
if matrixGen != nil && !appSet.Spec.ApplyNestedSelectors {
foundSelector := dropDisabledNestedSelectors(matrixGen.Generators)
if foundSelector {
log.Warnf("AppSet '%v' defines selector on nested matrix generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selector", appSet.Name)
}
}
mergeGen, err := getMergeGenerator(appSetBaseGenerator)
if err != nil {
return nil, err
}
if mergeGen != nil && !appSet.Spec.ApplyNestedSelectors {
foundSelector := dropDisabledNestedSelectors(mergeGen.Generators)
if foundSelector {
log.Warnf("AppSet '%v' defines selector on nested merge generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selector", appSet.Name)
}
}
t, err := Transform(
argoprojiov1alpha1.ApplicationSetGenerator{
@@ -168,7 +154,6 @@ func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Applic
SCMProvider: appSetBaseGenerator.SCMProvider,
ClusterDecisionResource: appSetBaseGenerator.ClusterDecisionResource,
PullRequest: appSetBaseGenerator.PullRequest,
Plugin: appSetBaseGenerator.Plugin,
Matrix: matrixGen,
Merge: mergeGen,
Selector: appSetBaseGenerator.Selector,
@@ -201,15 +186,12 @@ func (m *MergeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.App
matrixGen, _ := getMatrixGenerator(r)
mergeGen, _ := getMergeGenerator(r)
base := &argoprojiov1alpha1.ApplicationSetGenerator{
List: r.List,
Clusters: r.Clusters,
Git: r.Git,
PullRequest: r.PullRequest,
Plugin: r.Plugin,
SCMProvider: r.SCMProvider,
ClusterDecisionResource: r.ClusterDecisionResource,
Matrix: matrixGen,
Merge: mergeGen,
List: r.List,
Clusters: r.Clusters,
Git: r.Git,
PullRequest: r.PullRequest,
Matrix: matrixGen,
Merge: mergeGen,
}
generators := GetRelevantGenerators(base, m.supportedGenerators)
@@ -236,7 +218,7 @@ func getMergeGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*arg
}
merge, err := argoprojiov1alpha1.ToNestedMergeGenerator(r.Merge)
if err != nil {
return nil, fmt.Errorf("error converting to nested merge generator: %w", err)
return nil, err
}
return merge.ToMergeGenerator(), nil
}

View File

@@ -1,211 +0,0 @@
package generators
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"github.com/jeremywohl/flatten"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/settings"
"github.com/argoproj/argo-cd/v2/applicationset/services/plugin"
)
const (
DefaultPluginRequeueAfterSeconds = 30 * time.Minute
)
var _ Generator = (*PluginGenerator)(nil)
type PluginGenerator struct {
client client.Client
ctx context.Context
clientset kubernetes.Interface
namespace string
}
func NewPluginGenerator(client client.Client, ctx context.Context, clientset kubernetes.Interface, namespace string) Generator {
g := &PluginGenerator{
client: client,
ctx: ctx,
clientset: clientset,
namespace: namespace,
}
return g
}
func (g *PluginGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
// Return a requeue default of 30 minutes, if no default is specified.
if appSetGenerator.Plugin.RequeueAfterSeconds != nil {
return time.Duration(*appSetGenerator.Plugin.RequeueAfterSeconds) * time.Second
}
return DefaultPluginRequeueAfterSeconds
}
func (g *PluginGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
return &appSetGenerator.Plugin.Template
}
func (g *PluginGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil {
return nil, EmptyAppSetGeneratorError
}
if appSetGenerator.Plugin == nil {
return nil, EmptyAppSetGeneratorError
}
ctx := context.Background()
providerConfig := appSetGenerator.Plugin
pluginClient, err := g.getPluginFromGenerator(ctx, applicationSetInfo.Name, providerConfig)
if err != nil {
return nil, fmt.Errorf("error getting plugin from generator: %w", err)
}
list, err := pluginClient.List(ctx, providerConfig.Input.Parameters)
if err != nil {
return nil, fmt.Errorf("error listing params: %w", err)
}
res, err := g.generateParams(appSetGenerator, applicationSetInfo, list.Output.Parameters, appSetGenerator.Plugin.Input.Parameters, applicationSetInfo.Spec.GoTemplate)
if err != nil {
return nil, fmt.Errorf("error generating params: %w", err)
}
return res, nil
}
func (g *PluginGenerator) getPluginFromGenerator(ctx context.Context, appSetName string, generatorConfig *argoprojiov1alpha1.PluginGenerator) (*plugin.Service, error) {
cm, err := g.getConfigMap(ctx, generatorConfig.ConfigMapRef.Name)
if err != nil {
return nil, fmt.Errorf("error fetching ConfigMap: %w", err)
}
token, err := g.getToken(ctx, cm["token"])
if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %v", err)
}
var requestTimeout int
requestTimeoutStr, ok := cm["requestTimeout"]
if ok {
requestTimeout, err = strconv.Atoi(requestTimeoutStr)
if err != nil {
return nil, fmt.Errorf("error set requestTimeout : %w", err)
}
}
pluginClient, err := plugin.NewPluginService(ctx, appSetName, cm["baseUrl"], token, requestTimeout)
if err != nil {
return nil, fmt.Errorf("error initializing plugin client: %w", err)
}
return pluginClient, nil
}
func (g *PluginGenerator) generateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, objectsFound []map[string]interface{}, pluginParams argoprojiov1alpha1.PluginParameters, useGoTemplate bool) ([]map[string]interface{}, error) {
res := []map[string]interface{}{}
for _, objectFound := range objectsFound {
params := map[string]interface{}{}
if useGoTemplate {
for k, v := range objectFound {
params[k] = v
}
} else {
flat, err := flatten.Flatten(objectFound, "", flatten.DotStyle)
if err != nil {
return nil, err
}
for k, v := range flat {
params[k] = fmt.Sprintf("%v", v)
}
}
params["generator"] = map[string]interface{}{
"input": map[string]argoprojiov1alpha1.PluginParameters{
"parameters": pluginParams,
},
}
err := appendTemplatedValues(appSetGenerator.Plugin.Values, params, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
if err != nil {
return nil, err
}
res = append(res, params)
}
return res, nil
}
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)
}
secretName, tokenKey := plugin.ParseSecretKey(tokenRef)
secret := &corev1.Secret{}
err := g.client.Get(
ctx,
client.ObjectKey{
Name: secretName,
Namespace: g.namespace,
},
secret)
if err != nil {
return "", fmt.Errorf("error fetching secret %s/%s: %v", g.namespace, secretName, err)
}
secretValues := make(map[string]string, len(secret.Data))
for k, v := range secret.Data {
secretValues[k] = string(v)
}
token := settings.ReplaceStringSecret(tokenKey, secretValues)
return token, err
}
func (g *PluginGenerator) getConfigMap(ctx context.Context, configMapRef string) (map[string]string, error) {
cm := &corev1.ConfigMap{}
err := g.client.Get(
ctx,
client.ObjectKey{
Name: configMapRef,
Namespace: g.namespace,
},
cm)
if err != nil {
return nil, err
}
baseUrl, ok := cm.Data["baseUrl"]
if !ok || baseUrl == "" {
return nil, fmt.Errorf("baseUrl not found in ConfigMap")
}
token, ok := cm.Data["token"]
if !ok || token == "" {
return nil, fmt.Errorf("token not found in ConfigMap")
}
return cm.Data, nil
}

View File

@@ -1,705 +0,0 @@
package generators
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
kubefake "k8s.io/client-go/kubernetes/fake"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/argoproj/argo-cd/v2/applicationset/services/plugin"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func TestPluginGenerateParams(t *testing.T) {
testCases := []struct {
name string
configmap *v1.ConfigMap
secret *v1.Secret
inputParameters map[string]apiextensionsv1.JSON
values map[string]string
gotemplate bool
expected []map[string]interface{}
content []byte
expectedError error
}{
{
name: "simple case",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"baseUrl": "http://127.0.0.1",
"token": "$plugin.token",
},
},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
},
Data: map[string][]byte{
"plugin.token": []byte("my-secret"),
},
},
inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
gotemplate: false,
content: []byte(`{"output": {
"parameters": [{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123
}]
}}`),
expected: []map[string]interface{}{
{
"key1": "val1",
"key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123",
"generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
},
},
},
},
expectedError: nil,
},
{
name: "simple case with values",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"baseUrl": "http://127.0.0.1",
"token": "$plugin.token",
},
},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
},
Data: map[string][]byte{
"plugin.token": []byte("my-secret"),
},
},
inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
values: map[string]string{
"valuekey1": "valuevalue1",
"valuekey2": "templated-{{key1}}",
},
gotemplate: false,
content: []byte(`{"output": {
"parameters": [{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123
}]
}}`),
expected: []map[string]interface{}{
{
"key1": "val1",
"key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123",
"values.valuekey1": "valuevalue1",
"values.valuekey2": "templated-val1",
"generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
},
},
},
},
expectedError: nil,
},
{
name: "simple case with gotemplate",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"baseUrl": "http://127.0.0.1",
"token": "$plugin.token",
},
},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
},
Data: map[string][]byte{
"plugin.token": []byte("my-secret"),
},
},
inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
gotemplate: true,
content: []byte(`{"output": {
"parameters": [{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123
}]
}}`),
expected: []map[string]interface{}{
{
"key1": "val1",
"key2": map[string]interface{}{
"key2_1": "val2_1",
"key2_2": map[string]interface{}{
"key2_2_1": "val2_2_1",
},
},
"key3": float64(123),
"generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
},
},
},
},
expectedError: nil,
},
{
name: "simple case with appended params",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"baseUrl": "http://127.0.0.1",
"token": "$plugin.token",
},
},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
},
Data: map[string][]byte{
"plugin.token": []byte("my-secret"),
},
},
inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
gotemplate: false,
content: []byte(`{"output": {"parameters": [{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123,
"pkey2": "valplugin"
}]}}`),
expected: []map[string]interface{}{
{
"key1": "val1",
"key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123",
"pkey2": "valplugin",
"generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
},
},
},
},
expectedError: nil,
},
{
name: "no params",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"baseUrl": "http://127.0.0.1",
"token": "$plugin.token",
},
},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
},
Data: map[string][]byte{
"plugin.token": []byte("my-secret"),
},
},
inputParameters: argoprojiov1alpha1.PluginParameters{},
gotemplate: false,
content: []byte(`{"output": {
"parameters": [{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123
}]
}}`),
expected: []map[string]interface{}{
{
"key1": "val1",
"key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123",
"generator": map[string]interface{}{
"input": map[string]map[string]interface{}{
"parameters": {},
},
},
},
},
expectedError: nil,
},
{
name: "empty return",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"baseUrl": "http://127.0.0.1",
"token": "$plugin.token",
},
},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
},
Data: map[string][]byte{
"plugin.token": []byte("my-secret"),
},
},
inputParameters: map[string]apiextensionsv1.JSON{},
gotemplate: false,
content: []byte(`{"input": {"parameters": []}}`),
expected: []map[string]interface{}{},
expectedError: nil,
},
{
name: "wrong return",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"baseUrl": "http://127.0.0.1",
"token": "$plugin.token",
},
},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
},
Data: map[string][]byte{
"plugin.token": []byte("my-secret"),
},
},
inputParameters: map[string]apiextensionsv1.JSON{},
gotemplate: false,
content: []byte(`wrong body ...`),
expected: []map[string]interface{}{},
expectedError: fmt.Errorf("error listing params: error get api 'set': invalid character 'w' looking for beginning of value: wrong body ..."),
},
{
name: "external secret",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"baseUrl": "http://127.0.0.1",
"token": "$plugin-secret:plugin.token",
},
},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "plugin-secret",
Namespace: "default",
},
Data: map[string][]byte{
"plugin.token": []byte("my-secret"),
},
},
inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
gotemplate: false,
content: []byte(`{"output": {"parameters": [{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123,
"pkey2": "valplugin"
}]}}`),
expected: []map[string]interface{}{
{
"key1": "val1",
"key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123",
"pkey2": "valplugin",
"generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
},
},
},
},
expectedError: nil,
},
{
name: "no secret",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"baseUrl": "http://127.0.0.1",
"token": "$plugin.token",
},
},
secret: &v1.Secret{},
inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
gotemplate: false,
content: []byte(`{"output": {
"parameters": [{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123
}]
}}`),
expected: []map[string]interface{}{
{
"key1": "val1",
"key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123",
"generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
},
},
},
},
expectedError: fmt.Errorf("error getting plugin from generator: error fetching Secret token: error fetching secret default/argocd-secret: secrets \"argocd-secret\" not found"),
},
{
name: "no configmap",
configmap: &v1.ConfigMap{},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
},
Data: map[string][]byte{
"plugin.token": []byte("my-secret"),
},
},
inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
gotemplate: false,
content: []byte(`{"output": {
"parameters": [{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123
}]
}}`),
expected: []map[string]interface{}{
{
"key1": "val1",
"key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123",
"generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
},
},
},
},
expectedError: fmt.Errorf("error getting plugin from generator: error fetching ConfigMap: configmaps \"\" not found"),
},
{
name: "no baseUrl",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"token": "$plugin.token",
},
},
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: "default",
},
Data: map[string][]byte{
"plugin.token": []byte("my-secret"),
},
},
inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
gotemplate: false,
content: []byte(`{"output": {
"parameters": [{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123
}]
}}`),
expected: []map[string]interface{}{
{
"key1": "val1",
"key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123",
"generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
},
},
},
},
expectedError: fmt.Errorf("error getting plugin from generator: error fetching ConfigMap: baseUrl not found in ConfigMap"),
},
{
name: "no token",
configmap: &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "first-plugin-cm",
Namespace: "default",
},
Data: map[string]string{
"baseUrl": "http://127.0.0.1",
},
},
secret: &v1.Secret{},
inputParameters: map[string]apiextensionsv1.JSON{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
gotemplate: false,
content: []byte(`{"output": {
"parameters": [{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123
}]
}}`),
expected: []map[string]interface{}{
{
"key1": "val1",
"key2.key2_1": "val2_1",
"key2.key2_2.key2_2_1": "val2_2_1",
"key3": "123",
"generator": map[string]interface{}{
"input": argoprojiov1alpha1.PluginInput{
Parameters: argoprojiov1alpha1.PluginParameters{
"pkey1": {Raw: []byte(`"val1"`)},
"pkey2": {Raw: []byte(`"val2"`)},
},
},
},
},
},
expectedError: fmt.Errorf("error getting plugin from generator: error fetching ConfigMap: token not found in ConfigMap"),
},
}
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},
Input: argoprojiov1alpha1.PluginInput{
Parameters: testCase.inputParameters,
},
Values: testCase.values,
},
}
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.Replace(tokenKey, "$", "", -1)]
if authHeader != "Bearer "+string(expectedToken) {
w.WriteHeader(http.StatusUnauthorized)
return
}
w.Header().Set("Content-Type", "application/json")
_, err := w.Write(testCase.content)
if err != nil {
assert.NoError(t, fmt.Errorf("Error Write %v", err))
}
})
fakeServer := httptest.NewServer(handler)
defer fakeServer.Close()
if _, ok := testCase.configmap.Data["baseUrl"]; ok {
testCase.configmap.Data["baseUrl"] = fakeServer.URL
}
fakeClient := kubefake.NewSimpleClientset(append([]runtime.Object{}, testCase.configmap, testCase.secret)...)
fakeClientWithCache := fake.NewClientBuilder().WithObjects([]client.Object{testCase.configmap, testCase.secret}...).Build()
var pluginGenerator = NewPluginGenerator(fakeClientWithCache, ctx, fakeClient, "default")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
GoTemplate: testCase.gotemplate,
},
}
got, err := pluginGenerator.GenerateParams(&generatorConfig, &applicationSetInfo)
if err != nil {
fmt.Println(err)
}
if testCase.expectedError != nil {
assert.EqualError(t, err, testCase.expectedError.Error())
} else {
assert.NoError(t, err)
expectedJson, err := json.Marshal(testCase.expected)
require.NoError(t, err)
gotJson, err := json.Marshal(got)
require.NoError(t, err)
assert.Equal(t, string(expectedJson), string(gotJson))
}
})
}
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/gosimple/slug"
"github.com/argoproj/argo-cd/v2/applicationset/services/pull_request"
pullrequest "github.com/argoproj/argo-cd/v2/applicationset/services/pull_request"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
@@ -25,18 +26,12 @@ type PullRequestGenerator struct {
client client.Client
selectServiceProviderFunc func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error)
auth SCMAuthProviders
scmRootCAPath string
allowedSCMProviders []string
enableSCMProviders bool
}
func NewPullRequestGenerator(client client.Client, auth SCMAuthProviders, scmRootCAPath string, allowedScmProviders []string, enableSCMProviders bool) Generator {
func NewPullRequestGenerator(client client.Client, auth SCMAuthProviders) Generator {
g := &PullRequestGenerator{
client: client,
auth: auth,
scmRootCAPath: scmRootCAPath,
allowedSCMProviders: allowedScmProviders,
enableSCMProviders: enableSCMProviders,
client: client,
auth: auth,
}
g.selectServiceProviderFunc = g.selectServiceProvider
return g
@@ -68,10 +63,10 @@ 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)
pulls, err := pull_request.ListPullRequests(ctx, svc, appSetGenerator.PullRequest.Filters)
if err != nil {
return nil, fmt.Errorf("error listing repos: %v", err)
}
@@ -89,27 +84,18 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
}
var shortSHALength int
var shortSHALength7 int
for _, pull := range pulls {
shortSHALength = 8
if len(pull.HeadSHA) < 8 {
shortSHALength = len(pull.HeadSHA)
}
shortSHALength7 = 7
if len(pull.HeadSHA) < 7 {
shortSHALength7 = len(pull.HeadSHA)
}
paramMap := map[string]interface{}{
"number": strconv.Itoa(pull.Number),
"branch": pull.Branch,
"branch_slug": slug.Make(pull.Branch),
"target_branch": pull.TargetBranch,
"target_branch_slug": slug.Make(pull.TargetBranch),
"head_sha": pull.HeadSHA,
"head_short_sha": pull.HeadSHA[:shortSHALength],
"head_short_sha_7": pull.HeadSHA[:shortSHALength7],
"number": strconv.Itoa(pull.Number),
"branch": pull.Branch,
"branch_slug": slug.Make(pull.Branch),
"head_sha": pull.HeadSHA,
"head_short_sha": pull.HeadSHA[:shortSHALength],
}
// PR lables will only be supported for Go Template appsets, since fasttemplate will be deprecated.
@@ -123,13 +109,6 @@ 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 {
return g.github(ctx, generatorConfig.Github, applicationSetInfo)
}
@@ -139,7 +118,7 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
if err != nil {
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)
return pullrequest.NewGitLabService(ctx, token, providerConfig.API, providerConfig.Project, providerConfig.Labels, providerConfig.PullRequestState)
}
if generatorConfig.Gitea != nil {
providerConfig := generatorConfig.Gitea
@@ -161,32 +140,6 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
return pullrequest.NewBitbucketServiceNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.Repo)
}
}
if generatorConfig.Bitbucket != nil {
providerConfig := generatorConfig.Bitbucket
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: %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: %v", err)
}
return pullrequest.NewBitbucketCloudServiceBasicAuth(providerConfig.API, providerConfig.BasicAuth.Username, password, providerConfig.Owner, providerConfig.Repo)
} else {
return pullrequest.NewBitbucketCloudServiceNoAuth(providerConfig.API, providerConfig.Owner, providerConfig.Repo)
}
}
if generatorConfig.AzureDevOps != nil {
providerConfig := generatorConfig.AzureDevOps
token, err := g.getSecretRef(ctx, providerConfig.TokenRef, applicationSetInfo.Namespace)
if err != nil {
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)
}
return nil, fmt.Errorf("no Pull Request provider implementation configured")
}

View File

@@ -27,11 +27,10 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
return pullrequest.NewFakeService(
ctx,
[]*pullrequest.PullRequest{
{
Number: 1,
Branch: "branch1",
TargetBranch: "master",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
&pullrequest.PullRequest{
Number: 1,
Branch: "branch1",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
},
},
nil,
@@ -39,14 +38,11 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
},
expected: []map[string]interface{}{
{
"number": "1",
"branch": "branch1",
"branch_slug": "branch1",
"target_branch": "master",
"target_branch_slug": "master",
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb",
"head_short_sha_7": "089d92c",
"number": "1",
"branch": "branch1",
"branch_slug": "branch1",
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb",
},
},
expectedErr: nil,
@@ -56,11 +52,10 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
return pullrequest.NewFakeService(
ctx,
[]*pullrequest.PullRequest{
{
Number: 2,
Branch: "feat/areally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
TargetBranch: "feat/anotherreally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
HeadSHA: "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
&pullrequest.PullRequest{
Number: 2,
Branch: "feat/areally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
HeadSHA: "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
},
},
nil,
@@ -68,14 +63,11 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
},
expected: []map[string]interface{}{
{
"number": "2",
"branch": "feat/areally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
"branch_slug": "feat-areally-long-pull-request-name-to-test-argo",
"target_branch": "feat/anotherreally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
"target_branch_slug": "feat-anotherreally-long-pull-request-name-to-test",
"head_sha": "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
"head_short_sha": "9b34ff5b",
"head_short_sha_7": "9b34ff5",
"number": "2",
"branch": "feat/areally+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
"branch_slug": "feat-areally-long-pull-request-name-to-test-argo",
"head_sha": "9b34ff5bd418e57d58891eb0aa0728043ca1e8be",
"head_short_sha": "9b34ff5b",
},
},
expectedErr: nil,
@@ -85,11 +77,10 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
return pullrequest.NewFakeService(
ctx,
[]*pullrequest.PullRequest{
{
Number: 1,
Branch: "a-very-short-sha",
TargetBranch: "master",
HeadSHA: "abcd",
&pullrequest.PullRequest{
Number: 1,
Branch: "a-very-short-sha",
HeadSHA: "abcd",
},
},
nil,
@@ -97,14 +88,11 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
},
expected: []map[string]interface{}{
{
"number": "1",
"branch": "a-very-short-sha",
"branch_slug": "a-very-short-sha",
"target_branch": "master",
"target_branch_slug": "master",
"head_sha": "abcd",
"head_short_sha": "abcd",
"head_short_sha_7": "abcd",
"number": "1",
"branch": "a-very-short-sha",
"branch_slug": "a-very-short-sha",
"head_sha": "abcd",
"head_short_sha": "abcd",
},
},
expectedErr: nil,
@@ -125,12 +113,11 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
return pullrequest.NewFakeService(
ctx,
[]*pullrequest.PullRequest{
{
Number: 1,
Branch: "branch1",
TargetBranch: "master",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
Labels: []string{"preview"},
&pullrequest.PullRequest{
Number: 1,
Branch: "branch1",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
Labels: []string{"preview"},
},
},
nil,
@@ -138,15 +125,12 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
},
expected: []map[string]interface{}{
{
"number": "1",
"branch": "branch1",
"branch_slug": "branch1",
"target_branch": "master",
"target_branch_slug": "master",
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb",
"head_short_sha_7": "089d92c",
"labels": []string{"preview"},
"number": "1",
"branch": "branch1",
"branch_slug": "branch1",
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb",
"labels": []string{"preview"},
},
},
expectedErr: nil,
@@ -162,12 +146,11 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
return pullrequest.NewFakeService(
ctx,
[]*pullrequest.PullRequest{
{
Number: 1,
Branch: "branch1",
TargetBranch: "master",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
Labels: []string{"preview"},
&pullrequest.PullRequest{
Number: 1,
Branch: "branch1",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
Labels: []string{"preview"},
},
},
nil,
@@ -175,14 +158,11 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
},
expected: []map[string]interface{}{
{
"number": "1",
"branch": "branch1",
"branch_slug": "branch1",
"target_branch": "master",
"target_branch_slug": "master",
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb",
"head_short_sha_7": "089d92c",
"number": "1",
"branch": "branch1",
"branch_slug": "branch1",
"head_sha": "089d92cbf9ff857a39e6feccd32798ca700fb958",
"head_short_sha": "089d92cb",
},
},
expectedErr: nil,
@@ -273,102 +253,3 @@ func TestPullRequestGetSecretRef(t *testing.T) {
})
}
}
func TestAllowedSCMProviderPullRequest(t *testing.T) {
cases := []struct {
name string
providerConfig *argoprojiov1alpha1.PullRequestGenerator
expectedError error
}{
{
name: "Error Github",
providerConfig: &argoprojiov1alpha1.PullRequestGenerator{
Github: &argoprojiov1alpha1.PullRequestGeneratorGithub{
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: &ErrDisallowedSCMProvider{},
},
{
name: "Error Gitlab",
providerConfig: &argoprojiov1alpha1.PullRequestGenerator{
GitLab: &argoprojiov1alpha1.PullRequestGeneratorGitLab{
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: &ErrDisallowedSCMProvider{},
},
{
name: "Error Gitea",
providerConfig: &argoprojiov1alpha1.PullRequestGenerator{
Gitea: &argoprojiov1alpha1.PullRequestGeneratorGitea{
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: &ErrDisallowedSCMProvider{},
},
{
name: "Error Bitbucket",
providerConfig: &argoprojiov1alpha1.PullRequestGenerator{
BitbucketServer: &argoprojiov1alpha1.PullRequestGeneratorBitbucketServer{
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: &ErrDisallowedSCMProvider{},
},
}
for _, testCase := range cases {
testCaseCopy := testCase
t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel()
pullRequestGenerator := NewPullRequestGenerator(nil, SCMAuthProviders{}, "", []string{
"github.myorg.com",
"gitlab.myorg.com",
"gitea.myorg.com",
"bitbucket.myorg.com",
"azuredevops.myorg.com",
}, true)
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
PullRequest: testCaseCopy.providerConfig,
}},
},
}
_, err := pullRequestGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
assert.Error(t, err, "Must return an error")
assert.ErrorAs(t, err, testCaseCopy.expectedError)
})
}
}
func TestSCMProviderDisabled_PRGenerator(t *testing.T) {
generator := NewPullRequestGenerator(nil, SCMAuthProviders{}, "", []string{}, false)
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)
assert.ErrorIs(t, err, ErrSCMProvidersDisabled)
}

View File

@@ -2,7 +2,6 @@ package generators
import (
"context"
"errors"
"fmt"
"strings"
"time"
@@ -10,12 +9,9 @@ import (
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v2/applicationset/services/github_app_auth"
"github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
"github.com/argoproj/argo-cd/v2/common"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
@@ -30,28 +26,22 @@ type SCMProviderGenerator struct {
// Testing hooks.
overrideProvider scm_provider.SCMProviderService
SCMAuthProviders
scmRootCAPath string
allowedSCMProviders []string
enableSCMProviders bool
}
type SCMAuthProviders struct {
GitHubApps github_app_auth.Credentials
}
func NewSCMProviderGenerator(client client.Client, providers SCMAuthProviders, scmRootCAPath string, allowedSCMProviders []string, enableSCMProviders bool) Generator {
func NewSCMProviderGenerator(client client.Client, providers SCMAuthProviders) Generator {
return &SCMProviderGenerator{
client: client,
SCMAuthProviders: providers,
scmRootCAPath: scmRootCAPath,
allowedSCMProviders: allowedSCMProviders,
enableSCMProviders: enableSCMProviders,
client: client,
SCMAuthProviders: providers,
}
}
// Testing generator
func NewTestSCMProviderGenerator(overrideProvider scm_provider.SCMProviderService) Generator {
return &SCMProviderGenerator{overrideProvider: overrideProvider, enableSCMProviders: true}
return &SCMProviderGenerator{overrideProvider: overrideProvider}
}
func (g *SCMProviderGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
@@ -68,46 +58,6 @@ 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()
if url == "" || len(allowedScmProviders) == 0 {
return nil
}
for _, allowedScmProvider := range allowedScmProviders {
if url == allowedScmProvider {
return nil
}
}
log.WithFields(log.Fields{
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, ", "))
return NewErrDisallowedSCMProvider(url, allowedScmProviders)
}
func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
if appSetGenerator == nil {
return nil, EmptyAppSetGeneratorError
@@ -117,18 +67,10 @@ 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
@@ -143,7 +85,7 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
if err != nil {
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)
provider, err = scm_provider.NewGitlabProvider(ctx, providerConfig.Gitlab.Group, token, providerConfig.Gitlab.API, providerConfig.Gitlab.AllBranches, providerConfig.Gitlab.IncludeSubgroups)
if err != nil {
return nil, fmt.Errorf("error initializing Gitlab service: %v", err)
}
@@ -189,12 +131,6 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
if err != nil {
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: %v", awsErr)
}
} else {
return nil, fmt.Errorf("no SCM provider implementation configured")
}
@@ -204,40 +140,26 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
if err != nil {
return nil, fmt.Errorf("error listing repos: %v", err)
}
paramsArray := make([]map[string]interface{}, 0, len(repos))
params := make([]map[string]interface{}, 0, len(repos))
var shortSHALength int
var shortSHALength7 int
for _, repo := range repos {
shortSHALength = 8
if len(repo.SHA) < 8 {
shortSHALength = len(repo.SHA)
}
shortSHALength7 = 7
if len(repo.SHA) < 7 {
shortSHALength7 = len(repo.SHA)
}
params := map[string]interface{}{
params = append(params, map[string]interface{}{
"organization": repo.Organization,
"repository": repo.Repository,
"url": repo.URL,
"branch": repo.Branch,
"sha": repo.SHA,
"short_sha": repo.SHA[:shortSHALength],
"short_sha_7": repo.SHA[:shortSHALength7],
"labels": strings.Join(repo.Labels, ","),
"branchNormalized": utils.SanitizeName(repo.Branch),
}
err := appendTemplatedValues(appSetGenerator.SCMProvider.Values, params, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions)
if err != nil {
return nil, fmt.Errorf("failed to append templated values: %w", err)
}
paramsArray = append(paramsArray, params)
})
}
return paramsArray, nil
return params, nil
}
func (g *SCMProviderGenerator) getSecretRef(ctx context.Context, ref *argoprojiov1alpha1.SecretRef, namespace string) (string, error) {

View File

@@ -80,234 +80,38 @@ func TestSCMProviderGetSecretRef(t *testing.T) {
}
func TestSCMProviderGenerateParams(t *testing.T) {
cases := []struct {
name string
repos []*scm_provider.Repository
values map[string]string
expected []map[string]interface{}
expectedError error
}{
{
name: "Multiple repos with labels",
repos: []*scm_provider.Repository{
{
Organization: "myorg",
Repository: "repo1",
URL: "git@github.com:myorg/repo1.git",
Branch: "main",
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
Labels: []string{"prod", "staging"},
},
{
Organization: "myorg",
Repository: "repo2",
URL: "git@github.com:myorg/repo2.git",
Branch: "main",
SHA: "59d0",
},
mockProvider := &scm_provider.MockProvider{
Repos: []*scm_provider.Repository{
{
Organization: "myorg",
Repository: "repo1",
URL: "git@github.com:myorg/repo1.git",
Branch: "main",
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
Labels: []string{"prod", "staging"},
},
expected: []map[string]interface{}{
{
"organization": "myorg",
"repository": "repo1",
"url": "git@github.com:myorg/repo1.git",
"branch": "main",
"branchNormalized": "main",
"sha": "0bc57212c3cbbec69d20b34c507284bd300def5b",
"short_sha": "0bc57212",
"short_sha_7": "0bc5721",
"labels": "prod,staging",
},
{
"organization": "myorg",
"repository": "repo2",
"url": "git@github.com:myorg/repo2.git",
"branch": "main",
"branchNormalized": "main",
"sha": "59d0",
"short_sha": "59d0",
"short_sha_7": "59d0",
"labels": "",
},
},
},
{
name: "Value interpolation",
repos: []*scm_provider.Repository{
{
Organization: "myorg",
Repository: "repo3",
URL: "git@github.com:myorg/repo3.git",
Branch: "main",
SHA: "0bc57212c3cbbec69d20b34c507284bd300def5b",
Labels: []string{"prod", "staging"},
},
},
values: map[string]string{
"foo": "bar",
"should_i_force_push_to": "{{ branch }}?",
},
expected: []map[string]interface{}{
{
"organization": "myorg",
"repository": "repo3",
"url": "git@github.com:myorg/repo3.git",
"branch": "main",
"branchNormalized": "main",
"sha": "0bc57212c3cbbec69d20b34c507284bd300def5b",
"short_sha": "0bc57212",
"short_sha_7": "0bc5721",
"labels": "prod,staging",
"values.foo": "bar",
"values.should_i_force_push_to": "main?",
},
{
Organization: "myorg",
Repository: "repo2",
URL: "git@github.com:myorg/repo2.git",
Branch: "main",
SHA: "59d0",
},
},
}
for _, testCase := range cases {
testCaseCopy := testCase
t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel()
mockProvider := &scm_provider.MockProvider{
Repos: testCaseCopy.repos,
}
scmGenerator := &SCMProviderGenerator{overrideProvider: mockProvider, enableSCMProviders: true}
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
SCMProvider: &argoprojiov1alpha1.SCMProviderGenerator{
Values: testCaseCopy.values,
},
}},
},
}
got, err := scmGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
if testCaseCopy.expectedError != nil {
assert.EqualError(t, err, testCaseCopy.expectedError.Error())
} else {
assert.NoError(t, err)
assert.Equal(t, testCaseCopy.expected, got)
}
})
}
}
func TestAllowedSCMProvider(t *testing.T) {
cases := []struct {
name string
providerConfig *argoprojiov1alpha1.SCMProviderGenerator
expectedError error
}{
{
name: "Error Github",
providerConfig: &argoprojiov1alpha1.SCMProviderGenerator{
Github: &argoprojiov1alpha1.SCMProviderGeneratorGithub{
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: &ErrDisallowedSCMProvider{},
},
{
name: "Error Gitlab",
providerConfig: &argoprojiov1alpha1.SCMProviderGenerator{
Gitlab: &argoprojiov1alpha1.SCMProviderGeneratorGitlab{
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: &ErrDisallowedSCMProvider{},
},
{
name: "Error Gitea",
providerConfig: &argoprojiov1alpha1.SCMProviderGenerator{
Gitea: &argoprojiov1alpha1.SCMProviderGeneratorGitea{
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: &ErrDisallowedSCMProvider{},
},
{
name: "Error Bitbucket",
providerConfig: &argoprojiov1alpha1.SCMProviderGenerator{
BitbucketServer: &argoprojiov1alpha1.SCMProviderGeneratorBitbucketServer{
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: &ErrDisallowedSCMProvider{},
},
{
name: "Error AzureDevops",
providerConfig: &argoprojiov1alpha1.SCMProviderGenerator{
AzureDevOps: &argoprojiov1alpha1.SCMProviderGeneratorAzureDevOps{
API: "https://myservice.mynamespace.svc.cluster.local",
},
},
expectedError: &ErrDisallowedSCMProvider{},
},
}
for _, testCase := range cases {
testCaseCopy := testCase
t.Run(testCaseCopy.name, func(t *testing.T) {
t.Parallel()
scmGenerator := &SCMProviderGenerator{
allowedSCMProviders: []string{
"github.myorg.com",
"gitlab.myorg.com",
"gitea.myorg.com",
"bitbucket.myorg.com",
"azuredevops.myorg.com",
},
enableSCMProviders: true,
}
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
SCMProvider: testCaseCopy.providerConfig,
}},
},
}
_, err := scmGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
assert.Error(t, err, "Must return an error")
assert.ErrorAs(t, err, testCaseCopy.expectedError)
})
}
}
func TestSCMProviderDisabled_SCMGenerator(t *testing.T) {
generator := &SCMProviderGenerator{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)
assert.ErrorIs(t, err, ErrSCMProvidersDisabled)
gen := &SCMProviderGenerator{overrideProvider: mockProvider}
params, err := gen.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
SCMProvider: &argoprojiov1alpha1.SCMProviderGenerator{},
}, nil)
assert.Nil(t, err)
assert.Len(t, params, 2)
assert.Equal(t, "myorg", params[0]["organization"])
assert.Equal(t, "repo1", params[0]["repository"])
assert.Equal(t, "git@github.com:myorg/repo1.git", params[0]["url"])
assert.Equal(t, "main", params[0]["branch"])
assert.Equal(t, "0bc57212c3cbbec69d20b34c507284bd300def5b", params[0]["sha"])
assert.Equal(t, "0bc57212", params[0]["short_sha"])
assert.Equal(t, "59d0", params[1]["short_sha"])
assert.Equal(t, "prod,staging", params[0]["labels"])
assert.Equal(t, "repo2", params[1]["repository"])
}

View File

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

View File

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

View File

@@ -1,125 +0,0 @@
package generators
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestValueInterpolation(t *testing.T) {
testCases := []struct {
name string
values map[string]string
params map[string]interface{}
expected map[string]interface{}
}{
{
name: "Simple interpolation",
values: map[string]string{
"hello": "{{ world }}",
},
params: map[string]interface{}{
"world": "world!",
},
expected: map[string]interface{}{
"world": "world!",
"values.hello": "world!",
},
},
{
name: "Non-existent",
values: map[string]string{
"non-existent": "{{ non-existent }}",
},
params: map[string]interface{}{},
expected: map[string]interface{}{
"values.non-existent": "{{ non-existent }}",
},
},
{
name: "Billion laughs",
values: map[string]string{
"lol1": "lol",
"lol2": "{{values.lol1}}{{values.lol1}}",
"lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}",
},
params: map[string]interface{}{},
expected: map[string]interface{}{
"values.lol1": "lol",
"values.lol2": "{{values.lol1}}{{values.lol1}}",
"values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}",
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
err := appendTemplatedValues(testCase.values, testCase.params, false, nil)
assert.NoError(t, err)
assert.EqualValues(t, testCase.expected, testCase.params)
})
}
}
func TestValueInterpolationWithGoTemplating(t *testing.T) {
testCases := []struct {
name string
values map[string]string
params map[string]interface{}
expected map[string]interface{}
}{
{
name: "Simple interpolation",
values: map[string]string{
"hello": "{{ .world }}",
},
params: map[string]interface{}{
"world": "world!",
},
expected: map[string]interface{}{
"world": "world!",
"values": map[string]string{
"hello": "world!",
},
},
},
{
name: "Non-existent to default",
values: map[string]string{
"non_existent": "{{ default \"bar\" .non_existent }}",
},
params: map[string]interface{}{},
expected: map[string]interface{}{
"values": map[string]string{
"non_existent": "bar",
},
},
},
{
name: "Billion laughs",
values: map[string]string{
"lol1": "lol",
"lol2": "{{.values.lol1}}{{.values.lol1}}",
"lol3": "{{.values.lol2}}{{.values.lol2}}{{.values.lol2}}",
},
params: map[string]interface{}{},
expected: map[string]interface{}{
"values": map[string]string{
"lol1": "lol",
"lol2": "<no value><no value>",
"lol3": "<no value><no value><no value>",
},
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
err := appendTemplatedValues(testCase.values, testCase.params, true, nil)
assert.NoError(t, err)
assert.EqualValues(t, testCase.expected, testCase.params)
})
}
}

View File

@@ -1,161 +0,0 @@
package http
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
)
const (
userAgent = "argocd-applicationset"
defaultTimeout = 30
)
type Client struct {
// URL is the URL used for API requests.
baseURL string
// UserAgent is the user agent to include in HTTP requests.
UserAgent string
// Token is used to make authenticated API calls.
token string
// Client is an HTTP client used to communicate with the API.
client *http.Client
}
type ErrorResponse struct {
Body []byte
Response *http.Response
Message string
}
func NewClient(baseURL string, options ...ClientOptionFunc) (*Client, error) {
client, err := newClient(baseURL, options...)
if err != nil {
return nil, err
}
return client, nil
}
func newClient(baseURL string, options ...ClientOptionFunc) (*Client, error) {
c := &Client{baseURL: baseURL, UserAgent: userAgent}
// Configure the HTTP client.
c.client = &http.Client{
Timeout: time.Duration(defaultTimeout) * time.Second,
}
// Apply any given client options.
for _, fn := range options {
if fn == nil {
continue
}
if err := fn(c); err != nil {
return nil, err
}
}
return c, nil
}
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 += "/"
}
var buf io.ReadWriter
if body != nil {
buf = &bytes.Buffer{}
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
err := enc.Encode(body)
if err != nil {
return nil, err
}
}
req, err := http.NewRequest(method, c.baseURL+path, buf)
if err != nil {
return nil, err
}
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
if len(c.token) != 0 {
req.Header.Set("Authorization", "Bearer "+c.token)
}
if c.UserAgent != "" {
req.Header.Set("User-Agent", c.UserAgent)
}
return req, nil
}
func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*http.Response, error) {
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if err := CheckResponse(resp); err != nil {
return resp, err
}
switch v := v.(type) {
case nil:
case io.Writer:
_, err = io.Copy(v, resp.Body)
default:
buf := new(bytes.Buffer)
teeReader := io.TeeReader(resp.Body, buf)
decErr := json.NewDecoder(teeReader).Decode(v)
if decErr == io.EOF {
decErr = nil // ignore EOF errors caused by empty response body
}
if decErr != nil {
err = fmt.Errorf("%s: %s", decErr.Error(), buf.String())
}
}
return resp, err
}
// 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: %v", resp.StatusCode, err)
}
var raw map[string]interface{}
if err := json.Unmarshal(data, &raw); err != nil {
return fmt.Errorf("API error with status code %d: %s", resp.StatusCode, string(data))
}
message := ""
if value, ok := raw["message"].(string); ok {
message = value
} else if value, ok := raw["error"].(string); ok {
message = value
}
return fmt.Errorf("API error with status code %d: %s", resp.StatusCode, message)
}

View File

@@ -1,22 +0,0 @@
package http
import "time"
// ClientOptionFunc can be used to customize a new Restful API client.
type ClientOptionFunc func(*Client) error
// WithToken is an option for NewClient to set token
func WithToken(token string) ClientOptionFunc {
return func(c *Client) error {
c.token = token
return nil
}
}
// WithTimeout can be used to configure a custom timeout for requests.
func WithTimeout(timeout int) ClientOptionFunc {
return func(c *Client) error {
c.client.Timeout = time.Duration(timeout) * time.Second
return nil
}
}

View File

@@ -1,163 +0,0 @@
package http
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestClient(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte("Hello, World!"))
if err != nil {
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)
}
}
func TestClientDo(t *testing.T) {
ctx := context.Background()
for _, c := range []struct {
name string
params map[string]string
content []byte
fakeServer *httptest.Server
clientOptionFns []ClientOptionFunc
expected []map[string]interface{}
expectedCode int
expectedError error
}{
{
name: "Simple",
params: map[string]string{
"pkey1": "val1",
"pkey2": "val2",
},
fakeServer: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte(`[{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123
}]`))
if err != nil {
assert.NoError(t, fmt.Errorf("Error Write %v", err))
}
})),
clientOptionFns: nil,
expected: []map[string]interface{}{
{
"key1": "val1",
"key2": map[string]interface{}{
"key2_1": "val2_1",
"key2_2": map[string]interface{}{
"key2_2_1": "val2_2_1",
},
},
"key3": float64(123),
},
},
expectedCode: 200,
expectedError: nil,
},
{
name: "With Token",
params: map[string]string{
"pkey1": "val1",
"pkey2": "val2",
},
fakeServer: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader != "Bearer "+string("test-token") {
w.WriteHeader(http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte(`[{
"key1": "val1",
"key2": {
"key2_1": "val2_1",
"key2_2": {
"key2_2_1": "val2_2_1"
}
},
"key3": 123
}]`))
if err != nil {
assert.NoError(t, fmt.Errorf("Error Write %v", err))
}
})),
clientOptionFns: nil,
expected: []map[string]interface{}(nil),
expectedCode: 401,
expectedError: fmt.Errorf("API error with status code 401: "),
},
} {
cc := c
t.Run(cc.name, func(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)
}
var data []map[string]interface{}
resp, err := client.Do(ctx, req, &data)
if cc.expectedError != nil {
assert.EqualError(t, err, cc.expectedError.Error())
} else {
assert.Equal(t, resp.StatusCode, cc.expectedCode)
assert.Equal(t, data, cc.expected)
assert.NoError(t, err)
}
})
}
}
func TestCheckResponse(t *testing.T) {
resp := &http.Response{
StatusCode: http.StatusBadRequest,
Body: io.NopCloser(bytes.NewBufferString(`{"error":"invalid_request","description":"Invalid token"}`)),
}
err := CheckResponse(resp)
if err == nil {
t.Error("Expected an error, got nil")
}
expected := "API error with status code 400: invalid_request"
if err.Error() != expected {
t.Errorf("Expected error '%s', got '%s'", expected, err.Error())
}
}

View File

@@ -1,81 +0,0 @@
// Code generated by mockery v2.25.1. DO NOT EDIT.
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
)
// Repos is an autogenerated mock type for the Repos type
type Repos struct {
mock.Mock
}
// GetDirectories provides a mock function with given fields: ctx, repoURL, revision
func (_m *Repos) GetDirectories(ctx context.Context, repoURL string, revision string) ([]string, error) {
ret := _m.Called(ctx, repoURL, revision)
var r0 []string
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]string, error)); ok {
return rf(ctx, repoURL, revision)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string) []string); ok {
r0 = rf(ctx, repoURL, revision)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
r1 = rf(ctx, repoURL, revision)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetFiles provides a mock function with given fields: ctx, repoURL, revision, pattern
func (_m *Repos) GetFiles(ctx context.Context, repoURL string, revision string, pattern string) (map[string][]byte, error) {
ret := _m.Called(ctx, repoURL, revision, pattern)
var r0 map[string][]byte
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (map[string][]byte, error)); ok {
return rf(ctx, repoURL, revision, pattern)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) map[string][]byte); ok {
r0 = rf(ctx, repoURL, revision, pattern)
} 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) error); ok {
r1 = rf(ctx, repoURL, revision, pattern)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
type mockConstructorTestingTNewRepos interface {
mock.TestingT
Cleanup(func())
}
// 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)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@@ -1,57 +0,0 @@
// 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

@@ -1,73 +0,0 @@
package plugin
import (
"context"
"fmt"
"net/http"
internalhttp "github.com/argoproj/argo-cd/v2/applicationset/services/internal/http"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
// ServiceRequest is the request object sent to the plugin service.
type ServiceRequest struct {
// ApplicationSetName is the appSetName of the ApplicationSet for which we're requesting parameters. Useful for logging in
// the plugin service.
ApplicationSetName string `json:"applicationSetName"`
// Input is the map of parameters set in the ApplicationSet spec for this generator.
Input v1alpha1.PluginInput `json:"input"`
}
type Output struct {
// Parameters is the list of parameter sets returned by the plugin.
Parameters []map[string]interface{} `json:"parameters"`
}
// ServiceResponse is the response object returned by the plugin service.
type ServiceResponse struct {
// Output is the map of outputs returned by the plugin.
Output Output `json:"output"`
}
type Service struct {
client *internalhttp.Client
appSetName string
}
func NewPluginService(ctx context.Context, appSetName string, baseURL string, token string, requestTimeout int) (*Service, error) {
var clientOptionFns []internalhttp.ClientOptionFunc
clientOptionFns = append(clientOptionFns, internalhttp.WithToken(token))
if requestTimeout != 0 {
clientOptionFns = append(clientOptionFns, internalhttp.WithTimeout(requestTimeout))
}
client, err := internalhttp.NewClient(baseURL, clientOptionFns...)
if err != nil {
return nil, fmt.Errorf("error creating plugin client: %v", err)
}
return &Service{
client: client,
appSetName: appSetName,
}, nil
}
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: %v", err)
}
var data ServiceResponse
_, err = p.client.Do(ctx, req, &data)
if err != nil {
return nil, fmt.Errorf("error get api '%s': %v", p.appSetName, err)
}
return &data, err
}

View File

@@ -1,52 +0,0 @@
package plugin
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestPlugin(t *testing.T) {
expectedJSON := `{"parameters": [{"number":123,"digest":"sha256:942ae2dfd73088b54d7151a3c3fd5af038a51c50029bfcfd21f1e650d9579967"},{"number":456,"digest":"sha256:224e68cc69566e5cbbb76034b3c42cd2ed57c1a66720396e1c257794cb7d68c1"}]}`
token := "0bc57212c3cbbec69d20b34c507284bd300def5b"
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
authHeader := r.Header.Get("Authorization")
if authHeader != "Bearer "+token {
w.WriteHeader(http.StatusUnauthorized)
return
}
_, err := w.Write([]byte(expectedJSON))
if err != nil {
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)
}
var expectedData ServiceResponse
err = json.Unmarshal([]byte(expectedJSON), &expectedData)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, &expectedData, data)
}

View File

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

View File

@@ -1,17 +0,0 @@
package plugin
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseSecretKey(t *testing.T) {
secretName, tokenKey := ParseSecretKey("#my-secret:my-token")
assert.Equal(t, "my-secret", secretName)
assert.Equal(t, "$my-token", tokenKey)
secretName, tokenKey = ParseSecretKey("#my-secret")
assert.Equal(t, "argocd-secret", secretName)
assert.Equal(t, "#my-secret", tokenKey)
}

View File

@@ -1,145 +0,0 @@
package pull_request
import (
"context"
"fmt"
"strings"
"github.com/microsoft/azure-devops-go-api/azuredevops"
core "github.com/microsoft/azure-devops-go-api/azuredevops/core"
git "github.com/microsoft/azure-devops-go-api/azuredevops/git"
)
const AZURE_DEVOPS_DEFAULT_URL = "https://dev.azure.com"
type AzureDevOpsClientFactory interface {
// Returns an Azure Devops Client interface.
GetClient(ctx context.Context) (git.Client, error)
}
type devopsFactoryImpl struct {
connection *azuredevops.Connection
}
func (factory *devopsFactoryImpl) GetClient(ctx context.Context) (git.Client, error) {
gitClient, err := git.NewClient(ctx, factory.connection)
if err != nil {
return nil, fmt.Errorf("failed to get new Azure DevOps git client for pull request generator: %w", err)
}
return gitClient, nil
}
type AzureDevOpsService struct {
clientFactory AzureDevOpsClientFactory
project string
repo string
labels []string
}
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)
var connection *azuredevops.Connection
if token == "" {
connection = azuredevops.NewAnonymousConnection(organizationUrl)
} else {
connection = azuredevops.NewPatConnection(organizationUrl, token)
}
return &AzureDevOpsService{
clientFactory: &devopsFactoryImpl{connection: connection},
project: project,
repo: repo,
labels: labels,
}, nil
}
func (a *AzureDevOpsService) List(ctx context.Context) ([]*PullRequest, error) {
client, err := a.clientFactory.GetClient(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get Azure DevOps client: %w", err)
}
args := git.GetPullRequestsByProjectArgs{
Project: &a.project,
SearchCriteria: &git.GitPullRequestSearchCriteria{},
}
azurePullRequests, err := client.GetPullRequestsByProject(ctx, args)
if err != nil {
return nil, fmt.Errorf("failed to get pull requests by project: %w", err)
}
pullRequests := []*PullRequest{}
for _, pr := range *azurePullRequests {
if pr.Repository == nil ||
pr.Repository.Name == nil ||
pr.PullRequestId == nil ||
pr.SourceRefName == nil ||
pr.LastMergeSourceCommit == nil ||
pr.LastMergeSourceCommit.CommitId == nil {
continue
}
azureDevOpsLabels := convertLabels(pr.Labels)
if !containAzureDevOpsLabels(a.labels, azureDevOpsLabels) {
continue
}
if *pr.Repository.Name == a.repo {
pullRequests = append(pullRequests, &PullRequest{
Number: *pr.PullRequestId,
Branch: strings.Replace(*pr.SourceRefName, "refs/heads/", "", 1),
HeadSHA: *pr.LastMergeSourceCommit.CommitId,
Labels: azureDevOpsLabels,
})
}
}
return pullRequests, nil
}
// convertLabels converts WebApiTagDefinitions to strings
func convertLabels(tags *[]core.WebApiTagDefinition) []string {
if tags == nil {
return []string{}
}
labelStrings := make([]string, len(*tags))
for i, label := range *tags {
labelStrings[i] = *label.Name
}
return labelStrings
}
// containAzureDevOpsLabels returns true if gotLabels contains expectedLabels
func containAzureDevOpsLabels(expectedLabels []string, gotLabels []string) bool {
for _, expected := range expectedLabels {
found := false
for _, got := range gotLabels {
if expected == got {
found = true
break
}
}
if !found {
return false
}
}
return true
}
func buildURL(url, organization string) string {
if url == "" {
url = AZURE_DEVOPS_DEFAULT_URL
}
separator := ""
if !strings.HasSuffix(url, "/") {
separator = "/"
}
devOpsURL := fmt.Sprintf("%s%s%s", url, separator, organization)
return devOpsURL
}

View File

@@ -1,221 +0,0 @@
package pull_request
import (
"context"
"testing"
"github.com/microsoft/azure-devops-go-api/azuredevops/core"
git "github.com/microsoft/azure-devops-go-api/azuredevops/git"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
azureMock "github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider/azure_devops/git/mocks"
)
func createBoolPtr(x bool) *bool {
return &x
}
func createStringPtr(x string) *string {
return &x
}
func createIntPtr(x int) *int {
return &x
}
func createLabelsPtr(x []core.WebApiTagDefinition) *[]core.WebApiTagDefinition {
return &x
}
type AzureClientFactoryMock struct {
mock *mock.Mock
}
func (m *AzureClientFactoryMock) GetClient(ctx context.Context) (git.Client, error) {
args := m.mock.Called(ctx)
var client git.Client
c := args.Get(0)
if c != nil {
client = c.(git.Client)
}
var err error
if len(args) > 1 {
if e, ok := args.Get(1).(error); ok {
err = e
}
}
return client, err
}
func TestListPullRequest(t *testing.T) {
teamProject := "myorg_project"
repoName := "myorg_project_repo"
pr_id := 123
pr_head_sha := "cd4973d9d14a08ffe6b641a89a68891d6aac8056"
ctx := context.Background()
pullRequestMock := []git.GitPullRequest{
{
PullRequestId: createIntPtr(pr_id),
SourceRefName: createStringPtr("refs/heads/feature-branch"),
LastMergeSourceCommit: &git.GitCommitRef{
CommitId: createStringPtr(pr_head_sha),
},
Labels: &[]core.WebApiTagDefinition{},
Repository: &git.GitRepository{
Name: createStringPtr(repoName),
},
},
}
args := git.GetPullRequestsByProjectArgs{
Project: &teamProject,
SearchCriteria: &git.GitPullRequestSearchCriteria{},
}
gitClientMock := azureMock.Client{}
clientFactoryMock := &AzureClientFactoryMock{mock: &mock.Mock{}}
clientFactoryMock.mock.On("GetClient", mock.Anything).Return(&gitClientMock, nil)
gitClientMock.On("GetPullRequestsByProject", ctx, args).Return(&pullRequestMock, nil)
provider := AzureDevOpsService{
clientFactory: clientFactoryMock,
project: teamProject,
repo: repoName,
labels: nil,
}
list, err := provider.List(ctx)
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)
}
func TestConvertLabes(t *testing.T) {
testCases := []struct {
name string
gotLabels *[]core.WebApiTagDefinition
expectedLabels []string
}{
{
name: "empty labels",
gotLabels: createLabelsPtr([]core.WebApiTagDefinition{}),
expectedLabels: []string{},
},
{
name: "nil labels",
gotLabels: createLabelsPtr(nil),
expectedLabels: []string{},
},
{
name: "one label",
gotLabels: createLabelsPtr([]core.WebApiTagDefinition{
{Name: createStringPtr("label1"), Active: createBoolPtr(true)},
}),
expectedLabels: []string{"label1"},
},
{
name: "two label",
gotLabels: createLabelsPtr([]core.WebApiTagDefinition{
{Name: createStringPtr("label1"), Active: createBoolPtr(true)},
{Name: createStringPtr("label2"), Active: createBoolPtr(true)},
}),
expectedLabels: []string{"label1", "label2"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := convertLabels(tc.gotLabels)
assert.Equal(t, tc.expectedLabels, got)
})
}
}
func TestContainAzureDevOpsLabels(t *testing.T) {
testCases := []struct {
name string
expectedLabels []string
gotLabels []string
expectedResult bool
}{
{
name: "empty labels",
expectedLabels: []string{},
gotLabels: []string{},
expectedResult: true,
},
{
name: "no matching labels",
expectedLabels: []string{"label1", "label2"},
gotLabels: []string{"label3", "label4"},
expectedResult: false,
},
{
name: "some matching labels",
expectedLabels: []string{"label1", "label2"},
gotLabels: []string{"label1", "label3"},
expectedResult: false,
},
{
name: "all matching labels",
expectedLabels: []string{"label1", "label2"},
gotLabels: []string{"label1", "label2"},
expectedResult: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := containAzureDevOpsLabels(tc.expectedLabels, tc.gotLabels)
assert.Equal(t, tc.expectedResult, got)
})
}
}
func TestBuildURL(t *testing.T) {
testCases := []struct {
name string
url string
organization string
expected string
}{
{
name: "Provided default URL and organization",
url: "https://dev.azure.com/",
organization: "myorganization",
expected: "https://dev.azure.com/myorganization",
},
{
name: "Provided default URL and organization without trailing slash",
url: "https://dev.azure.com",
organization: "myorganization",
expected: "https://dev.azure.com/myorganization",
},
{
name: "Provided no URL and organization",
url: "",
organization: "myorganization",
expected: "https://dev.azure.com/myorganization",
},
{
name: "Provided custom URL and organization",
url: "https://azuredevops.mycompany.com/",
organization: "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, result, tc.expected)
})
}
}

View File

@@ -1,138 +0,0 @@
package pull_request
import (
"context"
"encoding/json"
"fmt"
"net/url"
"github.com/ktrysmt/go-bitbucket"
)
type BitbucketCloudService struct {
client *bitbucket.Client
owner string
repositorySlug string
}
type BitbucketCloudPullRequest struct {
ID int `json:"id"`
Source BitbucketCloudPullRequestSource `json:"source"`
}
type BitbucketCloudPullRequestSource struct {
Branch BitbucketCloudPullRequestSourceBranch `json:"branch"`
Commit BitbucketCloudPullRequestSourceCommit `json:"commit"`
}
type BitbucketCloudPullRequestSourceBranch struct {
Name string `json:"name"`
}
type BitbucketCloudPullRequestSourceCommit struct {
Hash string `json:"hash"`
}
type PullRequestResponse struct {
Page int32 `json:"page"`
Size int32 `json:"size"`
Pagelen int32 `json:"pagelen"`
Next string `json:"next"`
Previous string `json:"previous"`
Items []PullRequest `json:"values"`
}
var _ PullRequestService = (*BitbucketCloudService)(nil)
func parseUrl(uri string) (*url.URL, error) {
if uri == "" {
uri = "https://api.bitbucket.org/2.0"
}
url, err := url.Parse(uri)
if err != nil {
return nil, err
}
return url, nil
}
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: %v", baseUrl, owner, repositorySlug, err)
}
bitbucketClient := bitbucket.NewBasicAuth(username, password)
bitbucketClient.SetApiBaseURL(*url)
return &BitbucketCloudService{
client: bitbucketClient,
owner: owner,
repositorySlug: repositorySlug,
}, nil
}
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: %v", baseUrl, owner, repositorySlug, err)
}
bitbucketClient := bitbucket.NewOAuthbearerToken(bearerToken)
bitbucketClient.SetApiBaseURL(*url)
return &BitbucketCloudService{
client: bitbucketClient,
owner: owner,
repositorySlug: repositorySlug,
}, nil
}
func NewBitbucketCloudServiceNoAuth(baseUrl, owner, repositorySlug string) (PullRequestService, error) {
// There is currently no method to explicitly not require auth
return NewBitbucketCloudServiceBearerToken(baseUrl, "", owner, repositorySlug)
}
func (b *BitbucketCloudService) List(_ context.Context) ([]*PullRequest, error) {
opts := &bitbucket.PullRequestsOptions{
Owner: b.owner,
RepoSlug: b.repositorySlug,
}
response, err := b.client.Repositories.PullRequests.Gets(opts)
if err != nil {
return nil, fmt.Errorf("error listing pull requests for %s/%s: %v", b.owner, b.repositorySlug, err)
}
resp, ok := response.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("unknown type returned from bitbucket pull requests")
}
repoArray, ok := resp["values"].([]interface{})
if !ok {
return nil, fmt.Errorf("unknown type returned from response values")
}
jsonStr, err := json.Marshal(repoArray)
if err != nil {
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': %v", err)
}
pullRequests := []*PullRequest{}
for _, pull := range pulls {
pullRequests = append(pullRequests, &PullRequest{
Number: pull.ID,
Branch: pull.Source.Branch.Name,
HeadSHA: pull.Source.Commit.Hash,
})
}
return pullRequests, nil
}

View File

@@ -1,410 +0,0 @@
package pull_request
import (
"context"
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
func defaultHandlerCloud(t *testing.T) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var err error
switch r.RequestURI {
case "/repositories/OWNER/REPO/pullrequests/":
_, err = io.WriteString(w, `{
"size": 1,
"pagelen": 10,
"page": 1,
"values": [
{
"id": 101,
"source": {
"branch": {
"name": "feature/foo-bar"
},
"commit": {
"type": "commit",
"hash": "1a8dd249c04a"
}
}
}
]
}`)
default:
t.Fail()
}
if err != nil {
t.Fail()
}
}
}
func TestParseUrlEmptyUrl(t *testing.T) {
url, err := parseUrl("")
bitbucketUrl, _ := url.Parse("https://api.bitbucket.org/2.0")
assert.NoError(t, err)
assert.Equal(t, bitbucketUrl, url)
}
func TestInvalidBaseUrlBasicAuthCloud(t *testing.T) {
_, err := NewBitbucketCloudServiceBasicAuth("http:// example.org", "user", "password", "OWNER", "REPO")
assert.Error(t, err)
}
func TestInvalidBaseUrlBearerTokenCloud(t *testing.T) {
_, err := NewBitbucketCloudServiceBearerToken("http:// example.org", "TOKEN", "OWNER", "REPO")
assert.Error(t, err)
}
func TestInvalidBaseUrlNoAuthCloud(t *testing.T) {
_, err := NewBitbucketCloudServiceNoAuth("http:// example.org", "OWNER", "REPO")
assert.Error(t, err)
}
func TestListPullRequestBearerTokenCloud(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "Bearer TOKEN", r.Header.Get("Authorization"))
defaultHandlerCloud(t)(w, r)
}))
defer ts.Close()
svc, err := NewBitbucketCloudServiceBearerToken(ts.URL, "TOKEN", "OWNER", "REPO")
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
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)
}
func TestListPullRequestNoAuthCloud(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Empty(t, r.Header.Get("Authorization"))
defaultHandlerCloud(t)(w, r)
}))
defer ts.Close()
svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
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)
}
func TestListPullRequestBasicAuthCloud(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "Basic dXNlcjpwYXNzd29yZA==", r.Header.Get("Authorization"))
defaultHandlerCloud(t)(w, r)
}))
defer ts.Close()
svc, err := NewBitbucketCloudServiceBasicAuth(ts.URL, "user", "password", "OWNER", "REPO")
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
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)
}
func TestListPullRequestPaginationCloud(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var err error
switch r.RequestURI {
case "/repositories/OWNER/REPO/pullrequests/":
_, err = io.WriteString(w, fmt.Sprintf(`{
"size": 2,
"pagelen": 1,
"page": 1,
"next": "http://%s/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=2",
"values": [
{
"id": 101,
"source": {
"branch": {
"name": "feature-101"
},
"commit": {
"type": "commit",
"hash": "1a8dd249c04a"
}
}
},
{
"id": 102,
"source": {
"branch": {
"name": "feature-102"
},
"commit": {
"type": "commit",
"hash": "4cf807e67a6d"
}
}
}
]
}`, r.Host))
case "/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=2":
_, err = io.WriteString(w, fmt.Sprintf(`{
"size": 2,
"pagelen": 1,
"page": 2,
"previous": "http://%s/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=1",
"values": [
{
"id": 103,
"source": {
"branch": {
"name": "feature-103"
},
"commit": {
"type": "commit",
"hash": "6344d9623e3b"
}
}
}
]
}`, r.Host))
default:
t.Fail()
}
if err != nil {
t.Fail()
}
}))
defer ts.Close()
svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
assert.NoError(t, err)
assert.Equal(t, 3, len(pullRequests))
assert.Equal(t, PullRequest{
Number: 101,
Branch: "feature-101",
HeadSHA: "1a8dd249c04a",
}, *pullRequests[0])
assert.Equal(t, PullRequest{
Number: 102,
Branch: "feature-102",
HeadSHA: "4cf807e67a6d",
}, *pullRequests[1])
assert.Equal(t, PullRequest{
Number: 103,
Branch: "feature-103",
HeadSHA: "6344d9623e3b",
}, *pullRequests[2])
}
func TestListResponseErrorCloud(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500)
}))
defer ts.Close()
svc, _ := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
_, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
assert.Error(t, err)
}
func TestListResponseMalformedCloud(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.RequestURI {
case "/repositories/OWNER/REPO/pullrequests/":
_, err := io.WriteString(w, `[{
"size": 1,
"pagelen": 10,
"page": 1,
"values": [{ "id": 101 }]
}]`)
if err != nil {
t.Fail()
}
default:
t.Fail()
}
}))
defer ts.Close()
svc, _ := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
_, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
assert.Error(t, err)
}
func TestListResponseMalformedValuesCloud(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.RequestURI {
case "/repositories/OWNER/REPO/pullrequests/":
_, err := io.WriteString(w, `{
"size": 1,
"pagelen": 10,
"page": 1,
"values": { "id": 101 }
}`)
if err != nil {
t.Fail()
}
default:
t.Fail()
}
}))
defer ts.Close()
svc, _ := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
_, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
assert.Error(t, err)
}
func TestListResponseEmptyCloud(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.RequestURI {
case "/repositories/OWNER/REPO/pullrequests/":
_, err := io.WriteString(w, `{
"size": 1,
"pagelen": 10,
"page": 1,
"values": []
}`)
if err != nil {
t.Fail()
}
default:
t.Fail()
}
}))
defer ts.Close()
svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{})
assert.NoError(t, err)
assert.Empty(t, pullRequests)
}
func TestListPullRequestBranchMatchCloud(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var err error
switch r.RequestURI {
case "/repositories/OWNER/REPO/pullrequests/":
_, err = io.WriteString(w, fmt.Sprintf(`{
"size": 2,
"pagelen": 1,
"page": 1,
"next": "http://%s/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=2",
"values": [
{
"id": 101,
"source": {
"branch": {
"name": "feature-101"
},
"commit": {
"type": "commit",
"hash": "1a8dd249c04a"
}
}
},
{
"id": 200,
"source": {
"branch": {
"name": "feature-200"
},
"commit": {
"type": "commit",
"hash": "4cf807e67a6d"
}
}
}
]
}`, r.Host))
case "/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=2":
_, err = io.WriteString(w, fmt.Sprintf(`{
"size": 2,
"pagelen": 1,
"page": 2,
"previous": "http://%s/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=1",
"values": [
{
"id": 102,
"source": {
"branch": {
"name": "feature-102"
},
"commit": {
"type": "commit",
"hash": "6344d9623e3b"
}
}
}
]
}`, r.Host))
default:
t.Fail()
}
if err != nil {
t.Fail()
}
}))
defer ts.Close()
regexp := `feature-1[\d]{2}`
svc, err := NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
assert.NoError(t, err)
pullRequests, err := ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{
BranchMatch: &regexp,
},
})
assert.NoError(t, err)
assert.Equal(t, 2, len(pullRequests))
assert.Equal(t, PullRequest{
Number: 101,
Branch: "feature-101",
HeadSHA: "1a8dd249c04a",
}, *pullRequests[0])
assert.Equal(t, PullRequest{
Number: 102,
Branch: "feature-102",
HeadSHA: "6344d9623e3b",
}, *pullRequests[1])
regexp = `.*2$`
svc, err = NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
assert.NoError(t, err)
pullRequests, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{
BranchMatch: &regexp,
},
})
assert.NoError(t, err)
assert.Equal(t, 1, len(pullRequests))
assert.Equal(t, PullRequest{
Number: 102,
Branch: "feature-102",
HeadSHA: "6344d9623e3b",
}, *pullRequests[0])
regexp = `[\d{2}`
svc, err = NewBitbucketCloudServiceNoAuth(ts.URL, "OWNER", "REPO")
assert.NoError(t, err)
_, err = ListPullRequests(context.Background(), svc, []v1alpha1.PullRequestGeneratorFilter{
{
BranchMatch: &regexp,
},
})
assert.Error(t, err)
}

View File

@@ -66,11 +66,10 @@ func (b *BitbucketService) List(_ context.Context) ([]*PullRequest, error) {
for _, pull := range pulls {
pullRequests = append(pullRequests, &PullRequest{
Number: pull.ID,
Branch: pull.FromRef.DisplayID, // ID: refs/heads/main DisplayID: main
TargetBranch: pull.ToRef.DisplayID,
HeadSHA: pull.FromRef.LatestCommit, // This is not defined in the official docs, but works in practice
Labels: []string{}, // Not supported by library
Number: pull.ID,
Branch: pull.FromRef.DisplayID, // ID: refs/heads/main DisplayID: main
HeadSHA: pull.FromRef.LatestCommit, // This is not defined in the official docs, but works in practice
Labels: []string{}, // Not supported by library
})
}

View File

@@ -24,11 +24,6 @@ func defaultHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
"values": [
{
"id": 101,
"toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "master",
"id": "refs/heads/master"
},
"fromRef": {
"id": "refs/heads/feature-ABC-123",
"displayId": "feature-ABC-123",
@@ -60,7 +55,6 @@ func TestListPullRequestNoAuth(t *testing.T) {
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)
assert.Equal(t, "cb3cf2e4d1517c83e720d2585b9402dbef71f992", pullRequests[0].HeadSHA)
}
@@ -77,11 +71,6 @@ func TestListPullRequestPagination(t *testing.T) {
"values": [
{
"id": 101,
"toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "master",
"id": "refs/heads/master"
},
"fromRef": {
"id": "refs/heads/feature-101",
"displayId": "feature-101",
@@ -90,11 +79,6 @@ func TestListPullRequestPagination(t *testing.T) {
},
{
"id": 102,
"toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "branch",
"id": "refs/heads/branch"
},
"fromRef": {
"id": "refs/heads/feature-102",
"displayId": "feature-102",
@@ -112,11 +96,6 @@ func TestListPullRequestPagination(t *testing.T) {
"values": [
{
"id": 200,
"toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "master",
"id": "refs/heads/master"
},
"fromRef": {
"id": "refs/heads/feature-200",
"displayId": "feature-200",
@@ -140,25 +119,22 @@ func TestListPullRequestPagination(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, 3, len(pullRequests))
assert.Equal(t, PullRequest{
Number: 101,
Branch: "feature-101",
TargetBranch: "master",
HeadSHA: "ab3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{},
Number: 101,
Branch: "feature-101",
HeadSHA: "ab3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{},
}, *pullRequests[0])
assert.Equal(t, PullRequest{
Number: 102,
Branch: "feature-102",
TargetBranch: "branch",
HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{},
Number: 102,
Branch: "feature-102",
HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{},
}, *pullRequests[1])
assert.Equal(t, PullRequest{
Number: 200,
Branch: "feature-200",
TargetBranch: "master",
HeadSHA: "cb3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{},
Number: 200,
Branch: "feature-200",
HeadSHA: "cb3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{},
}, *pullRequests[2])
}
@@ -255,11 +231,6 @@ func TestListPullRequestBranchMatch(t *testing.T) {
"values": [
{
"id": 101,
"toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "master",
"id": "refs/heads/master"
},
"fromRef": {
"id": "refs/heads/feature-101",
"displayId": "feature-101",
@@ -268,11 +239,6 @@ func TestListPullRequestBranchMatch(t *testing.T) {
},
{
"id": 102,
"toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "branch",
"id": "refs/heads/branch"
},
"fromRef": {
"id": "refs/heads/feature-102",
"displayId": "feature-102",
@@ -290,11 +256,6 @@ func TestListPullRequestBranchMatch(t *testing.T) {
"values": [
{
"id": 200,
"toRef": {
"latestCommit": "5b766e3564a3453808f3cd3dd3f2e5fad8ef0e7a",
"displayId": "master",
"id": "refs/heads/master"
},
"fromRef": {
"id": "refs/heads/feature-200",
"displayId": "feature-200",
@@ -323,18 +284,16 @@ func TestListPullRequestBranchMatch(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, 2, len(pullRequests))
assert.Equal(t, PullRequest{
Number: 101,
Branch: "feature-101",
TargetBranch: "master",
HeadSHA: "ab3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{},
Number: 101,
Branch: "feature-101",
HeadSHA: "ab3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{},
}, *pullRequests[0])
assert.Equal(t, PullRequest{
Number: 102,
Branch: "feature-102",
TargetBranch: "branch",
HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{},
Number: 102,
Branch: "feature-102",
HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{},
}, *pullRequests[1])
regexp = `.*2$`
@@ -348,11 +307,10 @@ func TestListPullRequestBranchMatch(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, 1, len(pullRequests))
assert.Equal(t, PullRequest{
Number: 102,
Branch: "feature-102",
TargetBranch: "branch",
HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{},
Number: 102,
Branch: "feature-102",
HeadSHA: "bb3cf2e4d1517c83e720d2585b9402dbef71f992",
Labels: []string{},
}, *pullRequests[0])
regexp = `[\d{2}`

View File

@@ -26,13 +26,11 @@ func NewGiteaService(ctx context.Context, token, url, owner, repo string, insecu
if insecure {
cookieJar, _ := cookiejar.New(nil)
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
httpClient = &http.Client{
Jar: cookieJar,
Transport: tr,
}
Jar: cookieJar,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}}
}
client, err := gitea.NewClient(url, gitea.SetToken(token), gitea.SetHTTPClient(httpClient))
if err != nil {
@@ -56,11 +54,10 @@ func (g *GiteaService) List(ctx context.Context) ([]*PullRequest, error) {
list := []*PullRequest{}
for _, pr := range prs {
list = append(list, &PullRequest{
Number: int(pr.Index),
Branch: pr.Head.Ref,
TargetBranch: pr.Base.Ref,
HeadSHA: pr.Head.Sha,
Labels: getGiteaPRLabelNames(pr.Labels),
Number: int(pr.Index),
Branch: pr.Head.Ref,
HeadSHA: pr.Head.Sha,
Labels: getGiteaPRLabelNames(pr.Labels),
})
}
return list, nil

View File

@@ -256,7 +256,6 @@ func TestGiteaList(t *testing.T) {
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")
}
@@ -269,9 +268,9 @@ func TestGetGiteaPRLabelNames(t *testing.T) {
{
Name: "PR has labels",
PullLabels: []*gitea.Label{
{Name: "label1"},
{Name: "label2"},
{Name: "label3"},
&gitea.Label{Name: "label1"},
&gitea.Label{Name: "label2"},
&gitea.Label{Name: "label3"},
},
ExpectedResult: []string{"label1", "label2", "label3"},
},

View File

@@ -65,11 +65,10 @@ func (g *GithubService) List(ctx context.Context) ([]*PullRequest, error) {
continue
}
pullRequests = append(pullRequests, &PullRequest{
Number: *pull.Number,
Branch: *pull.Head.Ref,
TargetBranch: *pull.Base.Ref,
HeadSHA: *pull.Head.SHA,
Labels: getGithubPRLabelNames(pull.Labels),
Number: *pull.Number,
Branch: *pull.Head.Ref,
HeadSHA: *pull.Head.SHA,
Labels: getGithubPRLabelNames(pull.Labels),
})
}
if resp.NextPage == 0 {

View File

@@ -22,9 +22,9 @@ func TestContainLabels(t *testing.T) {
Name: "Match labels",
Labels: []string{"label1", "label2"},
PullLabels: []*github.Label{
{Name: toPtr("label1")},
{Name: toPtr("label2")},
{Name: toPtr("label3")},
&github.Label{Name: toPtr("label1")},
&github.Label{Name: toPtr("label2")},
&github.Label{Name: toPtr("label3")},
},
Expect: true,
},
@@ -32,9 +32,9 @@ func TestContainLabels(t *testing.T) {
Name: "Not match labels",
Labels: []string{"label1", "label4"},
PullLabels: []*github.Label{
{Name: toPtr("label1")},
{Name: toPtr("label2")},
{Name: toPtr("label3")},
&github.Label{Name: toPtr("label1")},
&github.Label{Name: toPtr("label2")},
&github.Label{Name: toPtr("label3")},
},
Expect: false,
},
@@ -42,9 +42,9 @@ func TestContainLabels(t *testing.T) {
Name: "No specify",
Labels: []string{},
PullLabels: []*github.Label{
{Name: toPtr("label1")},
{Name: toPtr("label2")},
{Name: toPtr("label3")},
&github.Label{Name: toPtr("label1")},
&github.Label{Name: toPtr("label2")},
&github.Label{Name: toPtr("label3")},
},
Expect: true,
},
@@ -68,9 +68,9 @@ func TestGetGitHubPRLabelNames(t *testing.T) {
{
Name: "PR has labels",
PullLabels: []*github.Label{
{Name: toPtr("label1")},
{Name: toPtr("label2")},
{Name: toPtr("label3")},
&github.Label{Name: toPtr("label1")},
&github.Label{Name: toPtr("label2")},
&github.Label{Name: toPtr("label3")},
},
ExpectedResult: []string{"label1", "label2", "label3"},
},

View File

@@ -3,11 +3,8 @@ package pull_request
import (
"context"
"fmt"
"net/http"
"os"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
"github.com/hashicorp/go-retryablehttp"
gitlab "github.com/xanzy/go-gitlab"
)
@@ -20,7 +17,7 @@ type GitLabService struct {
var _ PullRequestService = (*GitLabService)(nil)
func NewGitLabService(ctx context.Context, token, url, project string, labels []string, pullRequestState string, scmRootCAPath string, insecure bool) (PullRequestService, error) {
func NewGitLabService(ctx context.Context, token, url, project string, labels []string, pullRequestState string) (PullRequestService, error) {
var clientOptionFns []gitlab.ClientOptionFunc
// Set a custom Gitlab base URL if one is provided
@@ -32,14 +29,6 @@ func NewGitLabService(ctx context.Context, token, url, project string, labels []
token = os.Getenv("GITLAB_TOKEN")
}
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.TLSClientConfig = utils.GetTlsConfig(scmRootCAPath, insecure)
retryClient := retryablehttp.NewClient()
retryClient.HTTPClient.Transport = tr
clientOptionFns = append(clientOptionFns, gitlab.WithHTTPClient(retryClient.HTTPClient))
client, err := gitlab.NewClient(token, clientOptionFns...)
if err != nil {
return nil, fmt.Errorf("error creating Gitlab client: %v", err)
@@ -80,11 +69,10 @@ func (g *GitLabService) List(ctx context.Context) ([]*PullRequest, error) {
}
for _, mr := range mrs {
pullRequests = append(pullRequests, &PullRequest{
Number: mr.IID,
Branch: mr.SourceBranch,
TargetBranch: mr.TargetBranch,
HeadSHA: mr.SHA,
Labels: mr.Labels,
Number: mr.IID,
Branch: mr.SourceBranch,
HeadSHA: mr.SHA,
Labels: mr.Labels,
})
}
if resp.NextPage == 0 {

View File

@@ -34,7 +34,7 @@ func TestGitLabServiceCustomBaseURL(t *testing.T) {
writeMRListResponse(t, w)
})
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", nil, "", "", false)
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", nil, "")
assert.NoError(t, err)
_, err = svc.List(context.Background())
@@ -53,7 +53,7 @@ func TestGitLabServiceToken(t *testing.T) {
writeMRListResponse(t, w)
})
svc, err := NewGitLabService(context.Background(), "token-123", server.URL, "278964", nil, "", "", false)
svc, err := NewGitLabService(context.Background(), "token-123", server.URL, "278964", nil, "")
assert.NoError(t, err)
_, err = svc.List(context.Background())
@@ -72,7 +72,7 @@ func TestList(t *testing.T) {
writeMRListResponse(t, w)
})
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{}, "", "", false)
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{}, "")
assert.NoError(t, err)
prs, err := svc.List(context.Background())
@@ -80,7 +80,6 @@ func TestList(t *testing.T) {
assert.Len(t, prs, 1)
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")
}
@@ -96,7 +95,7 @@ func TestListWithLabels(t *testing.T) {
writeMRListResponse(t, w)
})
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{"feature", "ready"}, "", "", false)
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{"feature", "ready"}, "")
assert.NoError(t, err)
_, err = svc.List(context.Background())
@@ -115,7 +114,7 @@ func TestListWithState(t *testing.T) {
writeMRListResponse(t, w)
})
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{}, "opened", "", false)
svc, err := NewGitLabService(context.Background(), "", server.URL, "278964", []string{}, "opened")
assert.NoError(t, err)
_, err = svc.List(context.Background())

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