Compare commits

...

1 Commits

Author SHA1 Message Date
Blake Pettersson
feaf83cd6e sibling of cdef31276a 2023-06-20 19:19:14 +00:00
465 changed files with 17449 additions and 33246 deletions

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

@@ -6,7 +6,6 @@ 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?

View File

@@ -13,7 +13,7 @@ on:
env:
# Golang version to use across CI steps
GOLANG_VERSION: '1.20'
GOLANG_VERSION: '1.19'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -28,9 +28,9 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Setup Golang
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Download all Go modules
@@ -46,9 +46,9 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Setup Golang
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Restore go build cache
@@ -70,13 +70,13 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Setup Golang
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # 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@639cd343e1d3b897ff35927a75193d57cfcba299 # v3.6.0
uses: golangci/golangci-lint-action@0ad9a0988b3973e851ab0a07adf248ec2e100376 # v3.3.1
with:
version: v1.51.0
args: --timeout 10m --exclude SA5011 --verbose
@@ -93,11 +93,11 @@ jobs:
- name: Create checkout directory
run: mkdir -p ~/go/src/github.com/argoproj
- name: Checkout code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
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@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Install required packages
@@ -149,7 +149,7 @@ jobs:
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@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
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@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Install required packages
@@ -215,9 +215,9 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Setup Golang
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Create symlink in GOPATH
@@ -263,11 +263,11 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Setup NodeJS
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
with:
node-version: '20.2.0'
node-version: '18.15.0'
- name: Restore node dependency cache
id: cache-dependencies
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
@@ -300,7 +300,7 @@ jobs:
sonar_secret: ${{ secrets.SONAR_TOKEN }}
steps:
- name: Checkout code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
with:
fetch-depth: 0
- name: Restore node dependency cache
@@ -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
@@ -379,9 +379,9 @@ jobs:
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
steps:
- name: Checkout code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
- name: Setup Golang
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.0
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: GH actions workaround - Kill XSP4 process
@@ -397,7 +397,6 @@ 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@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1

View File

@@ -30,7 +30,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
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@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.3.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@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.3.0
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
if: ${{ github.ref_type != 'tag'}}
- name: Setup Golang
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
with:
go-version: ${{ inputs.go-version }}
- name: Install cosign
uses: sigstore/cosign-installer@dd6b2e2b610a11fd73dd187a43d57cc1394e35f9 # v3.0.5
uses: sigstore/cosign-installer@c3667d99424e7e6047999fb6246c0da843953c65 # v3.0.1
with:
cosign-release: 'v2.0.0'
- uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
- uses: docker/setup-buildx-action@ecf95283f03858871ff00b787d79c419715afc34 # v2.7.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,7 +122,7 @@ 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 }}
@@ -130,7 +130,7 @@ jobs:
- 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 }}

View File

@@ -25,7 +25,7 @@ jobs:
image-tag: ${{ steps.image.outputs.tag}}
platforms: ${{ steps.platforms.outputs.platforms }}
steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- 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.20
go-version: 1.19
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.20
go-version: 1.19
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.5.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@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.3.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@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.2.0
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
@@ -57,7 +57,7 @@ jobs:
git diff
- 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.20' # Note: go-version must also be set in job argocd-image.with.go-version
GOLANG_VERSION: '1.19' # 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.20
go-version: 1.19
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.7.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.5.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@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
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@fac708d6674e30b6ba41289acaab6d4b75aa0753 # 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@336e29918d653399e599bfca99fadc1d7ffbc9f7 # v4.3.0
uses: goreleaser/goreleaser-action@f82d6c1c344bcacabba2c841718984797f664a6b # v4.2.0
id: run-goreleaser
with:
version: latest
@@ -120,7 +120,7 @@ jobs:
contents: write # Needed for release uploads
if: github.repository == 'argoproj/argo-cd'
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.7.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.5.0
with:
base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
provenance-name: "argocd-cli.intoto.jsonl"
@@ -138,18 +138,18 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.2.0
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Golang
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
with:
go-version: ${{ env.GOLANG_VERSION }}
- name: Install cosign
uses: sigstore/cosign-installer@dd6b2e2b610a11fd73dd187a43d57cc1394e35f9 # v3.0.5
uses: sigstore/cosign-installer@c3667d99424e7e6047999fb6246c0da843953c65 # v3.0.1
with:
cosign-release: 'v2.0.0'
@@ -211,7 +211,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.2.0
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3.2.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
@@ -254,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
@@ -270,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@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@80e868c13c90f172d68d1f4501dee99e2479f7af # v2.1.3
uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2
with:
results_file: results.sarif
results_format: sarif

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
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

2
.gitpod.Dockerfile vendored
View File

@@ -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

@@ -4,7 +4,7 @@ ARG BASE_IMAGE=docker.io/library/ubuntu:22.04@sha256:ac58ff7fe25edc58bdf0067ca99
# 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.20.5@sha256:4b1fc02d16fca272e5e6e6adc98396219b43ef663a377eef4a97e881d364393f AS builder
FROM docker.io/library/golang:1.19.10@sha256:83f9f840072d05ad4d90ce4ac7cb2427632d6b89d5ffc558f18f9577ec8188c0 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.2.0@sha256:14f0471d0478fbb9177d0f9e8c146dc872273dcdcfc7fea93a27ed81fc6b0e96 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.20.5@sha256:4b1fc02d16fca272e5e6e6adc98396219b43ef663a377eef4a97e881d364393f AS argocd-build
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.19.10@sha256:83f9f840072d05ad4d90ce4ac7cb2427632d6b89d5ffc558f18f9577ec8188c0 AS argocd-build
WORKDIR /go/src/github.com/argoproj/argo-cd

View File

@@ -146,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"

View File

@@ -1,6 +1,6 @@
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"
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}"
@@ -8,5 +8,5 @@ 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}"
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"
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)

View File

@@ -65,10 +65,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

@@ -14,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/)
@@ -33,11 +32,11 @@ 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)
@@ -45,24 +44,21 @@ Currently, the following organizations are **officially** using Argo CD:
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)
@@ -101,7 +97,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Glovo](https://www.glovoapp.com)
1. [GMETRI](https://gmetri.com/)
1. [Gojek](https://www.gojek.io/)
1. [GoTo Financial](https://gotofinancial.com/)
1. [Greenpass](https://www.greenpass.com.br/)
1. [Gridfuse](https://gridfuse.com/)
1. [Groww](https://groww.in)
@@ -113,7 +108,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)
@@ -127,11 +121,10 @@ Currently, the following organizations are **officially** using Argo CD:
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/)
@@ -144,7 +137,6 @@ Currently, the following organizations are **officially** using Argo CD:
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/)
@@ -155,12 +147,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. [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/)
@@ -176,7 +166,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)
@@ -191,8 +180,8 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Orbital Insight](https://orbitalinsight.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/)
@@ -203,13 +192,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/)
@@ -229,7 +214,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. [SEEK](https://seek.com.au)
1. [SI Analytics](https://si-analytics.ai)
1. [Skit](https://skit.ai/)
1. [Skyscanner](https://www.skyscanner.net/)
@@ -244,7 +228,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/)
@@ -258,7 +241,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/)
@@ -276,7 +258,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Universidad Mesoamericana](https://www.umes.edu.gt/)
1. [Urbantz](https://urbantz.com/)
1. [Vectra](https://www.vectra.ai)
1. [Veepee](https://www.veepee.com)
1. [Viaduct](https://www.viaduct.ai/)
1. [Vinted](https://vinted.com/)
1. [Virtuo](https://www.govirtuo.com/)
@@ -298,6 +279,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.8.0
2.7.5

View File

@@ -32,7 +32,6 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"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"
@@ -57,10 +56,6 @@ const (
// https://github.com/argoproj-labs/argocd-notifications/blob/33d345fa838829bb50fca5c08523aba380d2c12b/pkg/controller/state.go#L17
NotifiedAnnotationKey = "notified.notifications.argoproj.io"
ReconcileRequeueOnValidationError = time.Minute * 3
// LabelKeyAppSetInstance is the label key to use to uniquely identify the apps of an applicationset
// The ArgoCD applicationset name is used as the instance name
LabelKeyAppSetInstance = "argocd.argoproj.io/application-set-name"
)
var (
@@ -508,13 +503,9 @@ func (r *ApplicationSetReconciler) generateApplications(applicationSetInfo argov
for _, a := range t {
tmplApplication := getTempApplication(a.Template)
if tmplApplication.Labels == nil {
tmplApplication.Labels = make(map[string]string)
}
tmplApplication.Labels[LabelKeyAppSetInstance] = applicationSetInfo.Name
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 {
log.WithError(err).WithField("params", a.Params).WithField("generator", requestedGenerator).
Error("error generating application from params")
@@ -536,7 +527,7 @@ func (r *ApplicationSetReconciler) generateApplications(applicationSetInfo argov
return res, applicationSetReason, firstError
}
func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager, enableProgressiveSyncs bool, maxConcurrentReconciliations int) error {
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)
@@ -557,9 +548,8 @@ func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager, enableProg
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)).
Watches(
&source.Kind{Type: &corev1.Secret{}},

View File

@@ -65,8 +65,8 @@ func (g *generatorMock) GetRequeueAfter(appSetGenerator *v1alpha1.ApplicationSet
return args.Get(0).(time.Duration)
}
func (r *rendererMock) RenderTemplateParams(tmpl *v1alpha1.Application, syncPolicy *v1alpha1.ApplicationSetSyncPolicy, params map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (*v1alpha1.Application, error) {
args := r.Called(tmpl, params, useGoTemplate, goTemplateOptions)
func (r *rendererMock) RenderTemplateParams(tmpl *v1alpha1.Application, syncPolicy *v1alpha1.ApplicationSetSyncPolicy, params map[string]interface{}, useGoTemplate bool) (*v1alpha1.Application, error) {
args := r.Called(tmpl, params, useGoTemplate)
if args.Error(1) != nil {
return nil, args.Error(1)
@@ -164,14 +164,11 @@ func TestExtractApplications(t *testing.T) {
if cc.generateParamsError == nil {
for _, p := range cc.params {
tmpApplication := getTempApplication(cc.template)
tmpApplication.Labels[LabelKeyAppSetInstance] = appSet.Name
if cc.rendererError != nil {
rendererMock.On("RenderTemplateParams", getTempApplication(cc.template), p, false, []string(nil)).
rendererMock.On("RenderTemplateParams", getTempApplication(cc.template), p, false).
Return(nil, cc.rendererError)
} else {
rendererMock.On("RenderTemplateParams", getTempApplication(cc.template), p, false, []string(nil)).
rendererMock.On("RenderTemplateParams", getTempApplication(cc.template), p, false).
Return(&app, nil)
expectedApps = append(expectedApps, app)
}
@@ -288,21 +285,7 @@ func TestMergeTemplateApplications(t *testing.T) {
rendererMock := rendererMock{}
appSet := &v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
Spec: v1alpha1.ApplicationSetSpec{
Generators: []v1alpha1.ApplicationSetGenerator{generator},
Template: cc.template,
},
}
tmpApplication := getTempApplication(cc.expectedMerged)
tmpApplication.Labels[LabelKeyAppSetInstance] = appSet.Name
rendererMock.On("RenderTemplateParams", tmpApplication, cc.params[0], false, []string(nil)).
rendererMock.On("RenderTemplateParams", getTempApplication(cc.expectedMerged), cc.params[0], false).
Return(&cc.expectedApps[0], nil)
r := ApplicationSetReconciler{
@@ -316,7 +299,17 @@ func TestMergeTemplateApplications(t *testing.T) {
KubeClientset: kubefake.NewSimpleClientset(),
}
got, _, _ := r.generateApplications(*appSet)
got, _, _ := r.generateApplications(v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
Spec: v1alpha1.ApplicationSetSpec{
Generators: []v1alpha1.ApplicationSetGenerator{generator},
Template: cc.template,
},
},
)
assert.Equal(t, cc.expectedApps, got)
})
@@ -2048,8 +2041,7 @@ func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "AppSet-branch1-1",
Labels: map[string]string{
"app1": "label1",
LabelKeyAppSetInstance: "",
"app1": "label1",
},
},
Spec: v1alpha1.ApplicationSpec{

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

@@ -6,9 +6,9 @@ import (
"time"
"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"
"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"
@@ -20,7 +20,7 @@ import (
)
func TestRequeueAfter(t *testing.T) {
mockServer := &mocks.Repos{}
mockServer := argoCDServiceMock{}
ctx := context.Background()
scheme := runtime.NewScheme()
err := argov1alpha1.AddToScheme(scheme)
@@ -150,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

@@ -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

@@ -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

@@ -109,7 +109,7 @@ func (g *ClusterGenerator) GenerateParams(
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
}
@@ -149,7 +149,7 @@ func (g *ClusterGenerator) GenerateParams(
}
}
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
}
@@ -162,6 +162,44 @@ func (g *ClusterGenerator) GenerateParams(
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

@@ -52,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).
@@ -147,9 +147,9 @@ 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 *interpolatedGenerator, err

View File

@@ -4,13 +4,13 @@ import (
"context"
"testing"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
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"
@@ -19,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) {
@@ -69,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(),
}},
@@ -170,7 +172,6 @@ func TestMatchValuesGoTemplate(t *testing.T) {
data,
emptyTemplate(),
&applicationSetInfo, nil)
assert.NoError(t, err)
assert.ElementsMatch(t, testCase.expected, results[0].Params)
})
@@ -340,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
}
@@ -357,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"}}`)}},
}}
@@ -366,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,
},
}
@@ -378,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{},
},
}
@@ -395,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",
@@ -413,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
@@ -422,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
@@ -448,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",
@@ -466,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")
@@ -476,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

View File

@@ -59,9 +59,9 @@ 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
}
@@ -72,7 +72,7 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
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)
@@ -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("failed to generate 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
@@ -198,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)
}
@@ -237,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 {
@@ -269,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

@@ -8,17 +8,23 @@ import (
"github.com/stretchr/testify/mock"
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"
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) {
values := map[string]string{}
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
foo:
bar: baz
`), values, false, nil, "")
`), false, "")
if err != nil {
t.Fatal(err)
}
@@ -37,11 +43,10 @@ foo:
}
func Test_generatePrefixedParamsFromGitFile(t *testing.T) {
values := map[string]string{}
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
foo:
bar: baz
`), values, false, nil, "myRepo")
`), false, "myRepo")
if err != nil {
t.Fatal(err)
}
@@ -60,11 +65,10 @@ foo:
}
func Test_generateParamsFromGitFileGoTemplate(t *testing.T) {
values := map[string]string{}
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
foo:
bar: baz
`), values, true, nil, "")
`), true, "")
if err != nil {
t.Fatal(err)
}
@@ -89,11 +93,10 @@ foo:
}
func Test_generatePrefixedParamsFromGitFileGoTemplate(t *testing.T) {
values := map[string]string{}
params, err := (*GitGenerator)(nil).generateParamsFromGitFile("path/dir/file_name.yaml", []byte(`
foo:
bar: baz
`), values, true, nil, "myRepo")
`), true, "myRepo")
if err != nil {
t.Fatal(err)
}
@@ -127,7 +130,6 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
pathParamPrefix string
repoApps []string
repoError error
values map[string]string
expected []map[string]interface{}
expectedError error
}{
@@ -218,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: "*"}},
@@ -261,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",
@@ -277,7 +260,6 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
Revision: "Revision",
Directories: testCaseCopy.directories,
PathParamPrefix: testCaseCopy.pathParamPrefix,
Values: testCaseCopy.values,
},
}},
},
@@ -292,7 +274,7 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
assert.Equal(t, testCaseCopy.expected, got)
}
argoCDServiceMock.AssertExpectations(t)
argoCDServiceMock.Mock.AssertExpectations(t)
})
}
}
@@ -557,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",
@@ -588,7 +570,7 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
assert.Equal(t, testCaseCopy.expected, got)
}
argoCDServiceMock.AssertExpectations(t)
argoCDServiceMock.Mock.AssertExpectations(t)
})
}
@@ -604,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
}{
@@ -668,74 +649,6 @@ 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"}},
@@ -917,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",
@@ -932,7 +845,6 @@ cluster:
RepoURL: "RepoURL",
Revision: "Revision",
Files: testCaseCopy.files,
Values: testCaseCopy.values,
},
}},
},
@@ -948,7 +860,7 @@ cluster:
assert.ElementsMatch(t, testCaseCopy.expected, got)
}
argoCDServiceMock.AssertExpectations(t)
argoCDServiceMock.Mock.AssertExpectations(t)
})
}
}
@@ -1267,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",
@@ -1298,7 +1210,7 @@ cluster:
assert.ElementsMatch(t, testCaseCopy.expected, got)
}
argoCDServiceMock.AssertExpectations(t)
argoCDServiceMock.Mock.AssertExpectations(t)
})
}
}

View File

@@ -97,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,
@@ -136,7 +135,6 @@ func (m *MatrixGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Ap
Clusters: r.Clusters,
Git: r.Git,
PullRequest: r.PullRequest,
Plugin: r.Plugin,
Matrix: matrixGen,
Merge: mergeGen,
}

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"
)
@@ -849,7 +848,7 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
}
listGenerator := &argoprojiov1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{},
Elements: []apiextensionsv1.JSON{},
ElementsYaml: "{{ .foo.bar | toJson }}",
}
@@ -871,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",
},
},
},
},
},
}
@@ -952,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{})
@@ -1053,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

@@ -154,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,
@@ -191,7 +190,6 @@ func (m *MergeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.App
Clusters: r.Clusters,
Git: r.Git,
PullRequest: r.PullRequest,
Plugin: r.Plugin,
Matrix: matrixGen,
Merge: mergeGen,
}

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, 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, 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, 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 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 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 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 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"
)
@@ -65,7 +66,7 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
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)
}
@@ -83,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.

View File

@@ -28,10 +28,9 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
ctx,
[]*pullrequest.PullRequest{
&pullrequest.PullRequest{
Number: 1,
Branch: "branch1",
TargetBranch: "master",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
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,
@@ -57,10 +53,9 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
ctx,
[]*pullrequest.PullRequest{
&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",
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,
@@ -86,10 +78,9 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
ctx,
[]*pullrequest.PullRequest{
&pullrequest.PullRequest{
Number: 1,
Branch: "a-very-short-sha",
TargetBranch: "master",
HeadSHA: "abcd",
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,
@@ -126,11 +114,10 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
ctx,
[]*pullrequest.PullRequest{
&pullrequest.PullRequest{
Number: 1,
Branch: "branch1",
TargetBranch: "master",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
Labels: []string{"preview"},
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,
@@ -163,11 +147,10 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
ctx,
[]*pullrequest.PullRequest{
&pullrequest.PullRequest{
Number: 1,
Branch: "branch1",
TargetBranch: "master",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
Labels: []string{"preview"},
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,

View File

@@ -131,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")
}
@@ -146,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,123 +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}
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)
}
})
}
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,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

@@ -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

@@ -54,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")
}

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

@@ -69,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

@@ -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")
}

View File

@@ -10,8 +10,6 @@ type PullRequest struct {
Number int
// Branch is the name of the branch from which the pull request originated.
Branch string
// TargetBranch is the name of the target branch of the pull request.
TargetBranch string
// HeadSHA is the SHA of the HEAD from which the pull request originated.
HeadSHA string
// Labels of the pull request.
@@ -24,6 +22,5 @@ type PullRequestService interface {
}
type Filter struct {
BranchMatch *regexp.Regexp
TargetBranchMatch *regexp.Regexp
BranchMatch *regexp.Regexp
}

View File

@@ -19,12 +19,6 @@ func compileFilters(filters []argoprojiov1alpha1.PullRequestGeneratorFilter) ([]
return nil, fmt.Errorf("error compiling BranchMatch regexp %q: %v", *filter.BranchMatch, err)
}
}
if filter.TargetBranchMatch != nil {
outFilter.TargetBranchMatch, err = regexp.Compile(*filter.TargetBranchMatch)
if err != nil {
return nil, fmt.Errorf("error compiling TargetBranchMatch regexp %q: %v", *filter.TargetBranchMatch, err)
}
}
outFilters = append(outFilters, outFilter)
}
return outFilters, nil
@@ -34,9 +28,6 @@ func matchFilter(pullRequest *PullRequest, filter *Filter) bool {
if filter.BranchMatch != nil && !filter.BranchMatch.MatchString(pullRequest.Branch) {
return false
}
if filter.TargetBranchMatch != nil && !filter.TargetBranchMatch.MatchString(pullRequest.TargetBranch) {
return false
}
return true
}

View File

@@ -16,10 +16,9 @@ func TestFilterBranchMatchBadRegexp(t *testing.T) {
context.Background(),
[]*PullRequest{
{
Number: 1,
Branch: "branch1",
TargetBranch: "master",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
Number: 1,
Branch: "branch1",
HeadSHA: "089d92cbf9ff857a39e6feccd32798ca700fb958",
},
},
nil,
@@ -38,28 +37,24 @@ func TestFilterBranchMatch(t *testing.T) {
context.Background(),
[]*PullRequest{
{
Number: 1,
Branch: "one",
TargetBranch: "master",
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
Number: 1,
Branch: "one",
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
},
{
Number: 2,
Branch: "two",
TargetBranch: "master",
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
Number: 2,
Branch: "two",
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
},
{
Number: 3,
Branch: "three",
TargetBranch: "master",
HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958",
Number: 3,
Branch: "three",
HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958",
},
{
Number: 4,
Branch: "four",
TargetBranch: "master",
HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958",
Number: 4,
Branch: "four",
HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958",
},
},
nil,
@@ -75,75 +70,29 @@ func TestFilterBranchMatch(t *testing.T) {
assert.Equal(t, "two", pullRequests[0].Branch)
}
func TestFilterTargetBranchMatch(t *testing.T) {
provider, _ := NewFakeService(
context.Background(),
[]*PullRequest{
{
Number: 1,
Branch: "one",
TargetBranch: "master",
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
},
{
Number: 2,
Branch: "two",
TargetBranch: "branch1",
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
},
{
Number: 3,
Branch: "three",
TargetBranch: "branch2",
HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958",
},
{
Number: 4,
Branch: "four",
TargetBranch: "branch3",
HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958",
},
},
nil,
)
filters := []argoprojiov1alpha1.PullRequestGeneratorFilter{
{
TargetBranchMatch: strp("1"),
},
}
pullRequests, err := ListPullRequests(context.Background(), provider, filters)
assert.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.Equal(t, "two", pullRequests[0].Branch)
}
func TestMultiFilterOr(t *testing.T) {
provider, _ := NewFakeService(
context.Background(),
[]*PullRequest{
{
Number: 1,
Branch: "one",
TargetBranch: "master",
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
Number: 1,
Branch: "one",
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
},
{
Number: 2,
Branch: "two",
TargetBranch: "master",
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
Number: 2,
Branch: "two",
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
},
{
Number: 3,
Branch: "three",
TargetBranch: "master",
HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958",
Number: 3,
Branch: "three",
HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958",
},
{
Number: 4,
Branch: "four",
TargetBranch: "master",
HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958",
Number: 4,
Branch: "four",
HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958",
},
},
nil,
@@ -164,69 +113,19 @@ func TestMultiFilterOr(t *testing.T) {
assert.Equal(t, "four", pullRequests[2].Branch)
}
func TestMultiFilterOrWithTargetBranchFilter(t *testing.T) {
provider, _ := NewFakeService(
context.Background(),
[]*PullRequest{
{
Number: 1,
Branch: "one",
TargetBranch: "master",
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
},
{
Number: 2,
Branch: "two",
TargetBranch: "branch1",
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
},
{
Number: 3,
Branch: "three",
TargetBranch: "branch2",
HeadSHA: "389d92cbf9ff857a39e6feccd32798ca700fb958",
},
{
Number: 4,
Branch: "four",
TargetBranch: "branch3",
HeadSHA: "489d92cbf9ff857a39e6feccd32798ca700fb958",
},
},
nil,
)
filters := []argoprojiov1alpha1.PullRequestGeneratorFilter{
{
BranchMatch: strp("w"),
TargetBranchMatch: strp("1"),
},
{
BranchMatch: strp("r"),
TargetBranchMatch: strp("3"),
},
}
pullRequests, err := ListPullRequests(context.Background(), provider, filters)
assert.NoError(t, err)
assert.Len(t, pullRequests, 2)
assert.Equal(t, "two", pullRequests[0].Branch)
assert.Equal(t, "four", pullRequests[1].Branch)
}
func TestNoFilters(t *testing.T) {
provider, _ := NewFakeService(
context.Background(),
[]*PullRequest{
{
Number: 1,
Branch: "one",
TargetBranch: "master",
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
Number: 1,
Branch: "one",
HeadSHA: "189d92cbf9ff857a39e6feccd32798ca700fb958",
},
{
Number: 2,
Branch: "two",
TargetBranch: "master",
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
Number: 2,
Branch: "two",
HeadSHA: "289d92cbf9ff857a39e6feccd32798ca700fb958",
},
},
nil,

View File

@@ -3,26 +3,25 @@ package services
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
"github.com/argoproj/argo-cd/v2/util/db"
"github.com/argoproj/argo-cd/v2/util/git"
"github.com/argoproj/argo-cd/v2/util/io"
)
// RepositoryDB Is a lean facade for ArgoDB,
// Using a lean interface makes it easier to test the functionality of the git generator
// Using a lean interface makes it more easy to test the functionality the git generator uses
type RepositoryDB interface {
GetRepository(ctx context.Context, url string) (*v1alpha1.Repository, error)
}
type argoCDService struct {
repositoriesDB RepositoryDB
storecreds git.CredsStore
submoduleEnabled bool
repoServerClientSet apiclient.Clientset
newFileGlobbingEnabled bool
repositoriesDB RepositoryDB
storecreds git.CredsStore
submoduleEnabled bool
}
type Repos interface {
@@ -34,63 +33,121 @@ type Repos interface {
GetDirectories(ctx context.Context, repoURL string, revision string) ([]string, error)
}
func NewArgoCDService(db db.ArgoDB, submoduleEnabled bool, repoClientset apiclient.Clientset, newFileGlobbingEnabled bool) (Repos, error) {
func NewArgoCDService(db db.ArgoDB, gitCredStore git.CredsStore, submoduleEnabled bool) Repos {
return &argoCDService{
repositoriesDB: db.(RepositoryDB),
submoduleEnabled: submoduleEnabled,
repoServerClientSet: repoClientset,
newFileGlobbingEnabled: newFileGlobbingEnabled,
}, nil
repositoriesDB: db.(RepositoryDB),
storecreds: gitCredStore,
submoduleEnabled: submoduleEnabled,
}
}
func (a *argoCDService) GetFiles(ctx context.Context, repoURL string, revision string, pattern string) (map[string][]byte, error) {
repo, err := a.repositoriesDB.GetRepository(ctx, repoURL)
if err != nil {
return nil, fmt.Errorf("error in GetRepository: %w", err)
return nil, fmt.Errorf("Error in GetRepository: %w", err)
}
fileRequest := &apiclient.GitFilesRequest{
Repo: repo,
SubmoduleEnabled: a.submoduleEnabled,
Revision: revision,
Path: pattern,
NewGitFileGlobbingEnabled: a.newFileGlobbingEnabled,
}
closer, client, err := a.repoServerClientSet.NewRepoServerClient()
gitRepoClient, err := git.NewClient(repo.Repo, repo.GetGitCreds(a.storecreds), repo.IsInsecure(), repo.IsLFSEnabled(), repo.Proxy)
if err != nil {
return nil, err
}
defer io.Close(closer)
fileResponse, err := client.GetGitFiles(ctx, fileRequest)
err = checkoutRepo(gitRepoClient, revision, a.submoduleEnabled)
if err != nil {
return nil, err
}
return fileResponse.GetMap(), nil
paths, err := gitRepoClient.LsFiles(pattern)
if err != nil {
return nil, fmt.Errorf("Error during listing files of local repo: %w", err)
}
res := map[string][]byte{}
for _, filePath := range paths {
bytes, err := os.ReadFile(filepath.Join(gitRepoClient.Root(), filePath))
if err != nil {
return nil, err
}
res[filePath] = bytes
}
return res, nil
}
func (a *argoCDService) GetDirectories(ctx context.Context, repoURL string, revision string) ([]string, error) {
repo, err := a.repositoriesDB.GetRepository(ctx, repoURL)
if err != nil {
return nil, fmt.Errorf("error in GetRepository: %w", err)
return nil, fmt.Errorf("Error in GetRepository: %w", err)
}
dirRequest := &apiclient.GitDirectoriesRequest{
Repo: repo,
SubmoduleEnabled: a.submoduleEnabled,
Revision: revision,
}
closer, client, err := a.repoServerClientSet.NewRepoServerClient()
gitRepoClient, err := git.NewClient(repo.Repo, repo.GetGitCreds(a.storecreds), repo.IsInsecure(), repo.IsLFSEnabled(), repo.Proxy)
if err != nil {
return nil, fmt.Errorf("error creating a new git client: %w", err)
}
err = checkoutRepo(gitRepoClient, revision, a.submoduleEnabled)
if err != nil {
return nil, fmt.Errorf("error while checking out repo: %w", err)
}
filteredPaths := []string{}
repoRoot := gitRepoClient.Root()
if err := filepath.Walk(repoRoot, func(path string, info os.FileInfo, fnErr error) error {
if fnErr != nil {
return fmt.Errorf("error walking the file tree: %w", fnErr)
}
if !info.IsDir() { // Skip files: directories only
return nil
}
fname := info.Name()
if strings.HasPrefix(fname, ".") { // Skip all folders starts with "."
return filepath.SkipDir
}
relativePath, err := filepath.Rel(repoRoot, path)
if err != nil {
return fmt.Errorf("error constructing relative repo path: %w", err)
}
if relativePath == "." { // Exclude '.' from results
return nil
}
filteredPaths = append(filteredPaths, relativePath)
return nil
}); err != nil {
return nil, err
}
defer io.Close(closer)
dirResponse, err := client.GetGitDirectories(ctx, dirRequest)
if err != nil {
return nil, err
}
return dirResponse.GetPaths(), nil
return filteredPaths, nil
}
func checkoutRepo(gitRepoClient git.Client, revision string, submoduleEnabled bool) error {
err := gitRepoClient.Init()
if err != nil {
return fmt.Errorf("Error during initializing repo: %w", err)
}
err = gitRepoClient.Fetch(revision)
if err != nil {
return fmt.Errorf("Error during fetching repo: %w", err)
}
commitSHA, err := gitRepoClient.LsRemote(revision)
if err != nil {
return fmt.Errorf("Error during fetching commitSHA: %w", err)
}
err = gitRepoClient.Checkout(commitSHA, submoduleEnabled)
if err != nil {
return fmt.Errorf("Error during repo checkout: %w", err)
}
return nil
}

View File

@@ -3,189 +3,231 @@ package services
import (
"context"
"fmt"
"sort"
"testing"
"github.com/argoproj/argo-cd/v2/applicationset/services/mocks"
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
repo_mocks "github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
db_mocks "github.com/argoproj/argo-cd/v2/util/db/mocks"
"github.com/argoproj/argo-cd/v2/util/git"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
type ArgocdRepositoryMock struct {
mock *mock.Mock
}
func (a ArgocdRepositoryMock) GetRepository(ctx context.Context, url string) (*v1alpha1.Repository, error) {
args := a.mock.Called(ctx, url)
return args.Get(0).(*v1alpha1.Repository), args.Error(1)
}
func TestGetDirectories(t *testing.T) {
type fields struct {
repositoriesDBFuncs []func(*mocks.RepositoryDB)
storecreds git.CredsStore
submoduleEnabled bool
repoServerClientFuncs []func(*repo_mocks.RepoServerServiceClient)
}
type args struct {
ctx context.Context
repoURL string
revision string
}
tests := []struct {
name string
fields fields
args args
want []string
wantErr assert.ErrorAssertionFunc
// Hardcode a specific revision to changes to argocd-example-apps from regressing this test:
// Author: Alexander Matyushentsev <Alexander_Matyushentsev@intuit.com>
// Date: Sun Jan 31 09:54:53 2021 -0800
// chore: downgrade kustomize guestbook image tag (#73)
exampleRepoRevision := "08f72e2a309beab929d9fd14626071b1a61a47f9"
for _, c := range []struct {
name string
repoURL string
revision string
repoRes *v1alpha1.Repository
repoErr error
expected []string
expectedError error
}{
{name: "ErrorGettingRepos", fields: fields{
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("unable to get repos"))
},
{
name: "All child folders should be returned",
repoURL: "https://github.com/argoproj/argocd-example-apps/",
revision: exampleRepoRevision,
repoRes: &v1alpha1.Repository{
Repo: "https://github.com/argoproj/argocd-example-apps/",
},
}, args: args{}, want: nil, wantErr: assert.Error},
{name: "ErrorGettingDirs", fields: fields{
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(&v1alpha1.Repository{}, nil)
},
repoErr: nil,
expected: []string{"apps", "apps/templates", "blue-green", "blue-green/templates", "guestbook", "helm-dependency",
"helm-guestbook", "helm-guestbook/templates", "helm-hooks", "jsonnet-guestbook", "jsonnet-guestbook-tla",
"ksonnet-guestbook", "ksonnet-guestbook/components", "ksonnet-guestbook/environments", "ksonnet-guestbook/environments/default",
"ksonnet-guestbook/environments/dev", "ksonnet-guestbook/environments/prod", "kustomize-guestbook", "plugins", "plugins/kasane",
"plugins/kustomized-helm", "plugins/kustomized-helm/overlays", "pre-post-sync", "sock-shop", "sock-shop/base", "sync-waves"},
},
{
name: "If GetRepository returns an error, it should pass back to caller",
repoURL: "https://github.com/argoproj/argocd-example-apps/",
revision: exampleRepoRevision,
repoRes: &v1alpha1.Repository{
Repo: "https://github.com/argoproj/argocd-example-apps/",
},
repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){
func(client *repo_mocks.RepoServerServiceClient) {
client.On("GetGitDirectories", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("unable to get dirs"))
},
repoErr: fmt.Errorf("Simulated error from GetRepository"),
expected: nil,
expectedError: fmt.Errorf("Error in GetRepository: Simulated error from GetRepository"),
},
{
name: "Test against repository containing no directories",
// Here I picked an arbitrary repository in argoproj-labs, with a commit containing no folders.
repoURL: "https://github.com/argoproj-labs/argo-workflows-operator/",
revision: "5f50933a576833b73b7a172909d8545a108685f4",
repoRes: &v1alpha1.Repository{
Repo: "https://github.com/argoproj-labs/argo-workflows-operator/",
},
}, args: args{}, want: nil, wantErr: assert.Error},
{name: "HappyCase", fields: fields{
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(&v1alpha1.Repository{}, nil)
},
},
repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){
func(client *repo_mocks.RepoServerServiceClient) {
client.On("GetGitDirectories", mock.Anything, mock.Anything).Return(&apiclient.GitDirectoriesResponse{
Paths: []string{"foo", "foo/bar", "bar/foo"},
}, nil)
},
},
}, args: args{}, want: []string{"foo", "foo/bar", "bar/foo"}, wantErr: assert.NoError},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockDb := &mocks.RepositoryDB{}
mockRepoClient := &repo_mocks.RepoServerServiceClient{}
// decorate the mocks
for i := range tt.fields.repositoriesDBFuncs {
tt.fields.repositoriesDBFuncs[i](mockDb)
}
for i := range tt.fields.repoServerClientFuncs {
tt.fields.repoServerClientFuncs[i](mockRepoClient)
repoErr: nil,
expected: []string{},
},
} {
cc := c
t.Run(cc.name, func(t *testing.T) {
argocdRepositoryMock := ArgocdRepositoryMock{mock: &mock.Mock{}}
argocdRepositoryMock.mock.On("GetRepository", mock.Anything, cc.repoURL).Return(cc.repoRes, cc.repoErr)
argocd := argoCDService{
repositoriesDB: argocdRepositoryMock,
}
a := &argoCDService{
repositoriesDB: mockDb,
storecreds: tt.fields.storecreds,
submoduleEnabled: tt.fields.submoduleEnabled,
repoServerClientSet: &repo_mocks.Clientset{RepoServerServiceClient: mockRepoClient},
got, err := argocd.GetDirectories(context.TODO(), cc.repoURL, cc.revision)
if cc.expectedError != nil {
assert.EqualError(t, err, cc.expectedError.Error())
} else {
sort.Strings(got)
sort.Strings(cc.expected)
assert.Equal(t, got, cc.expected)
assert.NoError(t, err)
}
got, err := a.GetDirectories(tt.args.ctx, tt.args.repoURL, tt.args.revision)
if !tt.wantErr(t, err, fmt.Sprintf("GetDirectories(%v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision)) {
return
}
assert.Equalf(t, tt.want, got, "GetDirectories(%v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision)
})
}
}
func TestGetFiles(t *testing.T) {
type fields struct {
repositoriesDBFuncs []func(*mocks.RepositoryDB)
storecreds git.CredsStore
submoduleEnabled bool
repoServerClientFuncs []func(*repo_mocks.RepoServerServiceClient)
}
type args struct {
ctx context.Context
// Hardcode a specific commit, so that changes to argoproj/argocd-example-apps/ don't break our tests
// "chore: downgrade kustomize guestbook image tag (#73)"
commitID := "08f72e2a309beab929d9fd14626071b1a61a47f9"
tests := []struct {
name string
repoURL string
revision string
pattern string
}
tests := []struct {
name string
fields fields
args args
want map[string][]byte
wantErr assert.ErrorAssertionFunc
repoRes *v1alpha1.Repository
repoErr error
expectSubsetOfPaths []string
doesNotContainPaths []string
expectedError error
}{
{name: "ErrorGettingRepos", fields: fields{
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("unable to get repos"))
},
{
name: "pull a specific revision of example apps and verify the list is expected",
repoRes: &v1alpha1.Repository{
Insecure: true,
InsecureIgnoreHostKey: true,
Repo: "https://github.com/argoproj/argocd-example-apps/",
},
}, args: args{}, want: nil, wantErr: assert.Error},
{name: "ErrorGettingFiles", fields: fields{
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(&v1alpha1.Repository{}, nil)
},
repoURL: "https://github.com/argoproj/argocd-example-apps/",
revision: commitID,
pattern: "*",
expectSubsetOfPaths: []string{
"apps/Chart.yaml",
"apps/templates/helm-guestbook.yaml",
"apps/templates/helm-hooks.yaml",
"apps/templates/kustomize-guestbook.yaml",
"apps/templates/namespaces.yaml",
"apps/templates/sync-waves.yaml",
"apps/values.yaml",
"blue-green/.helmignore",
"blue-green/Chart.yaml",
"blue-green/README.md",
"blue-green/templates/NOTES.txt",
"blue-green/templates/rollout.yaml",
"blue-green/templates/services.yaml",
"blue-green/values.yaml",
"guestbook/guestbook-ui-deployment.yaml",
"guestbook/guestbook-ui-svc.yaml",
"kustomize-guestbook/guestbook-ui-deployment.yaml",
"kustomize-guestbook/guestbook-ui-svc.yaml",
"kustomize-guestbook/kustomization.yaml",
},
repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){
func(client *repo_mocks.RepoServerServiceClient) {
client.On("GetGitFiles", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("unable to get files"))
},
},
{
name: "pull an invalid revision, and confirm an error is returned",
repoRes: &v1alpha1.Repository{
Insecure: true,
InsecureIgnoreHostKey: true,
Repo: "https://github.com/argoproj/argocd-example-apps/",
},
}, args: args{}, want: nil, wantErr: assert.Error},
{name: "HappyCase", fields: fields{
repositoriesDBFuncs: []func(*mocks.RepositoryDB){
func(db *mocks.RepositoryDB) {
db.On("GetRepository", mock.Anything, mock.Anything).Return(&v1alpha1.Repository{}, nil)
},
repoURL: "https://github.com/argoproj/argocd-example-apps/",
revision: "this-tag-does-not-exist",
pattern: "*",
expectSubsetOfPaths: []string{},
expectedError: fmt.Errorf("Error during fetching repo: `git fetch origin this-tag-does-not-exist --tags --force --prune` failed exit status 128: fatal: couldn't find remote ref this-tag-does-not-exist"),
},
{
name: "pull a specific revision of example apps, and use a ** pattern",
repoRes: &v1alpha1.Repository{
Insecure: true,
InsecureIgnoreHostKey: true,
Repo: "https://github.com/argoproj/argocd-example-apps/",
},
repoServerClientFuncs: []func(*repo_mocks.RepoServerServiceClient){
func(client *repo_mocks.RepoServerServiceClient) {
client.On("GetGitFiles", mock.Anything, mock.Anything).Return(&apiclient.GitFilesResponse{
Map: map[string][]byte{
"foo.json": []byte("hello: world!"),
"bar.yaml": []byte("yay: appsets"),
},
}, nil)
},
repoURL: "https://github.com/argoproj/argocd-example-apps/",
revision: commitID,
pattern: "**/*.yaml",
expectSubsetOfPaths: []string{
"apps/Chart.yaml",
"apps/templates/helm-guestbook.yaml",
"apps/templates/helm-hooks.yaml",
"apps/templates/kustomize-guestbook.yaml",
"apps/templates/namespaces.yaml",
"apps/templates/sync-waves.yaml",
"apps/values.yaml",
"blue-green/templates/rollout.yaml",
"blue-green/templates/services.yaml",
"blue-green/values.yaml",
"guestbook/guestbook-ui-deployment.yaml",
"guestbook/guestbook-ui-svc.yaml",
"kustomize-guestbook/guestbook-ui-deployment.yaml",
"kustomize-guestbook/guestbook-ui-svc.yaml",
"kustomize-guestbook/kustomization.yaml",
},
}, args: args{}, want: map[string][]byte{
"foo.json": []byte("hello: world!"),
"bar.yaml": []byte("yay: appsets"),
}, wantErr: assert.NoError},
doesNotContainPaths: []string{
"blue-green/.helmignore",
"blue-green/README.md",
"blue-green/templates/NOTES.txt",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockDb := &mocks.RepositoryDB{}
mockRepoClient := &repo_mocks.RepoServerServiceClient{}
// decorate the mocks
for i := range tt.fields.repositoriesDBFuncs {
tt.fields.repositoriesDBFuncs[i](mockDb)
}
for i := range tt.fields.repoServerClientFuncs {
tt.fields.repoServerClientFuncs[i](mockRepoClient)
for _, cc := range tests {
// Get all the paths for a repository, and confirm that the expected subset of paths is found (or the expected error is returned)
t.Run(cc.name, func(t *testing.T) {
argocdRepositoryMock := ArgocdRepositoryMock{mock: &mock.Mock{}}
argocdRepositoryMock.mock.On("GetRepository", mock.Anything, cc.repoURL).Return(cc.repoRes, cc.repoErr)
argocd := argoCDService{
repositoriesDB: argocdRepositoryMock,
}
a := &argoCDService{
repositoriesDB: mockDb,
storecreds: tt.fields.storecreds,
submoduleEnabled: tt.fields.submoduleEnabled,
repoServerClientSet: &repo_mocks.Clientset{RepoServerServiceClient: mockRepoClient},
getPathsRes, err := argocd.GetFiles(context.Background(), cc.repoURL, cc.revision, cc.pattern)
if cc.expectedError == nil {
assert.NoError(t, err)
for _, path := range cc.expectSubsetOfPaths {
assert.Contains(t, getPathsRes, path, "Unable to locate path: %s", path)
}
for _, shouldNotContain := range cc.doesNotContainPaths {
assert.NotContains(t, getPathsRes, shouldNotContain, "GetPaths should not contain %s", shouldNotContain)
}
} else {
assert.EqualError(t, err, cc.expectedError.Error())
}
got, err := a.GetFiles(tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern)
if !tt.wantErr(t, err, fmt.Sprintf("GetFiles(%v, %v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern)) {
return
}
assert.Equalf(t, tt.want, got, "GetFiles(%v, %v, %v, %v)", tt.args.ctx, tt.args.repoURL, tt.args.revision, tt.args.pattern)
})
}
}
func TestNewArgoCDService(t *testing.T) {
service, err := NewArgoCDService(&db_mocks.ArgoDB{}, false, &repo_mocks.Clientset{}, false)
assert.NoError(t, err, err)
assert.NotNil(t, service)
}

View File

@@ -1,376 +0,0 @@
package scm_provider
import (
"context"
"fmt"
"github.com/aws/aws-sdk-go/aws/request"
pathpkg "path"
"path/filepath"
"strings"
application "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/codecommit"
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
log "github.com/sirupsen/logrus"
"golang.org/x/exp/maps"
"k8s.io/utils/strings/slices"
)
const (
resourceTypeCodeCommitRepository = "codecommit:repository"
prefixGitUrlHttps = "https://git-codecommit."
prefixGitUrlHttpsFIPS = "https://git-codecommit-fips."
)
// AWSCodeCommitClient is a lean facade to the codecommitiface.CodeCommitAPI
// it helps to reduce the mockery generated code.
type AWSCodeCommitClient interface {
ListRepositoriesWithContext(aws.Context, *codecommit.ListRepositoriesInput, ...request.Option) (*codecommit.ListRepositoriesOutput, error)
GetRepositoryWithContext(aws.Context, *codecommit.GetRepositoryInput, ...request.Option) (*codecommit.GetRepositoryOutput, error)
ListBranchesWithContext(aws.Context, *codecommit.ListBranchesInput, ...request.Option) (*codecommit.ListBranchesOutput, error)
GetFolderWithContext(aws.Context, *codecommit.GetFolderInput, ...request.Option) (*codecommit.GetFolderOutput, error)
}
// AWSTaggingClient is a lean facade to the resourcegroupstaggingapiiface.ResourceGroupsTaggingAPIAPI
// it helps to reduce the mockery generated code.
type AWSTaggingClient interface {
GetResourcesWithContext(aws.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error)
}
type AWSCodeCommitProvider struct {
codeCommitClient AWSCodeCommitClient
taggingClient AWSTaggingClient
tagFilters []*application.TagFilter
allBranches bool
}
func NewAWSCodeCommitProvider(ctx context.Context, tagFilters []*application.TagFilter, role string, region string, allBranches bool) (*AWSCodeCommitProvider, error) {
taggingClient, codeCommitClient, err := createAWSDiscoveryClients(ctx, role, region)
if err != nil {
return nil, err
}
return &AWSCodeCommitProvider{
codeCommitClient: codeCommitClient,
taggingClient: taggingClient,
tagFilters: tagFilters,
allBranches: allBranches,
}, nil
}
func (p *AWSCodeCommitProvider) ListRepos(ctx context.Context, cloneProtocol string) ([]*Repository, error) {
repos := make([]*Repository, 0)
repoNames, err := p.listRepoNames(ctx)
if err != nil {
return nil, fmt.Errorf("failed to list codecommit repository: %w", err)
}
for _, repoName := range repoNames {
repo, err := p.codeCommitClient.GetRepositoryWithContext(ctx, &codecommit.GetRepositoryInput{
RepositoryName: aws.String(repoName),
})
if err != nil {
// we don't want to skip at this point. It's a valid repo, we don't want to have flapping Application on an AWS outage.
return nil, fmt.Errorf("failed to get codecommit repository: %w", err)
}
if repo == nil || repo.RepositoryMetadata == nil {
// unlikely to happen, but just in case to protect nil pointer dereferences.
log.Warnf("codecommit returned invalid response for repository %s, skipped", repoName)
continue
}
if aws.StringValue(repo.RepositoryMetadata.DefaultBranch) == "" {
// if a codecommit repo doesn't have default branch, it's uninitialized. not going to bother with it.
log.Warnf("repository %s does not have default branch, skipped", repoName)
continue
}
var url string
switch cloneProtocol {
// default to SSH if unspecified (i.e. if "").
case "", "ssh":
url = aws.StringValue(repo.RepositoryMetadata.CloneUrlSsh)
case "https":
url = aws.StringValue(repo.RepositoryMetadata.CloneUrlHttp)
case "https-fips":
url, err = getCodeCommitFIPSEndpoint(aws.StringValue(repo.RepositoryMetadata.CloneUrlHttp))
if err != nil {
return nil, fmt.Errorf("https-fips is provided but repoUrl can't be transformed to FIPS endpoint: %w", err)
}
default:
return nil, fmt.Errorf("unknown clone protocol for codecommit %v", cloneProtocol)
}
repos = append(repos, &Repository{
// there's no "organization" level at codecommit.
// we are just using AWS accountId for now.
Organization: aws.StringValue(repo.RepositoryMetadata.AccountId),
Repository: aws.StringValue(repo.RepositoryMetadata.RepositoryName),
URL: url,
Branch: aws.StringValue(repo.RepositoryMetadata.DefaultBranch),
// we could propagate repo tag keys, but without value not sure if it's any useful.
Labels: []string{},
RepositoryId: aws.StringValue(repo.RepositoryMetadata.RepositoryId),
})
}
return repos, nil
}
func (p *AWSCodeCommitProvider) RepoHasPath(ctx context.Context, repo *Repository, path string) (bool, error) {
// we use GetFolder instead of GetFile here because GetFile always downloads the full blob which has scalability problem.
// GetFolder is slightly less concerning.
path = toAbsolutePath(path)
// shortcut: if it's root folder ('/'), we always return true.
if path == "/" {
return true, nil
}
// here we are sure it's not root folder, strip the suffix for easier comparison.
path = strings.TrimSuffix(path, "/")
// we always get the parent folder, so we could support both submodule, file, symlink and folder cases.
parentPath := pathpkg.Dir(path)
basePath := pathpkg.Base(path)
input := &codecommit.GetFolderInput{
CommitSpecifier: aws.String(repo.Branch),
FolderPath: aws.String(parentPath),
RepositoryName: aws.String(repo.Repository),
}
output, err := p.codeCommitClient.GetFolderWithContext(ctx, input)
if err != nil {
if hasAwsError(err,
codecommit.ErrCodeRepositoryDoesNotExistException,
codecommit.ErrCodeCommitDoesNotExistException,
codecommit.ErrCodeFolderDoesNotExistException,
) {
return false, nil
}
// unhandled exception, propagate out
return false, err
}
// anything that matches.
for _, submodule := range output.SubModules {
if basePath == aws.StringValue(submodule.RelativePath) {
return true, nil
}
}
for _, subpath := range output.SubFolders {
if basePath == aws.StringValue(subpath.RelativePath) {
return true, nil
}
}
for _, subpath := range output.Files {
if basePath == aws.StringValue(subpath.RelativePath) {
return true, nil
}
}
for _, subpath := range output.SymbolicLinks {
if basePath == aws.StringValue(subpath.RelativePath) {
return true, nil
}
}
return false, nil
}
func (p *AWSCodeCommitProvider) GetBranches(ctx context.Context, repo *Repository) ([]*Repository, error) {
repos := make([]*Repository, 0)
if !p.allBranches {
output, err := p.codeCommitClient.GetRepositoryWithContext(ctx, &codecommit.GetRepositoryInput{
RepositoryName: aws.String(repo.Repository),
})
if err != nil {
return nil, err
}
repos = append(repos, &Repository{
Organization: repo.Organization,
Repository: repo.Repository,
URL: repo.URL,
Branch: aws.StringValue(output.RepositoryMetadata.DefaultBranch),
RepositoryId: repo.RepositoryId,
Labels: repo.Labels,
// getting SHA of the branch requires a separate GetBranch call.
// too expensive. for now, we just don't support it.
// SHA: "",
})
} else {
input := &codecommit.ListBranchesInput{
RepositoryName: aws.String(repo.Repository),
}
for {
output, err := p.codeCommitClient.ListBranchesWithContext(ctx, input)
if err != nil {
return nil, err
}
for _, branch := range output.Branches {
repos = append(repos, &Repository{
Organization: repo.Organization,
Repository: repo.Repository,
URL: repo.URL,
Branch: aws.StringValue(branch),
RepositoryId: repo.RepositoryId,
Labels: repo.Labels,
// getting SHA of the branch requires a separate GetBranch call.
// too expensive. for now, we just don't support it.
// SHA: "",
})
}
input.NextToken = output.NextToken
if aws.StringValue(output.NextToken) == "" {
break
}
}
}
return repos, nil
}
func (p *AWSCodeCommitProvider) listRepoNames(ctx context.Context) ([]string, error) {
tagFilters := p.getTagFilters()
repoNames := make([]string, 0)
var err error
if len(tagFilters) < 1 {
log.Debugf("no tag filer, calling codecommit api to list repos")
listReposInput := &codecommit.ListRepositoriesInput{}
var output *codecommit.ListRepositoriesOutput
for {
output, err = p.codeCommitClient.ListRepositoriesWithContext(ctx, listReposInput)
if err != nil {
break
}
for _, repo := range output.Repositories {
repoNames = append(repoNames, aws.StringValue(repo.RepositoryName))
}
listReposInput.NextToken = output.NextToken
if aws.StringValue(output.NextToken) == "" {
break
}
}
} else {
log.Debugf("tag filer is specified, calling tagging api to list repos")
discoveryInput := &resourcegroupstaggingapi.GetResourcesInput{
ResourceTypeFilters: aws.StringSlice([]string{resourceTypeCodeCommitRepository}),
TagFilters: tagFilters,
}
var output *resourcegroupstaggingapi.GetResourcesOutput
for {
output, err = p.taggingClient.GetResourcesWithContext(ctx, discoveryInput)
if err != nil {
break
}
for _, resource := range output.ResourceTagMappingList {
repoArn := aws.StringValue(resource.ResourceARN)
log.Debugf("discovered codecommit repo with arn %s", repoArn)
repoName, extractErr := getCodeCommitRepoName(repoArn)
if extractErr != nil {
log.Warnf("discovered codecommit repoArn %s cannot be parsed due to %v", repoArn, err)
continue
}
repoNames = append(repoNames, repoName)
}
discoveryInput.PaginationToken = output.PaginationToken
if aws.StringValue(output.PaginationToken) == "" {
break
}
}
}
return repoNames, err
}
func (p *AWSCodeCommitProvider) getTagFilters() []*resourcegroupstaggingapi.TagFilter {
filters := make(map[string]*resourcegroupstaggingapi.TagFilter)
for _, tagFilter := range p.tagFilters {
filter, hasKey := filters[tagFilter.Key]
if !hasKey {
filter = &resourcegroupstaggingapi.TagFilter{
Key: aws.String(tagFilter.Key),
}
filters[tagFilter.Key] = filter
}
if tagFilter.Value != "" {
filter.Values = append(filter.Values, aws.String(tagFilter.Value))
}
}
return maps.Values(filters)
}
func getCodeCommitRepoName(repoArn string) (string, error) {
parsedArn, err := arn.Parse(repoArn)
if err != nil {
return "", fmt.Errorf("failed to parse codecommit repository ARN: %w", err)
}
// see: https://docs.aws.amazon.com/codecommit/latest/userguide/auth-and-access-control-permissions-reference.html
// arn:aws:codecommit:region:account-id:repository-name
return parsedArn.Resource, nil
}
// getCodeCommitFIPSEndpoint transforms provided https:// codecommit URL to a FIPS-compliant endpoint.
// note that the specified region must support FIPS, otherwise the returned URL won't be reachable
// see: https://docs.aws.amazon.com/codecommit/latest/userguide/regions.html#regions-git
func getCodeCommitFIPSEndpoint(repoUrl string) (string, error) {
if strings.HasPrefix(repoUrl, prefixGitUrlHttpsFIPS) {
log.Debugf("provided repoUrl %s is already a fips endpoint", repoUrl)
return repoUrl, nil
}
if !strings.HasPrefix(repoUrl, prefixGitUrlHttps) {
return "", fmt.Errorf("the provided https endpoint isn't recognized, cannot be transformed to FIPS endpoint: %s", repoUrl)
}
// we already have the prefix, so we guarantee to replace exactly the prefix only.
return strings.Replace(repoUrl, prefixGitUrlHttps, prefixGitUrlHttpsFIPS, 1), nil
}
func hasAwsError(err error, codes ...string) bool {
if awsErr, ok := err.(awserr.Error); ok {
return slices.Contains(codes, awsErr.Code())
}
return false
}
// toAbsolutePath transforms a path input to absolute path, as required by AWS CodeCommit
// see https://docs.aws.amazon.com/codecommit/latest/APIReference/API_GetFolder.html
func toAbsolutePath(path string) string {
if filepath.IsAbs(path) {
return path
}
return filepath.ToSlash(filepath.Join("/", path))
}
func createAWSDiscoveryClients(_ context.Context, role string, region string) (*resourcegroupstaggingapi.ResourceGroupsTaggingAPI, *codecommit.CodeCommit, error) {
podSession, err := session.NewSession()
if err != nil {
return nil, nil, fmt.Errorf("error creating new AWS pod session: %w", err)
}
discoverySession := podSession
// assume role if provided - this allows cross account CodeCommit repo discovery.
if role != "" {
log.Debugf("role %s is provided for AWS CodeCommit discovery", role)
assumeRoleCreds := stscreds.NewCredentials(podSession, role)
discoverySession, err = session.NewSession(&aws.Config{
Credentials: assumeRoleCreds,
})
if err != nil {
return nil, nil, fmt.Errorf("error creating new AWS discovery session: %s", err)
}
} else {
log.Debugf("role is not provided for AWS CodeCommit discovery, using pod role")
}
// use region explicitly if provided - this allows cross region CodeCommit repo discovery.
if region != "" {
log.Debugf("region %s is provided for AWS CodeCommit discovery", region)
discoverySession = discoverySession.Copy(&aws.Config{
Region: aws.String(region),
})
} else {
log.Debugf("region is not provided for AWS CodeCommit discovery, using pod region")
}
taggingClient := resourcegroupstaggingapi.New(discoverySession)
codeCommitClient := codecommit.New(discoverySession)
return taggingClient, codeCommitClient, nil
}

View File

@@ -1,321 +0,0 @@
// Code generated by mockery v2.26.1. DO NOT EDIT.
package mocks
import (
context "context"
codecommit "github.com/aws/aws-sdk-go/service/codecommit"
mock "github.com/stretchr/testify/mock"
request "github.com/aws/aws-sdk-go/aws/request"
)
// AWSCodeCommitClient is an autogenerated mock type for the AWSCodeCommitClient type
type AWSCodeCommitClient struct {
mock.Mock
}
type AWSCodeCommitClient_Expecter struct {
mock *mock.Mock
}
func (_m *AWSCodeCommitClient) EXPECT() *AWSCodeCommitClient_Expecter {
return &AWSCodeCommitClient_Expecter{mock: &_m.Mock}
}
// GetFolderWithContext provides a mock function with given fields: _a0, _a1, _a2
func (_m *AWSCodeCommitClient) GetFolderWithContext(_a0 context.Context, _a1 *codecommit.GetFolderInput, _a2 ...request.Option) (*codecommit.GetFolderOutput, error) {
_va := make([]interface{}, len(_a2))
for _i := range _a2 {
_va[_i] = _a2[_i]
}
var _ca []interface{}
_ca = append(_ca, _a0, _a1)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *codecommit.GetFolderOutput
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.GetFolderInput, ...request.Option) (*codecommit.GetFolderOutput, error)); ok {
return rf(_a0, _a1, _a2...)
}
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.GetFolderInput, ...request.Option) *codecommit.GetFolderOutput); ok {
r0 = rf(_a0, _a1, _a2...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*codecommit.GetFolderOutput)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *codecommit.GetFolderInput, ...request.Option) error); ok {
r1 = rf(_a0, _a1, _a2...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AWSCodeCommitClient_GetFolderWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFolderWithContext'
type AWSCodeCommitClient_GetFolderWithContext_Call struct {
*mock.Call
}
// GetFolderWithContext is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *codecommit.GetFolderInput
// - _a2 ...request.Option
func (_e *AWSCodeCommitClient_Expecter) GetFolderWithContext(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *AWSCodeCommitClient_GetFolderWithContext_Call {
return &AWSCodeCommitClient_GetFolderWithContext_Call{Call: _e.mock.On("GetFolderWithContext",
append([]interface{}{_a0, _a1}, _a2...)...)}
}
func (_c *AWSCodeCommitClient_GetFolderWithContext_Call) Run(run func(_a0 context.Context, _a1 *codecommit.GetFolderInput, _a2 ...request.Option)) *AWSCodeCommitClient_GetFolderWithContext_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]request.Option, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(request.Option)
}
}
run(args[0].(context.Context), args[1].(*codecommit.GetFolderInput), variadicArgs...)
})
return _c
}
func (_c *AWSCodeCommitClient_GetFolderWithContext_Call) Return(_a0 *codecommit.GetFolderOutput, _a1 error) *AWSCodeCommitClient_GetFolderWithContext_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AWSCodeCommitClient_GetFolderWithContext_Call) RunAndReturn(run func(context.Context, *codecommit.GetFolderInput, ...request.Option) (*codecommit.GetFolderOutput, error)) *AWSCodeCommitClient_GetFolderWithContext_Call {
_c.Call.Return(run)
return _c
}
// GetRepositoryWithContext provides a mock function with given fields: _a0, _a1, _a2
func (_m *AWSCodeCommitClient) GetRepositoryWithContext(_a0 context.Context, _a1 *codecommit.GetRepositoryInput, _a2 ...request.Option) (*codecommit.GetRepositoryOutput, error) {
_va := make([]interface{}, len(_a2))
for _i := range _a2 {
_va[_i] = _a2[_i]
}
var _ca []interface{}
_ca = append(_ca, _a0, _a1)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *codecommit.GetRepositoryOutput
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.GetRepositoryInput, ...request.Option) (*codecommit.GetRepositoryOutput, error)); ok {
return rf(_a0, _a1, _a2...)
}
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.GetRepositoryInput, ...request.Option) *codecommit.GetRepositoryOutput); ok {
r0 = rf(_a0, _a1, _a2...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*codecommit.GetRepositoryOutput)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *codecommit.GetRepositoryInput, ...request.Option) error); ok {
r1 = rf(_a0, _a1, _a2...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AWSCodeCommitClient_GetRepositoryWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRepositoryWithContext'
type AWSCodeCommitClient_GetRepositoryWithContext_Call struct {
*mock.Call
}
// GetRepositoryWithContext is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *codecommit.GetRepositoryInput
// - _a2 ...request.Option
func (_e *AWSCodeCommitClient_Expecter) GetRepositoryWithContext(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
return &AWSCodeCommitClient_GetRepositoryWithContext_Call{Call: _e.mock.On("GetRepositoryWithContext",
append([]interface{}{_a0, _a1}, _a2...)...)}
}
func (_c *AWSCodeCommitClient_GetRepositoryWithContext_Call) Run(run func(_a0 context.Context, _a1 *codecommit.GetRepositoryInput, _a2 ...request.Option)) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]request.Option, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(request.Option)
}
}
run(args[0].(context.Context), args[1].(*codecommit.GetRepositoryInput), variadicArgs...)
})
return _c
}
func (_c *AWSCodeCommitClient_GetRepositoryWithContext_Call) Return(_a0 *codecommit.GetRepositoryOutput, _a1 error) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AWSCodeCommitClient_GetRepositoryWithContext_Call) RunAndReturn(run func(context.Context, *codecommit.GetRepositoryInput, ...request.Option) (*codecommit.GetRepositoryOutput, error)) *AWSCodeCommitClient_GetRepositoryWithContext_Call {
_c.Call.Return(run)
return _c
}
// ListBranchesWithContext provides a mock function with given fields: _a0, _a1, _a2
func (_m *AWSCodeCommitClient) ListBranchesWithContext(_a0 context.Context, _a1 *codecommit.ListBranchesInput, _a2 ...request.Option) (*codecommit.ListBranchesOutput, error) {
_va := make([]interface{}, len(_a2))
for _i := range _a2 {
_va[_i] = _a2[_i]
}
var _ca []interface{}
_ca = append(_ca, _a0, _a1)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *codecommit.ListBranchesOutput
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.ListBranchesInput, ...request.Option) (*codecommit.ListBranchesOutput, error)); ok {
return rf(_a0, _a1, _a2...)
}
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.ListBranchesInput, ...request.Option) *codecommit.ListBranchesOutput); ok {
r0 = rf(_a0, _a1, _a2...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*codecommit.ListBranchesOutput)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *codecommit.ListBranchesInput, ...request.Option) error); ok {
r1 = rf(_a0, _a1, _a2...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AWSCodeCommitClient_ListBranchesWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListBranchesWithContext'
type AWSCodeCommitClient_ListBranchesWithContext_Call struct {
*mock.Call
}
// ListBranchesWithContext is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *codecommit.ListBranchesInput
// - _a2 ...request.Option
func (_e *AWSCodeCommitClient_Expecter) ListBranchesWithContext(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *AWSCodeCommitClient_ListBranchesWithContext_Call {
return &AWSCodeCommitClient_ListBranchesWithContext_Call{Call: _e.mock.On("ListBranchesWithContext",
append([]interface{}{_a0, _a1}, _a2...)...)}
}
func (_c *AWSCodeCommitClient_ListBranchesWithContext_Call) Run(run func(_a0 context.Context, _a1 *codecommit.ListBranchesInput, _a2 ...request.Option)) *AWSCodeCommitClient_ListBranchesWithContext_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]request.Option, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(request.Option)
}
}
run(args[0].(context.Context), args[1].(*codecommit.ListBranchesInput), variadicArgs...)
})
return _c
}
func (_c *AWSCodeCommitClient_ListBranchesWithContext_Call) Return(_a0 *codecommit.ListBranchesOutput, _a1 error) *AWSCodeCommitClient_ListBranchesWithContext_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AWSCodeCommitClient_ListBranchesWithContext_Call) RunAndReturn(run func(context.Context, *codecommit.ListBranchesInput, ...request.Option) (*codecommit.ListBranchesOutput, error)) *AWSCodeCommitClient_ListBranchesWithContext_Call {
_c.Call.Return(run)
return _c
}
// ListRepositoriesWithContext provides a mock function with given fields: _a0, _a1, _a2
func (_m *AWSCodeCommitClient) ListRepositoriesWithContext(_a0 context.Context, _a1 *codecommit.ListRepositoriesInput, _a2 ...request.Option) (*codecommit.ListRepositoriesOutput, error) {
_va := make([]interface{}, len(_a2))
for _i := range _a2 {
_va[_i] = _a2[_i]
}
var _ca []interface{}
_ca = append(_ca, _a0, _a1)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *codecommit.ListRepositoriesOutput
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.ListRepositoriesInput, ...request.Option) (*codecommit.ListRepositoriesOutput, error)); ok {
return rf(_a0, _a1, _a2...)
}
if rf, ok := ret.Get(0).(func(context.Context, *codecommit.ListRepositoriesInput, ...request.Option) *codecommit.ListRepositoriesOutput); ok {
r0 = rf(_a0, _a1, _a2...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*codecommit.ListRepositoriesOutput)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *codecommit.ListRepositoriesInput, ...request.Option) error); ok {
r1 = rf(_a0, _a1, _a2...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AWSCodeCommitClient_ListRepositoriesWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListRepositoriesWithContext'
type AWSCodeCommitClient_ListRepositoriesWithContext_Call struct {
*mock.Call
}
// ListRepositoriesWithContext is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *codecommit.ListRepositoriesInput
// - _a2 ...request.Option
func (_e *AWSCodeCommitClient_Expecter) ListRepositoriesWithContext(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
return &AWSCodeCommitClient_ListRepositoriesWithContext_Call{Call: _e.mock.On("ListRepositoriesWithContext",
append([]interface{}{_a0, _a1}, _a2...)...)}
}
func (_c *AWSCodeCommitClient_ListRepositoriesWithContext_Call) Run(run func(_a0 context.Context, _a1 *codecommit.ListRepositoriesInput, _a2 ...request.Option)) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]request.Option, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(request.Option)
}
}
run(args[0].(context.Context), args[1].(*codecommit.ListRepositoriesInput), variadicArgs...)
})
return _c
}
func (_c *AWSCodeCommitClient_ListRepositoriesWithContext_Call) Return(_a0 *codecommit.ListRepositoriesOutput, _a1 error) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AWSCodeCommitClient_ListRepositoriesWithContext_Call) RunAndReturn(run func(context.Context, *codecommit.ListRepositoriesInput, ...request.Option) (*codecommit.ListRepositoriesOutput, error)) *AWSCodeCommitClient_ListRepositoriesWithContext_Call {
_c.Call.Return(run)
return _c
}
type mockConstructorTestingTNewAWSCodeCommitClient interface {
mock.TestingT
Cleanup(func())
}
// NewAWSCodeCommitClient creates a new instance of AWSCodeCommitClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewAWSCodeCommitClient(t mockConstructorTestingTNewAWSCodeCommitClient) *AWSCodeCommitClient {
mock := &AWSCodeCommitClient{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@@ -1,110 +0,0 @@
// Code generated by mockery v2.26.1. DO NOT EDIT.
package mocks
import (
context "context"
request "github.com/aws/aws-sdk-go/aws/request"
mock "github.com/stretchr/testify/mock"
resourcegroupstaggingapi "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
)
// AWSTaggingClient is an autogenerated mock type for the AWSTaggingClient type
type AWSTaggingClient struct {
mock.Mock
}
type AWSTaggingClient_Expecter struct {
mock *mock.Mock
}
func (_m *AWSTaggingClient) EXPECT() *AWSTaggingClient_Expecter {
return &AWSTaggingClient_Expecter{mock: &_m.Mock}
}
// GetResourcesWithContext provides a mock function with given fields: _a0, _a1, _a2
func (_m *AWSTaggingClient) GetResourcesWithContext(_a0 context.Context, _a1 *resourcegroupstaggingapi.GetResourcesInput, _a2 ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error) {
_va := make([]interface{}, len(_a2))
for _i := range _a2 {
_va[_i] = _a2[_i]
}
var _ca []interface{}
_ca = append(_ca, _a0, _a1)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *resourcegroupstaggingapi.GetResourcesOutput
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error)); ok {
return rf(_a0, _a1, _a2...)
}
if rf, ok := ret.Get(0).(func(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) *resourcegroupstaggingapi.GetResourcesOutput); ok {
r0 = rf(_a0, _a1, _a2...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*resourcegroupstaggingapi.GetResourcesOutput)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) error); ok {
r1 = rf(_a0, _a1, _a2...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AWSTaggingClient_GetResourcesWithContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetResourcesWithContext'
type AWSTaggingClient_GetResourcesWithContext_Call struct {
*mock.Call
}
// GetResourcesWithContext is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *resourcegroupstaggingapi.GetResourcesInput
// - _a2 ...request.Option
func (_e *AWSTaggingClient_Expecter) GetResourcesWithContext(_a0 interface{}, _a1 interface{}, _a2 ...interface{}) *AWSTaggingClient_GetResourcesWithContext_Call {
return &AWSTaggingClient_GetResourcesWithContext_Call{Call: _e.mock.On("GetResourcesWithContext",
append([]interface{}{_a0, _a1}, _a2...)...)}
}
func (_c *AWSTaggingClient_GetResourcesWithContext_Call) Run(run func(_a0 context.Context, _a1 *resourcegroupstaggingapi.GetResourcesInput, _a2 ...request.Option)) *AWSTaggingClient_GetResourcesWithContext_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]request.Option, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(request.Option)
}
}
run(args[0].(context.Context), args[1].(*resourcegroupstaggingapi.GetResourcesInput), variadicArgs...)
})
return _c
}
func (_c *AWSTaggingClient_GetResourcesWithContext_Call) Return(_a0 *resourcegroupstaggingapi.GetResourcesOutput, _a1 error) *AWSTaggingClient_GetResourcesWithContext_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *AWSTaggingClient_GetResourcesWithContext_Call) RunAndReturn(run func(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...request.Option) (*resourcegroupstaggingapi.GetResourcesOutput, error)) *AWSTaggingClient_GetResourcesWithContext_Call {
_c.Call.Return(run)
return _c
}
type mockConstructorTestingTNewAWSTaggingClient interface {
mock.TestingT
Cleanup(func())
}
// NewAWSTaggingClient creates a new instance of AWSTaggingClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewAWSTaggingClient(t mockConstructorTestingTNewAWSTaggingClient) *AWSTaggingClient {
mock := &AWSTaggingClient{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@@ -1,483 +0,0 @@
package scm_provider
import (
"context"
"errors"
"sort"
"testing"
"github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider/aws_codecommit/mocks"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/codecommit"
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
type awsCodeCommitTestRepository struct {
name string
id string
arn string
accountId string
defaultBranch string
expectedCloneUrl string
getRepositoryError error
getRepositoryNilMetadata bool
valid bool
}
func TestAWSCodeCommitListRepos(t *testing.T) {
testCases := []struct {
name string
repositories []*awsCodeCommitTestRepository
cloneProtocol string
tagFilters []*v1alpha1.TagFilter
expectTagFilters []*resourcegroupstaggingapi.TagFilter
listRepositoryError error
expectOverallError bool
expectListAtCodeCommit bool
}{
{
name: "ListRepos by tag with https",
cloneProtocol: "https",
repositories: []*awsCodeCommitTestRepository{
{
name: "repo1",
id: "8235624d-d248-4df9-a983-2558b01dbe83",
arn: "arn:aws:codecommit:us-east-1:111111111111:repo1",
defaultBranch: "main",
expectedCloneUrl: "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/repo1",
valid: true,
},
},
tagFilters: []*v1alpha1.TagFilter{
{Key: "key1", Value: "value1"},
{Key: "key1", Value: "value2"},
{Key: "key2"},
},
expectTagFilters: []*resourcegroupstaggingapi.TagFilter{
{Key: aws.String("key1"), Values: aws.StringSlice([]string{"value1", "value2"})},
{Key: aws.String("key2")},
},
expectOverallError: false,
expectListAtCodeCommit: false,
},
{
name: "ListRepos by tag with https-fips",
cloneProtocol: "https-fips",
repositories: []*awsCodeCommitTestRepository{
{
name: "repo1",
id: "8235624d-d248-4df9-a983-2558b01dbe83",
arn: "arn:aws:codecommit:us-east-1:111111111111:repo1",
defaultBranch: "main",
expectedCloneUrl: "https://git-codecommit-fips.us-east-1.amazonaws.com/v1/repos/repo1",
valid: true,
},
},
tagFilters: []*v1alpha1.TagFilter{
{Key: "key1"},
},
expectTagFilters: []*resourcegroupstaggingapi.TagFilter{
{Key: aws.String("key1")},
},
expectOverallError: false,
expectListAtCodeCommit: false,
},
{
name: "ListRepos without tag with invalid repo",
cloneProtocol: "ssh",
repositories: []*awsCodeCommitTestRepository{
{
name: "repo1",
id: "8235624d-d248-4df9-a983-2558b01dbe83",
arn: "arn:aws:codecommit:us-east-1:111111111111:repo1",
defaultBranch: "main",
expectedCloneUrl: "ssh://git-codecommit.us-east-1.amazonaws.com/v1/repos/repo1",
valid: true,
},
{
name: "repo2",
id: "640d5859-d265-4e27-a9fa-e0731eb13ed7",
arn: "arn:aws:codecommit:us-east-1:111111111111:repo2",
valid: false,
},
{
name: "repo3-nil-metadata",
id: "24a6ee96-d3a0-4be6-a595-c5e5b1ab1617",
arn: "arn:aws:codecommit:us-east-1:111111111111:repo3-nil-metadata",
getRepositoryNilMetadata: true,
valid: false,
},
},
expectOverallError: false,
expectListAtCodeCommit: true,
},
{
name: "ListRepos with invalid protocol",
cloneProtocol: "invalid-protocol",
repositories: []*awsCodeCommitTestRepository{
{
name: "repo1",
id: "8235624d-d248-4df9-a983-2558b01dbe83",
arn: "arn:aws:codecommit:us-east-1:111111111111:repo1",
defaultBranch: "main",
valid: true,
},
},
expectOverallError: true,
expectListAtCodeCommit: true,
},
{
name: "ListRepos error on listRepos",
cloneProtocol: "https",
listRepositoryError: errors.New("list repo error"),
expectOverallError: true,
expectListAtCodeCommit: true,
},
{
name: "ListRepos error on getRepo",
cloneProtocol: "https",
repositories: []*awsCodeCommitTestRepository{
{
name: "repo1",
id: "8235624d-d248-4df9-a983-2558b01dbe83",
arn: "arn:aws:codecommit:us-east-1:111111111111:repo1",
defaultBranch: "main",
getRepositoryError: errors.New("get repo error"),
valid: true,
},
},
expectOverallError: true,
expectListAtCodeCommit: true,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
codeCommitClient := mocks.NewAWSCodeCommitClient(t)
taggingClient := mocks.NewAWSTaggingClient(t)
ctx := context.Background()
codecommitRepoNameIdPairs := make([]*codecommit.RepositoryNameIdPair, 0)
resourceTaggings := make([]*resourcegroupstaggingapi.ResourceTagMapping, 0)
validRepositories := make([]*awsCodeCommitTestRepository, 0)
for _, repo := range testCase.repositories {
repoMetadata := &codecommit.RepositoryMetadata{
AccountId: aws.String(repo.accountId),
Arn: aws.String(repo.arn),
CloneUrlHttp: aws.String("https://git-codecommit.us-east-1.amazonaws.com/v1/repos/" + repo.name),
CloneUrlSsh: aws.String("ssh://git-codecommit.us-east-1.amazonaws.com/v1/repos/" + repo.name),
DefaultBranch: aws.String(repo.defaultBranch),
RepositoryId: aws.String(repo.id),
RepositoryName: aws.String(repo.name),
}
if repo.getRepositoryNilMetadata {
repoMetadata = nil
}
codeCommitClient.EXPECT().
GetRepositoryWithContext(ctx, &codecommit.GetRepositoryInput{RepositoryName: aws.String(repo.name)}).
Return(&codecommit.GetRepositoryOutput{RepositoryMetadata: repoMetadata}, repo.getRepositoryError)
codecommitRepoNameIdPairs = append(codecommitRepoNameIdPairs, &codecommit.RepositoryNameIdPair{
RepositoryId: aws.String(repo.id),
RepositoryName: aws.String(repo.name),
})
resourceTaggings = append(resourceTaggings, &resourcegroupstaggingapi.ResourceTagMapping{
ResourceARN: aws.String(repo.arn),
})
if repo.valid {
validRepositories = append(validRepositories, repo)
}
}
if testCase.expectListAtCodeCommit {
codeCommitClient.EXPECT().
ListRepositoriesWithContext(ctx, &codecommit.ListRepositoriesInput{}).
Return(&codecommit.ListRepositoriesOutput{
Repositories: codecommitRepoNameIdPairs,
}, testCase.listRepositoryError)
} else {
taggingClient.EXPECT().
GetResourcesWithContext(ctx, mock.MatchedBy(equalIgnoringTagFilterOrder(&resourcegroupstaggingapi.GetResourcesInput{
TagFilters: testCase.expectTagFilters,
ResourceTypeFilters: aws.StringSlice([]string{resourceTypeCodeCommitRepository}),
}))).
Return(&resourcegroupstaggingapi.GetResourcesOutput{
ResourceTagMappingList: resourceTaggings,
}, testCase.listRepositoryError)
}
provider := &AWSCodeCommitProvider{
codeCommitClient: codeCommitClient,
taggingClient: taggingClient,
tagFilters: testCase.tagFilters,
}
repos, err := provider.ListRepos(ctx, testCase.cloneProtocol)
if testCase.expectOverallError {
assert.Error(t, err)
} else {
assert.Len(t, repos, len(validRepositories))
for i, repo := range repos {
originRepo := validRepositories[i]
assert.Equal(t, originRepo.accountId, repo.Organization)
assert.Equal(t, originRepo.name, repo.Repository)
assert.Equal(t, originRepo.id, repo.RepositoryId)
assert.Equal(t, originRepo.defaultBranch, repo.Branch)
assert.Equal(t, originRepo.expectedCloneUrl, repo.URL)
assert.Empty(t, repo.SHA, "SHA is always empty")
}
}
})
}
}
func TestAWSCodeCommitRepoHasPath(t *testing.T) {
organization := "111111111111"
repoName := "repo1"
branch := "main"
testCases := []struct {
name string
path string
expectedGetFolderPath string
getFolderOutput *codecommit.GetFolderOutput
getFolderError error
expectOverallError bool
expectedResult bool
}{
{
name: "RepoHasPath on regular file",
path: "lib/config.yaml",
expectedGetFolderPath: "/lib",
getFolderOutput: &codecommit.GetFolderOutput{
Files: []*codecommit.File{
{RelativePath: aws.String("config.yaml")},
},
},
expectOverallError: false,
expectedResult: true,
},
{
name: "RepoHasPath on folder",
path: "lib/config",
expectedGetFolderPath: "/lib",
getFolderOutput: &codecommit.GetFolderOutput{
SubFolders: []*codecommit.Folder{
{RelativePath: aws.String("config")},
},
},
expectOverallError: false,
expectedResult: true,
},
{
name: "RepoHasPath on submodules",
path: "/lib/submodule/",
expectedGetFolderPath: "/lib",
getFolderOutput: &codecommit.GetFolderOutput{
SubModules: []*codecommit.SubModule{
{RelativePath: aws.String("submodule")},
},
},
expectOverallError: false,
expectedResult: true,
},
{
name: "RepoHasPath on symlink",
path: "./lib/service.json",
expectedGetFolderPath: "/lib",
getFolderOutput: &codecommit.GetFolderOutput{
SymbolicLinks: []*codecommit.SymbolicLink{
{RelativePath: aws.String("service.json")},
},
},
expectOverallError: false,
expectedResult: true,
},
{
name: "RepoHasPath when no match",
path: "no-match.json",
expectedGetFolderPath: "/",
getFolderOutput: &codecommit.GetFolderOutput{
Files: []*codecommit.File{
{RelativePath: aws.String("config.yaml")},
},
SubFolders: []*codecommit.Folder{
{RelativePath: aws.String("config")},
},
SubModules: []*codecommit.SubModule{
{RelativePath: aws.String("submodule")},
},
SymbolicLinks: []*codecommit.SymbolicLink{
{RelativePath: aws.String("service.json")},
},
},
expectOverallError: false,
expectedResult: false,
},
{
name: "RepoHasPath when parent folder not found",
path: "lib/submodule",
expectedGetFolderPath: "/lib",
getFolderError: &codecommit.FolderDoesNotExistException{},
expectOverallError: false,
},
{
name: "RepoHasPath when unknown error",
path: "lib/submodule",
expectedGetFolderPath: "/lib",
getFolderError: errors.New("unknown error"),
expectOverallError: true,
},
{
name: "RepoHasPath on root folder - './'",
path: "./",
expectOverallError: false,
expectedResult: true,
},
{
name: "RepoHasPath on root folder - '/'",
path: "/",
expectOverallError: false,
expectedResult: true,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
codeCommitClient := mocks.NewAWSCodeCommitClient(t)
taggingClient := mocks.NewAWSTaggingClient(t)
ctx := context.Background()
if testCase.expectedGetFolderPath != "" {
codeCommitClient.EXPECT().
GetFolderWithContext(ctx, &codecommit.GetFolderInput{
CommitSpecifier: aws.String(branch),
FolderPath: aws.String(testCase.expectedGetFolderPath),
RepositoryName: aws.String(repoName),
}).
Return(testCase.getFolderOutput, testCase.getFolderError)
}
provider := &AWSCodeCommitProvider{
codeCommitClient: codeCommitClient,
taggingClient: taggingClient,
}
actual, err := provider.RepoHasPath(ctx, &Repository{
Organization: organization,
Repository: repoName,
Branch: branch,
}, testCase.path)
if testCase.expectOverallError {
assert.Error(t, err)
} else {
assert.Equal(t, testCase.expectedResult, actual)
}
})
}
}
func TestAWSCodeCommitGetBranches(t *testing.T) {
name := "repo1"
id := "1a64adc4-2fb5-4abd-afe7-127984ba83c0"
defaultBranch := "main"
organization := "111111111111"
cloneUrl := "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/repo1"
testCases := []struct {
name string
branches []string
apiError error
expectOverallError bool
allBranches bool
}{
{
name: "GetBranches all branches",
branches: []string{"main", "feature/codecommit", "chore/go-upgrade"},
allBranches: true,
},
{
name: "GetBranches default branch only",
allBranches: false,
},
{
name: "GetBranches default branch only",
allBranches: false,
},
{
name: "GetBranches all branches on api error",
apiError: errors.New("api error"),
expectOverallError: true,
allBranches: true,
},
{
name: "GetBranches default branch on api error",
apiError: errors.New("api error"),
expectOverallError: true,
allBranches: false,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
codeCommitClient := mocks.NewAWSCodeCommitClient(t)
taggingClient := mocks.NewAWSTaggingClient(t)
ctx := context.Background()
if testCase.allBranches {
codeCommitClient.EXPECT().
ListBranchesWithContext(ctx, &codecommit.ListBranchesInput{
RepositoryName: aws.String(name),
}).
Return(&codecommit.ListBranchesOutput{Branches: aws.StringSlice(testCase.branches)}, testCase.apiError)
} else {
codeCommitClient.EXPECT().
GetRepositoryWithContext(ctx, &codecommit.GetRepositoryInput{RepositoryName: aws.String(name)}).
Return(&codecommit.GetRepositoryOutput{RepositoryMetadata: &codecommit.RepositoryMetadata{
AccountId: aws.String(organization),
DefaultBranch: aws.String(defaultBranch),
}}, testCase.apiError)
}
provider := &AWSCodeCommitProvider{
codeCommitClient: codeCommitClient,
taggingClient: taggingClient,
allBranches: testCase.allBranches,
}
actual, err := provider.GetBranches(ctx, &Repository{
Organization: organization,
Repository: name,
URL: cloneUrl,
RepositoryId: id,
})
if testCase.expectOverallError {
assert.Error(t, err)
} else {
assertCopiedProperties := func(repo *Repository) {
assert.Equal(t, id, repo.RepositoryId)
assert.Equal(t, name, repo.Repository)
assert.Equal(t, cloneUrl, repo.URL)
assert.Equal(t, organization, repo.Organization)
assert.Empty(t, repo.SHA)
}
actualBranches := make([]string, 0)
for _, repo := range actual {
assertCopiedProperties(repo)
actualBranches = append(actualBranches, repo.Branch)
}
if testCase.allBranches {
assert.ElementsMatch(t, testCase.branches, actualBranches)
} else {
assert.ElementsMatch(t, []string{defaultBranch}, actualBranches)
}
}
})
}
}
// equalIgnoringTagFilterOrder provides an argumentMatcher function that can be used to compare equality of GetResourcesInput ignoring the tagFilter ordering.
func equalIgnoringTagFilterOrder(expected *resourcegroupstaggingapi.GetResourcesInput) func(*resourcegroupstaggingapi.GetResourcesInput) bool {
return func(actual *resourcegroupstaggingapi.GetResourcesInput) bool {
sort.Slice(actual.TagFilters, func(i, j int) bool {
return *actual.TagFilters[i].Key < *actual.TagFilters[j].Key
})
return cmp.Equal(expected, actual)
}
}

View File

@@ -3,11 +3,11 @@ package scm_provider
import (
"context"
"fmt"
"net/http"
"os"
"net/http"
pathpkg "path"
"github.com/xanzy/go-gitlab"
gitlab "github.com/xanzy/go-gitlab"
)
type GitlabProvider struct {
@@ -65,7 +65,7 @@ func (g *GitlabProvider) GetBranches(ctx context.Context, repo *Repository) ([]*
func (g *GitlabProvider) ListRepos(ctx context.Context, cloneProtocol string) ([]*Repository, error) {
opt := &gitlab.ListGroupProjectsOptions{
ListOptions: gitlab.ListOptions{PerPage: 100},
IncludeSubGroups: &g.includeSubgroups,
IncludeSubgroups: &g.includeSubgroups,
}
repos := []*Repository{}
for {

View File

@@ -0,0 +1,34 @@
package test
import (
"context"
"github.com/stretchr/testify/mock"
)
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

@@ -31,7 +31,7 @@ func init() {
}
type Renderer interface {
RenderTemplateParams(tmpl *argoappsv1.Application, syncPolicy *argoappsv1.ApplicationSetSyncPolicy, params map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (*argoappsv1.Application, error)
RenderTemplateParams(tmpl *argoappsv1.Application, syncPolicy *argoappsv1.ApplicationSetSyncPolicy, params map[string]interface{}, useGoTemplate bool) (*argoappsv1.Application, error)
}
type Render struct {
@@ -50,7 +50,7 @@ func copyUnexported(copy, original reflect.Value) {
// This function is in charge of searching all String fields of the object recursively and apply templating
// thanks to https://gist.github.com/randallmlough/1fd78ec8a1034916ca52281e3b886dc7
func (r *Render) deeplyReplace(copy, original reflect.Value, replaceMap map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) error {
func (r *Render) deeplyReplace(copy, original reflect.Value, replaceMap map[string]interface{}, useGoTemplate bool) error {
switch original.Kind() {
// The first cases handle nested structures and translate them recursively
// If it is a pointer we need to unwrap and call once again
@@ -70,7 +70,7 @@ func (r *Render) deeplyReplace(copy, original reflect.Value, replaceMap map[stri
copyUnexported(copy, original)
}
// Unwrap the newly created pointer
if err := r.deeplyReplace(copy.Elem(), originalValue, replaceMap, useGoTemplate, goTemplateOptions); err != nil {
if err := r.deeplyReplace(copy.Elem(), originalValue, replaceMap, useGoTemplate); err != nil {
return err
}
@@ -84,7 +84,7 @@ func (r *Render) deeplyReplace(copy, original reflect.Value, replaceMap map[stri
// Create a new object. Now new gives us a pointer, but we want the value it
// points to, so we have to call Elem() to unwrap it
copyValue := reflect.New(originalValue.Type()).Elem()
if err := r.deeplyReplace(copyValue, originalValue, replaceMap, useGoTemplate, goTemplateOptions); err != nil {
if err := r.deeplyReplace(copyValue, originalValue, replaceMap, useGoTemplate); err != nil {
return err
}
copy.Set(copyValue)
@@ -105,7 +105,7 @@ func (r *Render) deeplyReplace(copy, original reflect.Value, replaceMap map[stri
}
jsonOriginal := reflect.ValueOf(&unmarshaled)
jsonCopy := reflect.New(jsonOriginal.Type()).Elem()
err = r.deeplyReplace(jsonCopy, jsonOriginal, replaceMap, useGoTemplate, goTemplateOptions)
err = r.deeplyReplace(jsonCopy, jsonOriginal, replaceMap, useGoTemplate)
if err != nil {
return fmt.Errorf("failed to deeply replace JSON field contents: %w", err)
}
@@ -115,7 +115,7 @@ func (r *Render) deeplyReplace(copy, original reflect.Value, replaceMap map[stri
return fmt.Errorf("failed to marshal templated JSON field: %w", err)
}
copy.Field(i).Set(reflect.ValueOf(data))
} else if err := r.deeplyReplace(copy.Field(i), original.Field(i), replaceMap, useGoTemplate, goTemplateOptions); err != nil {
} else if err := r.deeplyReplace(copy.Field(i), original.Field(i), replaceMap, useGoTemplate); err != nil {
return err
}
}
@@ -129,7 +129,7 @@ func (r *Render) deeplyReplace(copy, original reflect.Value, replaceMap map[stri
}
for i := 0; i < original.Len(); i += 1 {
if err := r.deeplyReplace(copy.Index(i), original.Index(i), replaceMap, useGoTemplate, goTemplateOptions); err != nil {
if err := r.deeplyReplace(copy.Index(i), original.Index(i), replaceMap, useGoTemplate); err != nil {
return err
}
}
@@ -143,19 +143,19 @@ func (r *Render) deeplyReplace(copy, original reflect.Value, replaceMap map[stri
}
for _, key := range original.MapKeys() {
originalValue := original.MapIndex(key)
if originalValue.Kind() != reflect.String && isNillable(originalValue) && originalValue.IsNil() {
if originalValue.Kind() != reflect.String && originalValue.IsNil() {
continue
}
// New gives us a pointer, but again we want the value
copyValue := reflect.New(originalValue.Type()).Elem()
if err := r.deeplyReplace(copyValue, originalValue, replaceMap, useGoTemplate, goTemplateOptions); err != nil {
if err := r.deeplyReplace(copyValue, originalValue, replaceMap, useGoTemplate); err != nil {
return err
}
// Keys can be templated as well as values (e.g. to template something into an annotation).
if key.Kind() == reflect.String {
templatedKey, err := r.Replace(key.String(), replaceMap, useGoTemplate, goTemplateOptions)
templatedKey, err := r.Replace(key.String(), replaceMap, useGoTemplate)
if err != nil {
return err
}
@@ -169,7 +169,7 @@ func (r *Render) deeplyReplace(copy, original reflect.Value, replaceMap map[stri
// If it is a string translate it (yay finally we're doing what we came for)
case reflect.String:
strToTemplate := original.String()
templated, err := r.Replace(strToTemplate, replaceMap, useGoTemplate, goTemplateOptions)
templated, err := r.Replace(strToTemplate, replaceMap, useGoTemplate)
if err != nil {
return err
}
@@ -191,17 +191,7 @@ func (r *Render) deeplyReplace(copy, original reflect.Value, replaceMap map[stri
return nil
}
// isNillable returns true if the value is something which may be set to nil. This function is meant to guard against a
// panic from calling IsNil on a non-pointer type.
func isNillable(v reflect.Value) bool {
switch v.Kind() {
case reflect.Map, reflect.Pointer, reflect.UnsafePointer, reflect.Interface, reflect.Slice:
return true
}
return false
}
func (r *Render) RenderTemplateParams(tmpl *argoappsv1.Application, syncPolicy *argoappsv1.ApplicationSetSyncPolicy, params map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (*argoappsv1.Application, error) {
func (r *Render) RenderTemplateParams(tmpl *argoappsv1.Application, syncPolicy *argoappsv1.ApplicationSetSyncPolicy, params map[string]interface{}, useGoTemplate bool) (*argoappsv1.Application, error) {
if tmpl == nil {
return nil, fmt.Errorf("application template is empty")
}
@@ -213,7 +203,7 @@ func (r *Render) RenderTemplateParams(tmpl *argoappsv1.Application, syncPolicy *
original := reflect.ValueOf(tmpl)
copy := reflect.New(original.Type()).Elem()
if err := r.deeplyReplace(copy, original, params, useGoTemplate, goTemplateOptions); err != nil {
if err := r.deeplyReplace(copy, original, params, useGoTemplate); err != nil {
return nil, err
}
@@ -233,7 +223,7 @@ func (r *Render) RenderTemplateParams(tmpl *argoappsv1.Application, syncPolicy *
return replacedTmpl, nil
}
func (r *Render) RenderGeneratorParams(gen *argoappsv1.ApplicationSetGenerator, params map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (*argoappsv1.ApplicationSetGenerator, error) {
func (r *Render) RenderGeneratorParams(gen *argoappsv1.ApplicationSetGenerator, params map[string]interface{}, useGoTemplate bool) (*argoappsv1.ApplicationSetGenerator, error) {
if gen == nil {
return nil, fmt.Errorf("generator is empty")
}
@@ -245,7 +235,7 @@ func (r *Render) RenderGeneratorParams(gen *argoappsv1.ApplicationSetGenerator,
original := reflect.ValueOf(gen)
copy := reflect.New(original.Type()).Elem()
if err := r.deeplyReplace(copy, original, params, useGoTemplate, goTemplateOptions); err != nil {
if err := r.deeplyReplace(copy, original, params, useGoTemplate); err != nil {
return nil, fmt.Errorf("failed to replace parameters in generator: %w", err)
}
@@ -258,15 +248,12 @@ var isTemplatedRegex = regexp.MustCompile(".*{{.*}}.*")
// Replace executes basic string substitution of a template with replacement values.
// remaining in the substituted template.
func (r *Render) Replace(tmpl string, replaceMap map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (string, error) {
func (r *Render) Replace(tmpl string, replaceMap map[string]interface{}, useGoTemplate bool) (string, error) {
if useGoTemplate {
template, err := template.New("").Funcs(sprigFuncMap).Parse(tmpl)
if err != nil {
return "", fmt.Errorf("failed to parse template %s: %w", tmpl, err)
}
for _, option := range goTemplateOptions {
template = template.Option(option)
}
var replacedTmplBuffer bytes.Buffer
if err = template.Execute(&replacedTmplBuffer, replaceMap); err != nil {

View File

@@ -8,11 +8,11 @@ import (
logtest "github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
argoappsetv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
argoappsv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
@@ -174,7 +174,7 @@ func TestRenderTemplateParams(t *testing.T) {
// Render the cloned application, into a new application
render := Render{}
newApplication, err := render.RenderTemplateParams(application, nil, test.params, false, nil)
newApplication, err := render.RenderTemplateParams(application, nil, test.params, false)
// Retrieve the value of the target field from the newApplication, then verify that
// the target field has been templated into the expected value
@@ -236,12 +236,11 @@ func TestRenderTemplateParamsGoTemplate(t *testing.T) {
}
tests := []struct {
name string
fieldVal string
params map[string]interface{}
expectedVal string
errorMessage string
templateOptions []string
name string
fieldVal string
params map[string]interface{}
expectedVal string
errorMessage string
}{
{
name: "simple substitution",
@@ -424,26 +423,6 @@ func TestRenderTemplateParamsGoTemplate(t *testing.T) {
},
errorMessage: `failed to execute go template {{.data.test}}: template: :1:7: executing "" at <.data.test>: can't evaluate field test in type interface {}`,
},
{
name: "lookup missing value with missingkey=default",
fieldVal: `--> {{.doesnotexist}} <--`,
expectedVal: `--> <no value> <--`,
params: map[string]interface{}{
// if no params are passed then for some reason templating is skipped
"unused": "this is not used",
},
},
{
name: "lookup missing value with missingkey=error",
fieldVal: `--> {{.doesnotexist}} <--`,
expectedVal: "",
params: map[string]interface{}{
// if no params are passed then for some reason templating is skipped
"unused": "this is not used",
},
templateOptions: []string{"missingkey=error"},
errorMessage: `failed to execute go template --> {{.doesnotexist}} <--: template: :1:6: executing "" at <.doesnotexist>: map has no entry for key "doesnotexist"`,
},
}
for _, test := range tests {
@@ -460,7 +439,7 @@ func TestRenderTemplateParamsGoTemplate(t *testing.T) {
// Render the cloned application, into a new application
render := Render{}
newApplication, err := render.RenderTemplateParams(application, nil, test.params, true, test.templateOptions)
newApplication, err := render.RenderTemplateParams(application, nil, test.params, true)
// Retrieve the value of the target field from the newApplication, then verify that
// the target field has been templated into the expected value
@@ -485,34 +464,6 @@ func TestRenderTemplateParamsGoTemplate(t *testing.T) {
}
}
func TestRenderGeneratorParams_does_not_panic(t *testing.T) {
// This test verifies that the RenderGeneratorParams function does not panic when the value in a map is a non-
// nillable type. This is a regression test.
render := Render{}
params := map[string]interface{}{
"branch": "master",
}
generator := &argoappsv1.ApplicationSetGenerator{
Plugin: &argoappsv1.PluginGenerator{
ConfigMapRef: argoappsv1.PluginConfigMapRef{
Name: "cm-plugin",
},
Input: argoappsv1.PluginInput{
Parameters: map[string]apiextensionsv1.JSON{
"branch": {
Raw: []byte(`"{{.branch}}"`),
},
"repo": {
Raw: []byte(`"argo-test"`),
},
},
},
},
}
_, err := render.RenderGeneratorParams(generator, params, true, []string{})
assert.NoError(t, err)
}
func TestRenderTemplateKeys(t *testing.T) {
t.Run("fasttemplate", func(t *testing.T) {
application := &argoappsv1.Application{
@@ -529,7 +480,7 @@ func TestRenderTemplateKeys(t *testing.T) {
}
render := Render{}
newApplication, err := render.RenderTemplateParams(application, nil, params, false, nil)
newApplication, err := render.RenderTemplateParams(application, nil, params, false)
require.NoError(t, err)
require.Contains(t, newApplication.ObjectMeta.Annotations, "annotation-some-key")
assert.Equal(t, newApplication.ObjectMeta.Annotations["annotation-some-key"], "annotation-some-value")
@@ -549,7 +500,7 @@ func TestRenderTemplateKeys(t *testing.T) {
}
render := Render{}
newApplication, err := render.RenderTemplateParams(application, nil, params, true, nil)
newApplication, err := render.RenderTemplateParams(application, nil, params, true)
require.NoError(t, err)
require.Contains(t, newApplication.ObjectMeta.Annotations, "annotation-some-key")
assert.Equal(t, newApplication.ObjectMeta.Annotations["annotation-some-key"], "annotation-some-value")
@@ -577,7 +528,7 @@ func TestRenderTemplateParamsFinalizers(t *testing.T) {
for _, c := range []struct {
testName string
syncPolicy *argoappsv1.ApplicationSetSyncPolicy
syncPolicy *argoappsetv1.ApplicationSetSyncPolicy
existingFinalizers []string
expectedFinalizers []string
}{
@@ -616,13 +567,13 @@ func TestRenderTemplateParamsFinalizers(t *testing.T) {
{
testName: "non-nil sync policy should use standard finalizer",
existingFinalizers: nil,
syncPolicy: &argoappsv1.ApplicationSetSyncPolicy{},
syncPolicy: &argoappsetv1.ApplicationSetSyncPolicy{},
expectedFinalizers: []string{"resources-finalizer.argocd.argoproj.io"},
},
{
testName: "preserveResourcesOnDeletion should not have a finalizer",
existingFinalizers: nil,
syncPolicy: &argoappsv1.ApplicationSetSyncPolicy{
syncPolicy: &argoappsetv1.ApplicationSetSyncPolicy{
PreserveResourcesOnDeletion: true,
},
expectedFinalizers: nil,
@@ -630,7 +581,7 @@ func TestRenderTemplateParamsFinalizers(t *testing.T) {
{
testName: "user-specified finalizer should overwrite preserveResourcesOnDeletion",
existingFinalizers: []string{"resources-finalizer.argocd.argoproj.io/background"},
syncPolicy: &argoappsv1.ApplicationSetSyncPolicy{
syncPolicy: &argoappsetv1.ApplicationSetSyncPolicy{
PreserveResourcesOnDeletion: true,
},
expectedFinalizers: []string{"resources-finalizer.argocd.argoproj.io/background"},
@@ -650,7 +601,7 @@ func TestRenderTemplateParamsFinalizers(t *testing.T) {
// Render the cloned application, into a new application
render := Render{}
res, err := render.RenderTemplateParams(application, c.syncPolicy, params, true, nil)
res, err := render.RenderTemplateParams(application, c.syncPolicy, params, true)
assert.Nil(t, err)
assert.ElementsMatch(t, res.Finalizers, c.expectedFinalizers)
@@ -664,27 +615,27 @@ func TestRenderTemplateParamsFinalizers(t *testing.T) {
func TestCheckInvalidGenerators(t *testing.T) {
scheme := runtime.NewScheme()
err := argoappsv1.AddToScheme(scheme)
err := argoappsetv1.AddToScheme(scheme)
assert.Nil(t, err)
err = argoappsv1.AddToScheme(scheme)
assert.Nil(t, err)
for _, c := range []struct {
testName string
appSet argoappsv1.ApplicationSet
appSet argoappsetv1.ApplicationSet
expectedMsg string
}{
{
testName: "invalid generator, without annotation",
appSet: argoappsv1.ApplicationSet{
appSet: argoappsetv1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "test-app-set",
Namespace: "namespace",
},
Spec: argoappsv1.ApplicationSetSpec{
Generators: []argoappsv1.ApplicationSetGenerator{
Spec: argoappsetv1.ApplicationSetSpec{
Generators: []argoappsetv1.ApplicationSetGenerator{
{
List: &argoappsv1.ListGenerator{},
List: &argoappsetv1.ListGenerator{},
Clusters: nil,
Git: nil,
},
@@ -696,7 +647,7 @@ func TestCheckInvalidGenerators(t *testing.T) {
{
List: nil,
Clusters: nil,
Git: &argoappsv1.GitGenerator{},
Git: &argoappsetv1.GitGenerator{},
},
},
},
@@ -705,7 +656,7 @@ func TestCheckInvalidGenerators(t *testing.T) {
},
{
testName: "invalid generator, with annotation",
appSet: argoappsv1.ApplicationSet{
appSet: argoappsetv1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "test-app-set",
Namespace: "namespace",
@@ -722,10 +673,10 @@ func TestCheckInvalidGenerators(t *testing.T) {
}`,
},
},
Spec: argoappsv1.ApplicationSetSpec{
Generators: []argoappsv1.ApplicationSetGenerator{
Spec: argoappsetv1.ApplicationSetSpec{
Generators: []argoappsetv1.ApplicationSetGenerator{
{
List: &argoappsv1.ListGenerator{},
List: &argoappsetv1.ListGenerator{},
Clusters: nil,
Git: nil,
},
@@ -737,7 +688,7 @@ func TestCheckInvalidGenerators(t *testing.T) {
{
List: nil,
Clusters: nil,
Git: &argoappsv1.GitGenerator{},
Git: &argoappsetv1.GitGenerator{},
},
{
List: nil,
@@ -768,20 +719,20 @@ func TestCheckInvalidGenerators(t *testing.T) {
func TestInvalidGenerators(t *testing.T) {
scheme := runtime.NewScheme()
err := argoappsv1.AddToScheme(scheme)
err := argoappsetv1.AddToScheme(scheme)
assert.Nil(t, err)
err = argoappsv1.AddToScheme(scheme)
assert.Nil(t, err)
for _, c := range []struct {
testName string
appSet argoappsv1.ApplicationSet
appSet argoappsetv1.ApplicationSet
expectedInvalid bool
expectedNames map[string]bool
}{
{
testName: "valid generators, with annotation",
appSet: argoappsv1.ApplicationSet{
appSet: argoappsetv1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
@@ -797,22 +748,22 @@ func TestInvalidGenerators(t *testing.T) {
}`,
},
},
Spec: argoappsv1.ApplicationSetSpec{
Generators: []argoappsv1.ApplicationSetGenerator{
Spec: argoappsetv1.ApplicationSetSpec{
Generators: []argoappsetv1.ApplicationSetGenerator{
{
List: &argoappsv1.ListGenerator{},
List: &argoappsetv1.ListGenerator{},
Clusters: nil,
Git: nil,
},
{
List: nil,
Clusters: &argoappsv1.ClusterGenerator{},
Clusters: &argoappsetv1.ClusterGenerator{},
Git: nil,
},
{
List: nil,
Clusters: nil,
Git: &argoappsv1.GitGenerator{},
Git: &argoappsetv1.GitGenerator{},
},
},
},
@@ -822,13 +773,13 @@ func TestInvalidGenerators(t *testing.T) {
},
{
testName: "invalid generators, no annotation",
appSet: argoappsv1.ApplicationSet{
appSet: argoappsetv1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
Spec: argoappsv1.ApplicationSetSpec{
Generators: []argoappsv1.ApplicationSetGenerator{
Spec: argoappsetv1.ApplicationSetSpec{
Generators: []argoappsetv1.ApplicationSetGenerator{
{
List: nil,
Clusters: nil,
@@ -847,16 +798,16 @@ func TestInvalidGenerators(t *testing.T) {
},
{
testName: "valid and invalid generators, no annotation",
appSet: argoappsv1.ApplicationSet{
appSet: argoappsetv1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
Spec: argoappsv1.ApplicationSetSpec{
Generators: []argoappsv1.ApplicationSetGenerator{
Spec: argoappsetv1.ApplicationSetSpec{
Generators: []argoappsetv1.ApplicationSetGenerator{
{
List: nil,
Clusters: &argoappsv1.ClusterGenerator{},
Clusters: &argoappsetv1.ClusterGenerator{},
Git: nil,
},
{
@@ -867,7 +818,7 @@ func TestInvalidGenerators(t *testing.T) {
{
List: nil,
Clusters: nil,
Git: &argoappsv1.GitGenerator{},
Git: &argoappsetv1.GitGenerator{},
},
},
},
@@ -877,7 +828,7 @@ func TestInvalidGenerators(t *testing.T) {
},
{
testName: "valid and invalid generators, with annotation",
appSet: argoappsv1.ApplicationSet{
appSet: argoappsetv1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
@@ -894,11 +845,11 @@ func TestInvalidGenerators(t *testing.T) {
}`,
},
},
Spec: argoappsv1.ApplicationSetSpec{
Generators: []argoappsv1.ApplicationSetGenerator{
Spec: argoappsetv1.ApplicationSetSpec{
Generators: []argoappsetv1.ApplicationSetGenerator{
{
List: nil,
Clusters: &argoappsv1.ClusterGenerator{},
Clusters: &argoappsetv1.ClusterGenerator{},
Git: nil,
},
{
@@ -909,7 +860,7 @@ func TestInvalidGenerators(t *testing.T) {
{
List: nil,
Clusters: nil,
Git: &argoappsv1.GitGenerator{},
Git: &argoappsetv1.GitGenerator{},
},
{
List: nil,
@@ -927,7 +878,7 @@ func TestInvalidGenerators(t *testing.T) {
},
{
testName: "invalid generator, annotation with missing spec",
appSet: argoappsv1.ApplicationSet{
appSet: argoappsetv1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
@@ -936,8 +887,8 @@ func TestInvalidGenerators(t *testing.T) {
}`,
},
},
Spec: argoappsv1.ApplicationSetSpec{
Generators: []argoappsv1.ApplicationSetGenerator{
Spec: argoappsetv1.ApplicationSetSpec{
Generators: []argoappsetv1.ApplicationSetGenerator{
{
List: nil,
Clusters: nil,
@@ -951,7 +902,7 @@ func TestInvalidGenerators(t *testing.T) {
},
{
testName: "invalid generator, annotation with missing generators array",
appSet: argoappsv1.ApplicationSet{
appSet: argoappsetv1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
@@ -962,8 +913,8 @@ func TestInvalidGenerators(t *testing.T) {
}`,
},
},
Spec: argoappsv1.ApplicationSetSpec{
Generators: []argoappsv1.ApplicationSetGenerator{
Spec: argoappsetv1.ApplicationSetSpec{
Generators: []argoappsetv1.ApplicationSetGenerator{
{
List: nil,
Clusters: nil,
@@ -977,7 +928,7 @@ func TestInvalidGenerators(t *testing.T) {
},
{
testName: "invalid generator, annotation with empty generators array",
appSet: argoappsv1.ApplicationSet{
appSet: argoappsetv1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
@@ -990,8 +941,8 @@ func TestInvalidGenerators(t *testing.T) {
}`,
},
},
Spec: argoappsv1.ApplicationSetSpec{
Generators: []argoappsv1.ApplicationSetGenerator{
Spec: argoappsetv1.ApplicationSetSpec{
Generators: []argoappsetv1.ApplicationSetGenerator{
{
List: nil,
Clusters: nil,
@@ -1005,7 +956,7 @@ func TestInvalidGenerators(t *testing.T) {
},
{
testName: "invalid generator, annotation with empty generator",
appSet: argoappsv1.ApplicationSet{
appSet: argoappsetv1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
@@ -1019,8 +970,8 @@ func TestInvalidGenerators(t *testing.T) {
}`,
},
},
Spec: argoappsv1.ApplicationSetSpec{
Generators: []argoappsv1.ApplicationSetGenerator{
Spec: argoappsetv1.ApplicationSetSpec{
Generators: []argoappsetv1.ApplicationSetGenerator{
{
List: nil,
Clusters: nil,

View File

@@ -98,7 +98,6 @@ func (h *WebhookHandler) HandleEvent(payload interface{}) {
// check if the ApplicationSet uses any generator that is relevant to the payload
shouldRefresh = shouldRefreshGitGenerator(gen.Git, gitGenInfo) ||
shouldRefreshPRGenerator(gen.PullRequest, prGenInfo) ||
shouldRefreshPluginGenerator(gen.Plugin) ||
h.shouldRefreshMatrixGenerator(gen.Matrix, &appSet, gitGenInfo, prGenInfo) ||
h.shouldRefreshMergeGenerator(gen.Merge, &appSet, gitGenInfo, prGenInfo)
if shouldRefresh {
@@ -288,10 +287,6 @@ func shouldRefreshGitGenerator(gen *v1alpha1.GitGenerator, info *gitGeneratorInf
return true
}
func shouldRefreshPluginGenerator(gen *v1alpha1.PluginGenerator) bool {
return gen != nil
}
func genRevisionHasChanged(gen *v1alpha1.GitGenerator, revision string, touchedHead bool) bool {
targetRev := parseRevision(gen.Revision)
if targetRev == "HEAD" || targetRev == "" { // revision is head
@@ -422,7 +417,6 @@ func (h *WebhookHandler) shouldRefreshMatrixGenerator(gen *v1alpha1.MatrixGenera
SCMProvider: g0.SCMProvider,
ClusterDecisionResource: g0.ClusterDecisionResource,
PullRequest: g0.PullRequest,
Plugin: g0.Plugin,
Matrix: matrixGenerator0,
Merge: mergeGenerator0,
}
@@ -477,7 +471,6 @@ func (h *WebhookHandler) shouldRefreshMatrixGenerator(gen *v1alpha1.MatrixGenera
SCMProvider: g1.SCMProvider,
ClusterDecisionResource: g1.ClusterDecisionResource,
PullRequest: g1.PullRequest,
Plugin: g1.Plugin,
Matrix: matrixGenerator1,
Merge: mergeGenerator1,
}
@@ -485,7 +478,7 @@ func (h *WebhookHandler) shouldRefreshMatrixGenerator(gen *v1alpha1.MatrixGenera
// Interpolate second child generator with params from first child generator, if there are any params
if len(params) != 0 {
for _, p := range params {
tempInterpolatedGenerator, err := generators.InterpolateGenerator(requestedGenerator1, p, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions)
tempInterpolatedGenerator, err := generators.InterpolateGenerator(requestedGenerator1, p, appSet.Spec.GoTemplate)
interpolatedGenerator := &tempInterpolatedGenerator
if err != nil {
log.Error(err)
@@ -495,7 +488,6 @@ func (h *WebhookHandler) shouldRefreshMatrixGenerator(gen *v1alpha1.MatrixGenera
// Check all interpolated child generators
if shouldRefreshGitGenerator(interpolatedGenerator.Git, gitGenInfo) ||
shouldRefreshPRGenerator(interpolatedGenerator.PullRequest, prGenInfo) ||
shouldRefreshPluginGenerator(interpolatedGenerator.Plugin) ||
h.shouldRefreshMatrixGenerator(interpolatedGenerator.Matrix, appSet, gitGenInfo, prGenInfo) ||
h.shouldRefreshMergeGenerator(requestedGenerator1.Merge, appSet, gitGenInfo, prGenInfo) {
return true
@@ -506,7 +498,6 @@ func (h *WebhookHandler) shouldRefreshMatrixGenerator(gen *v1alpha1.MatrixGenera
// First child generator didn't return any params, just check the second child generator
return shouldRefreshGitGenerator(requestedGenerator1.Git, gitGenInfo) ||
shouldRefreshPRGenerator(requestedGenerator1.PullRequest, prGenInfo) ||
shouldRefreshPluginGenerator(requestedGenerator1.Plugin) ||
h.shouldRefreshMatrixGenerator(requestedGenerator1.Matrix, appSet, gitGenInfo, prGenInfo) ||
h.shouldRefreshMergeGenerator(requestedGenerator1.Merge, appSet, gitGenInfo, prGenInfo)
}

View File

@@ -15,6 +15,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
kubefake "k8s.io/client-go/kubernetes/fake"
@@ -24,6 +25,7 @@ import (
"github.com/argoproj/argo-cd/v2/applicationset/services/scm_provider"
"github.com/argoproj/argo-cd/v2/common"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
argosettings "github.com/argoproj/argo-cd/v2/util/settings"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
)
@@ -32,15 +34,15 @@ type generatorMock struct {
mock.Mock
}
func (g *generatorMock) GetTemplate(appSetGenerator *v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate {
return &v1alpha1.ApplicationSetTemplate{}
func (g *generatorMock) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
return &argoprojiov1alpha1.ApplicationSetTemplate{}
}
func (g *generatorMock) GenerateParams(appSetGenerator *v1alpha1.ApplicationSetGenerator, _ *v1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
func (g *generatorMock) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, _ *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
return []map[string]interface{}{}, nil
}
func (g *generatorMock) GetRequeueAfter(appSetGenerator *v1alpha1.ApplicationSetGenerator) time.Duration {
func (g *generatorMock) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
d, _ := time.ParseDuration("10s")
return d
}
@@ -60,7 +62,7 @@ func TestWebhookHandler(t *testing.T) {
headerKey: "X-GitHub-Event",
headerValue: "push",
payloadFile: "github-commit-event.json",
effectedAppSets: []string{"git-github", "matrix-git-github", "merge-git-github", "matrix-scm-git-github", "matrix-nested-git-github", "merge-nested-git-github", "plugin", "matrix-pull-request-github-plugin"},
effectedAppSets: []string{"git-github", "matrix-git-github", "merge-git-github", "matrix-scm-git-github", "matrix-nested-git-github", "merge-nested-git-github"},
expectedStatusCode: http.StatusOK,
expectedRefresh: true,
},
@@ -69,7 +71,7 @@ func TestWebhookHandler(t *testing.T) {
headerKey: "X-GitHub-Event",
headerValue: "push",
payloadFile: "github-commit-branch-event.json",
effectedAppSets: []string{"git-github", "plugin", "matrix-pull-request-github-plugin"},
effectedAppSets: []string{"git-github"},
expectedStatusCode: http.StatusOK,
expectedRefresh: true,
},
@@ -78,7 +80,7 @@ func TestWebhookHandler(t *testing.T) {
headerKey: "X-GitHub-Event",
headerValue: "ping",
payloadFile: "github-ping-event.json",
effectedAppSets: []string{"git-github", "plugin"},
effectedAppSets: []string{"git-github"},
expectedStatusCode: http.StatusOK,
expectedRefresh: false,
},
@@ -87,7 +89,7 @@ func TestWebhookHandler(t *testing.T) {
headerKey: "X-Gitlab-Event",
headerValue: "Push Hook",
payloadFile: "gitlab-event.json",
effectedAppSets: []string{"git-gitlab", "plugin", "matrix-pull-request-github-plugin"},
effectedAppSets: []string{"git-gitlab"},
expectedStatusCode: http.StatusOK,
expectedRefresh: true,
},
@@ -96,7 +98,7 @@ func TestWebhookHandler(t *testing.T) {
headerKey: "X-Random-Event",
headerValue: "Push Hook",
payloadFile: "gitlab-event.json",
effectedAppSets: []string{"git-gitlab", "plugin"},
effectedAppSets: []string{"git-gitlab"},
expectedStatusCode: http.StatusBadRequest,
expectedRefresh: false,
},
@@ -105,7 +107,7 @@ func TestWebhookHandler(t *testing.T) {
headerKey: "X-Random-Event",
headerValue: "Push Hook",
payloadFile: "invalid-event.json",
effectedAppSets: []string{"git-gitlab", "plugin"},
effectedAppSets: []string{"git-gitlab"},
expectedStatusCode: http.StatusBadRequest,
expectedRefresh: false,
},
@@ -114,7 +116,7 @@ func TestWebhookHandler(t *testing.T) {
headerKey: "X-GitHub-Event",
headerValue: "pull_request",
payloadFile: "github-pull-request-opened-event.json",
effectedAppSets: []string{"pull-request-github", "matrix-pull-request-github", "matrix-scm-pull-request-github", "merge-pull-request-github", "plugin", "matrix-pull-request-github-plugin"},
effectedAppSets: []string{"pull-request-github", "matrix-pull-request-github", "matrix-scm-pull-request-github", "merge-pull-request-github"},
expectedStatusCode: http.StatusOK,
expectedRefresh: true,
},
@@ -123,7 +125,7 @@ func TestWebhookHandler(t *testing.T) {
headerKey: "X-GitHub-Event",
headerValue: "pull_request",
payloadFile: "github-pull-request-assigned-event.json",
effectedAppSets: []string{"pull-request-github", "matrix-pull-request-github", "matrix-scm-pull-request-github", "merge-pull-request-github", "plugin", "matrix-pull-request-github-plugin"},
effectedAppSets: []string{"pull-request-github", "matrix-pull-request-github", "matrix-scm-pull-request-github", "merge-pull-request-github"},
expectedStatusCode: http.StatusOK,
expectedRefresh: false,
},
@@ -132,7 +134,7 @@ func TestWebhookHandler(t *testing.T) {
headerKey: "X-Gitlab-Event",
headerValue: "Merge Request Hook",
payloadFile: "gitlab-merge-request-open-event.json",
effectedAppSets: []string{"pull-request-gitlab", "plugin", "matrix-pull-request-github-plugin"},
effectedAppSets: []string{"pull-request-gitlab"},
expectedStatusCode: http.StatusOK,
expectedRefresh: true,
},
@@ -141,7 +143,7 @@ func TestWebhookHandler(t *testing.T) {
headerKey: "X-Gitlab-Event",
headerValue: "Merge Request Hook",
payloadFile: "gitlab-merge-request-approval-event.json",
effectedAppSets: []string{"pull-request-gitlab", "plugin"},
effectedAppSets: []string{"pull-request-gitlab"},
expectedStatusCode: http.StatusOK,
expectedRefresh: false,
},
@@ -162,13 +164,11 @@ func TestWebhookHandler(t *testing.T) {
fakeAppWithGitGenerator("git-gitlab", namespace, "https://gitlab/group/name"),
fakeAppWithGithubPullRequestGenerator("pull-request-github", namespace, "Codertocat", "Hello-World"),
fakeAppWithGitlabPullRequestGenerator("pull-request-gitlab", namespace, "100500"),
fakeAppWithPluginGenerator("plugin", namespace),
fakeAppWithMatrixAndGitGenerator("matrix-git-github", namespace, "https://github.com/org/repo"),
fakeAppWithMatrixAndPullRequestGenerator("matrix-pull-request-github", namespace, "Codertocat", "Hello-World"),
fakeAppWithMatrixAndScmWithGitGenerator("matrix-scm-git-github", namespace, "org"),
fakeAppWithMatrixAndScmWithPullRequestGenerator("matrix-scm-pull-request-github", namespace, "Codertocat"),
fakeAppWithMatrixAndNestedGitGenerator("matrix-nested-git-github", namespace, "https://github.com/org/repo"),
fakeAppWithMatrixAndPullRequestGeneratorWithPluginGenerator("matrix-pull-request-github-plugin", namespace, "Codertocat", "Hello-World", "plugin-cm"),
fakeAppWithMergeAndGitGenerator("merge-git-github", namespace, "https://github.com/org/repo"),
fakeAppWithMergeAndPullRequestGenerator("merge-pull-request-github", namespace, "Codertocat", "Hello-World"),
fakeAppWithMergeAndNestedGitGenerator("merge-nested-git-github", namespace, "https://github.com/org/repo"),
@@ -216,7 +216,6 @@ func mockGenerators() map[string]generators.Generator {
// generatorMockList := generatorMock{}
generatorMockGit := &generatorMock{}
generatorMockPR := &generatorMock{}
generatorMockPlugin := &generatorMock{}
mockSCMProvider := &scm_provider.MockProvider{
Repos: []*scm_provider.Repository{
{
@@ -242,7 +241,6 @@ func mockGenerators() map[string]generators.Generator {
"Git": generatorMockGit,
"SCMProvider": generatorMockSCM,
"PullRequest": generatorMockPR,
"Plugin": generatorMockPlugin,
}
nestedGenerators := map[string]generators.Generator{
@@ -250,7 +248,6 @@ func mockGenerators() map[string]generators.Generator {
"Git": terminalMockGenerators["Git"],
"SCMProvider": terminalMockGenerators["SCMProvider"],
"PullRequest": terminalMockGenerators["PullRequest"],
"Plugin": terminalMockGenerators["Plugin"],
"Matrix": generators.NewMatrixGenerator(terminalMockGenerators),
"Merge": generators.NewMergeGenerator(terminalMockGenerators),
}
@@ -260,21 +257,20 @@ func mockGenerators() map[string]generators.Generator {
"Git": terminalMockGenerators["Git"],
"SCMProvider": terminalMockGenerators["SCMProvider"],
"PullRequest": terminalMockGenerators["PullRequest"],
"Plugin": terminalMockGenerators["Plugin"],
"Matrix": generators.NewMatrixGenerator(nestedGenerators),
"Merge": generators.NewMergeGenerator(nestedGenerators),
}
}
func TestGenRevisionHasChanged(t *testing.T) {
assert.True(t, genRevisionHasChanged(&v1alpha1.GitGenerator{}, "master", true))
assert.False(t, genRevisionHasChanged(&v1alpha1.GitGenerator{}, "master", false))
assert.True(t, genRevisionHasChanged(&argoprojiov1alpha1.GitGenerator{}, "master", true))
assert.False(t, genRevisionHasChanged(&argoprojiov1alpha1.GitGenerator{}, "master", false))
assert.True(t, genRevisionHasChanged(&v1alpha1.GitGenerator{Revision: "dev"}, "dev", true))
assert.False(t, genRevisionHasChanged(&v1alpha1.GitGenerator{Revision: "dev"}, "master", false))
assert.True(t, genRevisionHasChanged(&argoprojiov1alpha1.GitGenerator{Revision: "dev"}, "dev", true))
assert.False(t, genRevisionHasChanged(&argoprojiov1alpha1.GitGenerator{Revision: "dev"}, "master", false))
assert.True(t, genRevisionHasChanged(&v1alpha1.GitGenerator{Revision: "refs/heads/dev"}, "dev", true))
assert.False(t, genRevisionHasChanged(&v1alpha1.GitGenerator{Revision: "refs/heads/dev"}, "master", false))
assert.True(t, genRevisionHasChanged(&argoprojiov1alpha1.GitGenerator{Revision: "refs/heads/dev"}, "dev", true))
assert.False(t, genRevisionHasChanged(&argoprojiov1alpha1.GitGenerator{Revision: "refs/heads/dev"}, "master", false))
}
func fakeAppWithGitGenerator(name, namespace, repo string) *v1alpha1.ApplicationSet {
@@ -296,17 +292,17 @@ func fakeAppWithGitGenerator(name, namespace, repo string) *v1alpha1.Application
}
}
func fakeAppWithGitlabPullRequestGenerator(name, namespace, projectId string) *v1alpha1.ApplicationSet {
return &v1alpha1.ApplicationSet{
func fakeAppWithGitlabPullRequestGenerator(name, namespace, projectId string) *argoprojiov1alpha1.ApplicationSet {
return &argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: v1alpha1.ApplicationSetSpec{
Generators: []v1alpha1.ApplicationSetGenerator{
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{
{
PullRequest: &v1alpha1.PullRequestGenerator{
GitLab: &v1alpha1.PullRequestGeneratorGitLab{
PullRequest: &argoprojiov1alpha1.PullRequestGenerator{
GitLab: &argoprojiov1alpha1.PullRequestGeneratorGitLab{
Project: projectId,
},
},
@@ -316,8 +312,8 @@ func fakeAppWithGitlabPullRequestGenerator(name, namespace, projectId string) *v
}
}
func fakeAppWithGithubPullRequestGenerator(name, namespace, owner, repo string) *v1alpha1.ApplicationSet {
return &v1alpha1.ApplicationSet{
func fakeAppWithGithubPullRequestGenerator(name, namespace, owner, repo string) *argoprojiov1alpha1.ApplicationSet {
return &argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
@@ -349,10 +345,10 @@ func fakeAppWithMatrixAndGitGenerator(name, namespace, repo string) *v1alpha1.Ap
Matrix: &v1alpha1.MatrixGenerator{
Generators: []v1alpha1.ApplicationSetNestedGenerator{
{
List: &v1alpha1.ListGenerator{},
List: &argoprojiov1alpha1.ListGenerator{},
},
{
Git: &v1alpha1.GitGenerator{
Git: &argoprojiov1alpha1.GitGenerator{
RepoURL: repo,
},
},
@@ -376,11 +372,11 @@ func fakeAppWithMatrixAndPullRequestGenerator(name, namespace, owner, repo strin
Matrix: &v1alpha1.MatrixGenerator{
Generators: []v1alpha1.ApplicationSetNestedGenerator{
{
List: &v1alpha1.ListGenerator{},
List: &argoprojiov1alpha1.ListGenerator{},
},
{
PullRequest: &v1alpha1.PullRequestGenerator{
Github: &v1alpha1.PullRequestGeneratorGithub{
PullRequest: &argoprojiov1alpha1.PullRequestGenerator{
Github: &argoprojiov1alpha1.PullRequestGeneratorGithub{
Owner: owner,
Repo: repo,
},
@@ -394,27 +390,27 @@ func fakeAppWithMatrixAndPullRequestGenerator(name, namespace, owner, repo strin
}
}
func fakeAppWithMatrixAndScmWithGitGenerator(name, namespace, owner string) *v1alpha1.ApplicationSet {
return &v1alpha1.ApplicationSet{
func fakeAppWithMatrixAndScmWithGitGenerator(name, namespace, owner string) *argoprojiov1alpha1.ApplicationSet {
return &argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: v1alpha1.ApplicationSetSpec{
Generators: []v1alpha1.ApplicationSetGenerator{
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{
{
Matrix: &v1alpha1.MatrixGenerator{
Generators: []v1alpha1.ApplicationSetNestedGenerator{
Matrix: &argoprojiov1alpha1.MatrixGenerator{
Generators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
SCMProvider: &v1alpha1.SCMProviderGenerator{
SCMProvider: &argoprojiov1alpha1.SCMProviderGenerator{
CloneProtocol: "ssh",
Github: &v1alpha1.SCMProviderGeneratorGithub{
Github: &argoprojiov1alpha1.SCMProviderGeneratorGithub{
Organization: owner,
},
},
},
{
Git: &v1alpha1.GitGenerator{
Git: &argoprojiov1alpha1.GitGenerator{
RepoURL: "{{ url }}",
},
},
@@ -426,28 +422,28 @@ func fakeAppWithMatrixAndScmWithGitGenerator(name, namespace, owner string) *v1a
}
}
func fakeAppWithMatrixAndScmWithPullRequestGenerator(name, namespace, owner string) *v1alpha1.ApplicationSet {
return &v1alpha1.ApplicationSet{
func fakeAppWithMatrixAndScmWithPullRequestGenerator(name, namespace, owner string) *argoprojiov1alpha1.ApplicationSet {
return &argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: v1alpha1.ApplicationSetSpec{
Generators: []v1alpha1.ApplicationSetGenerator{
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{
{
Matrix: &v1alpha1.MatrixGenerator{
Generators: []v1alpha1.ApplicationSetNestedGenerator{
Matrix: &argoprojiov1alpha1.MatrixGenerator{
Generators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
SCMProvider: &v1alpha1.SCMProviderGenerator{
SCMProvider: &argoprojiov1alpha1.SCMProviderGenerator{
CloneProtocol: "https",
Github: &v1alpha1.SCMProviderGeneratorGithub{
Github: &argoprojiov1alpha1.SCMProviderGeneratorGithub{
Organization: owner,
},
},
},
{
PullRequest: &v1alpha1.PullRequestGenerator{
Github: &v1alpha1.PullRequestGeneratorGithub{
PullRequest: &argoprojiov1alpha1.PullRequestGenerator{
Github: &argoprojiov1alpha1.PullRequestGeneratorGithub{
Owner: "{{ organization }}",
Repo: "{{ repository }}",
},
@@ -461,19 +457,19 @@ func fakeAppWithMatrixAndScmWithPullRequestGenerator(name, namespace, owner stri
}
}
func fakeAppWithMatrixAndNestedGitGenerator(name, namespace, repo string) *v1alpha1.ApplicationSet {
return &v1alpha1.ApplicationSet{
func fakeAppWithMatrixAndNestedGitGenerator(name, namespace, repo string) *argoprojiov1alpha1.ApplicationSet {
return &argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: v1alpha1.ApplicationSetSpec{
Generators: []v1alpha1.ApplicationSetGenerator{
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{
{
Matrix: &v1alpha1.MatrixGenerator{
Generators: []v1alpha1.ApplicationSetNestedGenerator{
Matrix: &argoprojiov1alpha1.MatrixGenerator{
Generators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
List: &v1alpha1.ListGenerator{},
List: &argoprojiov1alpha1.ListGenerator{},
},
{
Matrix: &apiextensionsv1.JSON{
@@ -505,8 +501,8 @@ func fakeAppWithMatrixAndNestedGitGenerator(name, namespace, repo string) *v1alp
}
}
func fakeAppWithMergeAndGitGenerator(name, namespace, repo string) *v1alpha1.ApplicationSet {
return &v1alpha1.ApplicationSet{
func fakeAppWithMergeAndGitGenerator(name, namespace, repo string) *argoprojiov1alpha1.ApplicationSet {
return &argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
@@ -556,22 +552,22 @@ func fakeAppWithMergeAndPullRequestGenerator(name, namespace, owner, repo string
}
}
func fakeAppWithMergeAndNestedGitGenerator(name, namespace, repo string) *v1alpha1.ApplicationSet {
return &v1alpha1.ApplicationSet{
func fakeAppWithMergeAndNestedGitGenerator(name, namespace, repo string) *argoprojiov1alpha1.ApplicationSet {
return &argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: v1alpha1.ApplicationSetSpec{
Generators: []v1alpha1.ApplicationSetGenerator{
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{
{
Merge: &v1alpha1.MergeGenerator{
Merge: &argoprojiov1alpha1.MergeGenerator{
MergeKeys: []string{
"server",
},
Generators: []v1alpha1.ApplicationSetNestedGenerator{
Generators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
{
List: &v1alpha1.ListGenerator{},
List: &argoprojiov1alpha1.ListGenerator{},
},
{
Merge: &apiextensionsv1.JSON{
@@ -598,66 +594,12 @@ func fakeAppWithMergeAndNestedGitGenerator(name, namespace, repo string) *v1alph
}
}
func fakeAppWithPluginGenerator(name, namespace string) *v1alpha1.ApplicationSet {
return &v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: v1alpha1.ApplicationSetSpec{
Generators: []v1alpha1.ApplicationSetGenerator{
{
Plugin: &v1alpha1.PluginGenerator{
ConfigMapRef: v1alpha1.PluginConfigMapRef{
Name: "test",
},
},
},
},
},
}
}
func fakeAppWithMatrixAndPullRequestGeneratorWithPluginGenerator(name, namespace, owner, repo, configmapName string) *v1alpha1.ApplicationSet {
return &v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: v1alpha1.ApplicationSetSpec{
Generators: []v1alpha1.ApplicationSetGenerator{
{
Matrix: &v1alpha1.MatrixGenerator{
Generators: []v1alpha1.ApplicationSetNestedGenerator{
{
PullRequest: &v1alpha1.PullRequestGenerator{
Github: &v1alpha1.PullRequestGeneratorGithub{
Owner: owner,
Repo: repo,
},
},
},
{
Plugin: &v1alpha1.PluginGenerator{
ConfigMapRef: v1alpha1.PluginConfigMapRef{
Name: configmapName,
},
},
},
},
},
},
},
},
}
}
func newFakeClient(ns string) *kubefake.Clientset {
s := runtime.NewScheme()
s.AddKnownTypes(v1alpha1.SchemeGroupVersion, &v1alpha1.ApplicationSet{})
return kubefake.NewSimpleClientset(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "argocd-cm", Namespace: ns, Labels: map[string]string{
"app.kubernetes.io/part-of": "argocd",
}}}, &corev1.Secret{
}}}, &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: common.ArgoCDSecretName,
Namespace: ns,

View File

@@ -1502,51 +1502,6 @@
}
}
},
"/api/v1/applications/{name}/revisions/{revision}/chartdetails": {
"get": {
"tags": [
"ApplicationService"
],
"summary": "Get the chart metadata (description, maintainers, home) for a specific revision of the application",
"operationId": "ApplicationService_RevisionChartDetails",
"parameters": [
{
"type": "string",
"description": "the application's name",
"name": "name",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the revision of the app",
"name": "revision",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the application's namespace.",
"name": "appNamespace",
"in": "query"
}
],
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1alpha1ChartDetails"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/runtimeError"
}
}
}
}
},
"/api/v1/applications/{name}/revisions/{revision}/metadata": {
"get": {
"tags": [
@@ -5685,7 +5640,7 @@
},
"v1alpha1ApplicationCondition": {
"type": "object",
"title": "ApplicationCondition contains details about an application condition, which is usually an error or warning",
"title": "ApplicationCondition contains details about an application condition, which is usally an error or warning",
"properties": {
"lastTransitionTime": {
"$ref": "#/definitions/v1Time"
@@ -5848,9 +5803,6 @@
"merge": {
"$ref": "#/definitions/v1alpha1MergeGenerator"
},
"plugin": {
"$ref": "#/definitions/v1alpha1PluginGenerator"
},
"pullRequest": {
"$ref": "#/definitions/v1alpha1PullRequestGenerator"
},
@@ -5899,9 +5851,6 @@
"merge": {
"$ref": "#/definitions/v1JSON"
},
"plugin": {
"$ref": "#/definitions/v1alpha1PluginGenerator"
},
"pullRequest": {
"$ref": "#/definitions/v1alpha1PullRequestGenerator"
},
@@ -5951,12 +5900,6 @@
"goTemplate": {
"type": "boolean"
},
"goTemplateOptions": {
"type": "array",
"items": {
"type": "string"
}
},
"preservedFields": {
"$ref": "#/definitions/v1alpha1ApplicationPreservedFields"
},
@@ -6357,10 +6300,6 @@
"$ref": "#/definitions/v1alpha1ApplicationCondition"
}
},
"controllerNamespace": {
"type": "string",
"title": "ControllerNamespace indicates the namespace in which the application controller is located"
},
"health": {
"$ref": "#/definitions/v1alpha1HealthStatus"
},
@@ -6501,26 +6440,6 @@
}
}
},
"v1alpha1ChartDetails": {
"type": "object",
"title": "ChartDetails contains helm chart metadata for a specific version",
"properties": {
"description": {
"type": "string"
},
"home": {
"type": "string",
"title": "The URL of this projects home page, e.g. \"http://example.com\""
},
"maintainers": {
"type": "array",
"title": "List of maintainer details, name and email, e.g. [\"John Doe <john_doe@my-company.com>\"]",
"items": {
"type": "string"
}
}
}
},
"v1alpha1Cluster": {
"type": "object",
"title": "Cluster is the definition of a cluster resource",
@@ -6875,13 +6794,6 @@
},
"template": {
"$ref": "#/definitions/v1alpha1ApplicationSetTemplate"
},
"values": {
"type": "object",
"title": "Values contains key/value pairs which are passed directly as parameters to the template",
"additionalProperties": {
"type": "string"
}
}
}
},
@@ -7319,54 +7231,6 @@
}
}
},
"v1alpha1PluginConfigMapRef": {
"type": "object",
"properties": {
"name": {
"type": "string",
"title": "Name of the ConfigMap"
}
}
},
"v1alpha1PluginGenerator": {
"description": "PluginGenerator defines connection info specific to Plugin.",
"type": "object",
"properties": {
"configMapRef": {
"$ref": "#/definitions/v1alpha1PluginConfigMapRef"
},
"input": {
"$ref": "#/definitions/v1alpha1PluginInput"
},
"requeueAfterSeconds": {
"description": "RequeueAfterSeconds determines how long the ApplicationSet controller will wait before reconciling the ApplicationSet again.",
"type": "string",
"format": "int64"
},
"template": {
"$ref": "#/definitions/v1alpha1ApplicationSetTemplate"
},
"values": {
"description": "Values contains key/value pairs which are passed directly as parameters to the template. These values will not be\nsent as parameters to the plugin.",
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
},
"v1alpha1PluginInput": {
"type": "object",
"properties": {
"parameters": {
"description": "Parameters contains the information to pass to the plugin. It is a map. The keys must be strings, and the\nvalues can be any type.",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/v1JSON"
}
}
}
},
"v1alpha1ProjectRole": {
"type": "object",
"title": "ProjectRole represents a role that has access to a project",
@@ -7462,9 +7326,6 @@
"properties": {
"branchMatch": {
"type": "string"
},
"targetBranchMatch": {
"type": "string"
}
}
},
@@ -8200,9 +8061,6 @@
"description": "SCMProviderGenerator defines a generator that scrapes a SCMaaS API to find candidate repos.",
"type": "object",
"properties": {
"awsCodeCommit": {
"$ref": "#/definitions/v1alpha1SCMProviderGeneratorAWSCodeCommit"
},
"azureDevOps": {
"$ref": "#/definitions/v1alpha1SCMProviderGeneratorAzureDevOps"
},
@@ -8239,38 +8097,6 @@
},
"template": {
"$ref": "#/definitions/v1alpha1ApplicationSetTemplate"
},
"values": {
"type": "object",
"title": "Values contains key/value pairs which are passed directly as parameters to the template",
"additionalProperties": {
"type": "string"
}
}
}
},
"v1alpha1SCMProviderGeneratorAWSCodeCommit": {
"description": "SCMProviderGeneratorAWSCodeCommit defines connection info specific to AWS CodeCommit.",
"type": "object",
"properties": {
"allBranches": {
"description": "Scan all branches instead of just the default branch.",
"type": "boolean"
},
"region": {
"description": "Region provides the AWS region to discover repos.\nif not provided, AppSet controller will infer the current region from environment.",
"type": "string"
},
"role": {
"description": "Role provides the AWS IAM role to assume, for cross-account repo discovery\nif not provided, AppSet controller will use its pod/node identity to discover.",
"type": "string"
},
"tagFilters": {
"type": "array",
"title": "TagFilters provides the tag filter(s) for repo discovery",
"items": {
"$ref": "#/definitions/v1alpha1TagFilter"
}
}
}
},
@@ -8551,9 +8377,6 @@
"type": "object",
"title": "SyncOperationResult represent result of sync operation",
"properties": {
"managedNamespaceMetadata": {
"$ref": "#/definitions/v1alpha1ManagedNamespaceMetadata"
},
"resources": {
"type": "array",
"title": "Resources contains a list of sync result items for each individual resource in a sync operation",
@@ -8620,7 +8443,7 @@
},
"selfHeal": {
"type": "boolean",
"title": "SelfHeal specifies whether to revert resources back to their desired state upon modification in the cluster (default: false)"
"title": "SelfHeal specifes whether to revert resources back to their desired state upon modification in the cluster (default: false)"
}
}
},
@@ -8755,17 +8578,6 @@
}
}
},
"v1alpha1TagFilter": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string"
}
}
},
"versionVersionMessage": {
"type": "object",
"title": "VersionMessage represents version of the Argo CD API server",
@@ -8776,9 +8588,6 @@
"Compiler": {
"type": "string"
},
"ExtraBuildInfo": {
"type": "string"
},
"GitCommit": {
"type": "string"
},

View File

@@ -23,7 +23,6 @@ import (
cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
"github.com/argoproj/argo-cd/v2/util/cli"
"github.com/argoproj/argo-cd/v2/util/db"
"github.com/argoproj/argo-cd/v2/util/env"
"github.com/argoproj/argo-cd/v2/util/errors"
kubeutil "github.com/argoproj/argo-cd/v2/util/kube"
@@ -63,7 +62,6 @@ func NewCommand() *cobra.Command {
otlpAddress string
applicationNamespaces []string
persistResourceHealth bool
shardingAlgorithm string
)
var command = cobra.Command{
Use: cliName,
@@ -136,7 +134,7 @@ func NewCommand() *cobra.Command {
appController.InvalidateProjectsCache()
}))
kubectl := kubeutil.NewKubectl()
clusterFilter := getClusterFilter(kubeClient, settingsMgr, shardingAlgorithm)
clusterFilter := getClusterFilter()
appController, err = controller.NewApplicationController(
namespace,
settingsMgr,
@@ -154,8 +152,7 @@ func NewCommand() *cobra.Command {
kubectlParallelismLimit,
persistResourceHealth,
clusterFilter,
applicationNamespaces,
)
applicationNamespaces)
errors.CheckError(err)
cacheutil.CollectMetrics(redisClient, appController.GetMetricsServer())
@@ -198,14 +195,13 @@ func NewCommand() *cobra.Command {
command.Flags().StringVar(&otlpAddress, "otlp-address", env.StringFromEnv("ARGOCD_APPLICATION_CONTROLLER_OTLP_ADDRESS", ""), "OpenTelemetry collector address to send traces to")
command.Flags().StringSliceVar(&applicationNamespaces, "application-namespaces", env.StringsFromEnv("ARGOCD_APPLICATION_NAMESPACES", []string{}, ","), "List of additional namespaces that applications are allowed to be reconciled from")
command.Flags().BoolVar(&persistResourceHealth, "persist-resource-health", env.ParseBoolFromEnv("ARGOCD_APPLICATION_CONTROLLER_PERSIST_RESOURCE_HEALTH", true), "Enables storing the managed resources health in the Application CRD")
command.Flags().StringVar(&shardingAlgorithm, "sharding-method", env.StringFromEnv(common.EnvControllerShardingAlgorithm, common.DefaultShardingAlgorithm), "Enables choice of sharding method. Supported sharding methods are : [legacy, round-robin] ")
cacheSrc = appstatecache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {
redisClient = client
})
return &command
}
func getClusterFilter(kubeClient *kubernetes.Clientset, settingsMgr *settings.SettingsManager, shardingAlgorithm string) sharding.ClusterFilterFunction {
func getClusterFilter() func(cluster *v1alpha1.Cluster) bool {
replicas := env.ParseNumFromEnv(common.EnvControllerReplicas, 0, 0, math.MaxInt32)
shard := env.ParseNumFromEnv(common.EnvControllerShard, -1, -math.MaxInt32, math.MaxInt32)
var clusterFilter func(cluster *v1alpha1.Cluster) bool
@@ -216,10 +212,7 @@ func getClusterFilter(kubeClient *kubernetes.Clientset, settingsMgr *settings.Se
errors.CheckError(err)
}
log.Infof("Processing clusters from shard %d", shard)
db := db.NewDB(settingsMgr.GetNamespace(), settingsMgr, kubeClient)
log.Infof("Using filter function: %s", shardingAlgorithm)
distributionFunction := sharding.GetDistributionFunction(db, shardingAlgorithm)
clusterFilter = sharding.GetClusterFilter(distributionFunction, shard)
clusterFilter = sharding.GetClusterFilter(replicas, shard)
} else {
log.Info("Processing all cluster shards")
}

View File

@@ -2,13 +2,10 @@ package command
import (
"fmt"
"math"
"net/http"
"os"
"time"
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
"github.com/argoproj/argo-cd/v2/util/tls"
"github.com/argoproj/pkg/stats"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
@@ -20,6 +17,7 @@ import (
"github.com/argoproj/argo-cd/v2/applicationset/webhook"
cmdutil "github.com/argoproj/argo-cd/v2/cmd/util"
"github.com/argoproj/argo-cd/v2/common"
"github.com/argoproj/argo-cd/v2/reposerver/askpass"
"github.com/argoproj/argo-cd/v2/util/env"
"github.com/argoproj/argo-cd/v2/util/github_app"
@@ -47,22 +45,17 @@ func getSubmoduleEnabled() bool {
func NewCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
metricsAddr string
probeBindAddr string
webhookAddr string
enableLeaderElection bool
namespace string
argocdRepoServer string
policy string
debugLog bool
dryRun bool
enableProgressiveSyncs bool
enableNewGitFileGlobbing bool
repoServerPlaintext bool
repoServerStrictTLS bool
repoServerTimeoutSeconds int
maxConcurrentReconciliations int
clientConfig clientcmd.ClientConfig
metricsAddr string
probeBindAddr string
webhookAddr string
enableLeaderElection bool
namespace string
argocdRepoServer string
policy string
debugLog bool
dryRun bool
enableProgressiveSyncs bool
)
scheme := runtime.NewScheme()
_ = clientgoscheme.AddToScheme(scheme)
@@ -87,7 +80,9 @@ func NewCommand() *cobra.Command {
cli.SetLogLevel(cmdutil.LogLevel)
restConfig, err := clientConfig.ClientConfig()
errors.CheckError(err)
if err != nil {
return err
}
restConfig.UserAgent = fmt.Sprintf("argocd-applicationset-controller/%s (%s)", vers.Version, vers.Platform)
@@ -115,44 +110,28 @@ func NewCommand() *cobra.Command {
os.Exit(1)
}
dynamicClient, err := dynamic.NewForConfig(mgr.GetConfig())
errors.CheckError(err)
if err != nil {
return err
}
k8sClient, err := kubernetes.NewForConfig(mgr.GetConfig())
errors.CheckError(err)
if err != nil {
return err
}
argoSettingsMgr := argosettings.NewSettingsManager(ctx, k8sClient, namespace)
appSetConfig := appclientset.NewForConfigOrDie(mgr.GetConfig())
argoCDDB := db.NewDB(namespace, argoSettingsMgr, k8sClient)
askPassServer := askpass.NewServer()
scmAuth := generators.SCMAuthProviders{
GitHubApps: github_app.NewAuthCredentials(argoCDDB.(db.RepoCredsDB)),
}
tlsConfig := apiclient.TLSConfiguration{
DisableTLS: repoServerPlaintext,
StrictValidation: repoServerPlaintext,
}
if !repoServerPlaintext && repoServerStrictTLS {
pool, err := tls.LoadX509CertPool(
fmt.Sprintf("%s/reposerver/tls/tls.crt", env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath)),
fmt.Sprintf("%s/reposerver/tls/ca.crt", env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath)),
)
errors.CheckError(err)
tlsConfig.Certificates = pool
}
repoClientset := apiclient.NewRepoServerClientset(argocdRepoServer, repoServerTimeoutSeconds, tlsConfig)
argoCDService, err := services.NewArgoCDService(argoCDDB, getSubmoduleEnabled(), repoClientset, enableNewGitFileGlobbing)
errors.CheckError(err)
terminalGenerators := map[string]generators.Generator{
"List": generators.NewListGenerator(),
"Clusters": generators.NewClusterGenerator(mgr.GetClient(), ctx, k8sClient, namespace),
"Git": generators.NewGitGenerator(argoCDService),
"Git": generators.NewGitGenerator(services.NewArgoCDService(argoCDDB, askPassServer, getSubmoduleEnabled())),
"SCMProvider": generators.NewSCMProviderGenerator(mgr.GetClient(), scmAuth),
"ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, dynamicClient, k8sClient, namespace),
"PullRequest": generators.NewPullRequestGenerator(mgr.GetClient(), scmAuth),
"Plugin": generators.NewPluginGenerator(mgr.GetClient(), ctx, k8sClient, namespace),
}
nestedGenerators := map[string]generators.Generator{
@@ -162,7 +141,6 @@ func NewCommand() *cobra.Command {
"SCMProvider": terminalGenerators["SCMProvider"],
"ClusterDecisionResource": terminalGenerators["ClusterDecisionResource"],
"PullRequest": terminalGenerators["PullRequest"],
"Plugin": terminalGenerators["Plugin"],
"Matrix": generators.NewMatrixGenerator(terminalGenerators),
"Merge": generators.NewMergeGenerator(terminalGenerators),
}
@@ -174,7 +152,6 @@ func NewCommand() *cobra.Command {
"SCMProvider": terminalGenerators["SCMProvider"],
"ClusterDecisionResource": terminalGenerators["ClusterDecisionResource"],
"PullRequest": terminalGenerators["PullRequest"],
"Plugin": terminalGenerators["Plugin"],
"Matrix": generators.NewMatrixGenerator(nestedGenerators),
"Merge": generators.NewMergeGenerator(nestedGenerators),
}
@@ -188,6 +165,7 @@ func NewCommand() *cobra.Command {
startWebhookServer(webhookHandler, webhookAddr)
}
go func() { errors.CheckError(askPassServer.Run(askpass.SocketPath)) }()
if err = (&controllers.ApplicationSetReconciler{
Generators: topLevelGenerators,
Client: mgr.GetClient(),
@@ -199,7 +177,7 @@ func NewCommand() *cobra.Command {
KubeClientset: k8sClient,
ArgoDB: argoCDDB,
EnableProgressiveSyncs: enableProgressiveSyncs,
}).SetupWithManager(mgr, enableProgressiveSyncs, maxConcurrentReconciliations); err != nil {
}).SetupWithManager(mgr, enableProgressiveSyncs); err != nil {
log.Error(err, "unable to create controller", "controller", "ApplicationSet")
os.Exit(1)
}
@@ -228,11 +206,6 @@ func NewCommand() *cobra.Command {
command.Flags().StringVar(&cmdutil.LogLevel, "loglevel", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_LOGLEVEL", "info"), "Set the logging level. One of: debug|info|warn|error")
command.Flags().BoolVar(&dryRun, "dry-run", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_DRY_RUN", false), "Enable dry run mode")
command.Flags().BoolVar(&enableProgressiveSyncs, "enable-progressive-syncs", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_PROGRESSIVE_SYNCS", false), "Enable use of the experimental progressive syncs feature.")
command.Flags().BoolVar(&enableNewGitFileGlobbing, "enable-new-git-file-globbing", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_NEW_GIT_FILE_GLOBBING", false), "Enable new globbing in Git files generator.")
command.Flags().BoolVar(&repoServerPlaintext, "repo-server-plaintext", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_REPO_SERVER_PLAINTEXT", false), "Disable TLS on connections to repo server")
command.Flags().BoolVar(&repoServerStrictTLS, "repo-server-strict-tls", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_REPO_SERVER_STRICT_TLS", false), "Whether to use strict validation of the TLS cert presented by the repo server")
command.Flags().IntVar(&repoServerTimeoutSeconds, "repo-server-timeout-seconds", env.ParseNumFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_REPO_SERVER_TIMEOUT_SECONDS", 60, 0, math.MaxInt64), "Repo server RPC call timeout seconds.")
command.Flags().IntVar(&maxConcurrentReconciliations, "concurrent-reconciliations", env.ParseNumFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_CONCURRENT_RECONCILIATIONS", 10, 1, 100), "Max concurrent reconciliations limit for the controller")
return &command
}

View File

@@ -30,7 +30,7 @@ func NewCommand() *cobra.Command {
var command = cobra.Command{
Use: cliName,
Short: "Run ArgoCD ConfigManagementPlugin Server",
Long: "ArgoCD ConfigManagementPlugin Server is an internal service which runs as sidecar container in reposerver deployment. The following configuration options are available:",
Long: "ArgoCD ConfigManagementPlugin Server is an internal service which runs as sidecar container in reposerver deployment. It can be configured by following options.",
DisableAutoGenTag: true,
RunE: func(c *cobra.Command, args []string) error {
ctx := c.Context()

View File

@@ -8,11 +8,11 @@ import (
"github.com/argoproj/argo-cd/v2/common"
"github.com/ghodss/yaml"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/yaml"
cmdutil "github.com/argoproj/argo-cd/v2/cmd/util"
"github.com/argoproj/argo-cd/v2/util/cli"

View File

@@ -153,8 +153,8 @@ func NewCommand() *cobra.Command {
command.Flags().IntVar(&processorsCount, "processors-count", 1, "Processors count.")
command.Flags().StringVar(&appLabelSelector, "app-label-selector", "", "App label selector.")
command.Flags().StringVar(&namespace, "namespace", "", "Namespace which controller handles. Current namespace if empty.")
command.Flags().StringVar(&logLevel, "loglevel", env.StringFromEnv("ARGOCD_NOTIFICATIONS_CONTROLLER_LOGLEVEL", "info"), "Set the logging level. One of: debug|info|warn|error")
command.Flags().StringVar(&logFormat, "logformat", env.StringFromEnv("ARGOCD_NOTIFICATIONS_CONTROLLER_LOGFORMAT", "text"), "Set the logging format. One of: text|json")
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
command.Flags().StringVar(&logFormat, "logformat", "text", "Set the logging format. One of: text|json")
command.Flags().IntVar(&metricsPort, "metrics-port", defaultMetricsPort, "Metrics port")
command.Flags().StringVar(&argocdRepoServer, "argocd-repo-server", common.DefaultRepoServerAddr, "Argo CD repo server address")
command.Flags().BoolVar(&argocdRepoServerPlaintext, "argocd-repo-server-plaintext", false, "Use a plaintext client (non-TLS) to connect to repository server")

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