Compare commits

..

2 Commits

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

go mod tidy

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

one test file for both implementations

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

simplify

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

fix test for linux

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

fix git client mock

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

fix git client mock

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

address comments

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

unit tests

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

lint

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

fix image, fix health checks, fix merge issue

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

fix lint issues

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

remove code that doesn't work for GHE

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

changes from comments

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

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

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

deepcopy gen

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

5
.gitattributes vendored
View File

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

View File

@@ -2,7 +2,7 @@
name: Bug report name: Bug report
about: Create a report to help us improve about: Create a report to help us improve
title: '' title: ''
labels: ['bug', 'triage/pending'] labels: 'bug'
assignees: '' assignees: ''
--- ---
@@ -10,9 +10,9 @@ assignees: ''
Checklist: Checklist:
- [ ] I've searched in the docs and FAQ for my answer: https://bit.ly/argocd-faq. * [ ] I've searched in the docs and FAQ for my answer: https://bit.ly/argocd-faq.
- [ ] I've included steps to reproduce the bug. * [ ] I've included steps to reproduce the bug.
- [ ] I've pasted the output of `argocd version`. * [ ] I've pasted the output of `argocd version`.
**Describe the bug** **Describe the bug**

View File

@@ -2,10 +2,9 @@
name: Enhancement proposal name: Enhancement proposal
about: Propose an enhancement for this project about: Propose an enhancement for this project
title: '' title: ''
labels: ['enhancement', 'triage/pending'] labels: 'enhancement'
assignees: '' assignees: ''
--- ---
# Summary # Summary
What change you think needs making. What change you think needs making.
@@ -16,4 +15,4 @@ Please give examples of your use case, e.g. when would you use this.
# Proposal # Proposal
How do you think this should be implemented? How do you think this should be implemented?

View File

@@ -2,17 +2,17 @@
name: New Dev Tool Request name: New Dev Tool Request
about: This is a request for adding a new tool for setting up a dev environment. about: This is a request for adding a new tool for setting up a dev environment.
title: '' title: ''
labels: ['component:dev-env', 'triage/pending'] labels: ''
assignees: '' assignees: ''
--- ---
Checklist: Checklist:
- [ ] I am willing to maintain this tool, or have another Argo CD maintainer who is. * [ ] I am willing to maintain this tool, or have another Argo CD maintainer who is.
- [ ] I have another Argo CD maintainer who is willing to help maintain this tool (there needs to be at least two maintainers willing to maintain this tool) * [ ] I have another Argo CD maintainer who is willing to help maintain this tool (there needs to be at least two maintainers willing to maintain this tool)
- [ ] I have a lead sponsor who is a core Argo CD maintainer * [ ] I have a lead sponsor who is a core Argo CD maintainer
- [ ] There is a PR which adds said tool - this is so that the maintainers can assess the impact of having this in the tree * [ ] There is a PR which adds said tool - this is so that the maintainers can assess the impact of having this in the tree
- [ ] I have given a motivation why this should be added * [ ] I have given a motivation why this should be added
### The proposer ### The proposer
@@ -24,7 +24,7 @@ Checklist:
### Motivation ### Motivation
<!-- Why this tool would be useful to have in the tree. --> <!-- Why this tool would be useful to have in the tree. -->
### Link to PR (Optional) ### Link to PR (Optional)

View File

@@ -22,6 +22,5 @@ Target GA date: ___. __, ____
- [ ] At release date, evaluate if any bugs justify delaying the release. If not, cut the release (or delegate this task to an Approver and coordinate timing) - [ ] At release date, evaluate if any bugs justify delaying the release. If not, cut the release (or delegate this task to an Approver and coordinate timing)
- [ ] If unreleased changes are on the release branch for {current minor version minus 3}, cut a final patch release for that series (or delegate this task to an Approver and coordinate timing) - [ ] If unreleased changes are on the release branch for {current minor version minus 3}, cut a final patch release for that series (or delegate this task to an Approver and coordinate timing)
- [ ] After the release, post in #argo-cd that the {current minor version minus 3} has reached EOL (example: https://cloud-native.slack.com/archives/C01TSERG0KZ/p1667336234059729) - [ ] After the release, post in #argo-cd that the {current minor version minus 3} has reached EOL (example: https://cloud-native.slack.com/archives/C01TSERG0KZ/p1667336234059729)
- [ ] Update the `stable` tag to be the GA release you just pushed
- [ ] (For the next release champion) Review the [items scheduled for the next release](https://github.com/orgs/argoproj/projects/25). If any item does not have an assignee who can commit to finish the feature, move it to the next release. - [ ] (For the next release champion) Review the [items scheduled for the next release](https://github.com/orgs/argoproj/projects/25). If any item does not have an assignee who can commit to finish the feature, move it to the next release.
- [ ] (For the next release champion) Schedule a time mid-way through the release cycle to review items again. - [ ] (For the next release champion) Schedule a time mid-way through the release cycle to review items again.

View File

@@ -1,11 +1,10 @@
--- ---
name: Security log name: Security log
about: Propose adding security-related logs or tagging existing logs with security fields about: Propose adding security-related logs or tagging existing logs with security fields
title: 'seclog: [Event Description]' title: "seclog: [Event Description]"
labels: ['security', 'triage/pending'] labels: security-log
assignees: '' assignees: notfromstatefarm
--- ---
# Event to be logged # Event to be logged
Specify the event that needs to be logged or existing logs that need to be tagged. Specify the event that needs to be logged or existing logs that need to be tagged.
@@ -17,3 +16,4 @@ What security level should these events be logged under? Refer to https://argo-c
# Common Weakness Enumeration # Common Weakness Enumeration
Is there an associated [CWE](https://cwe.mitre.org/) that could be tagged as well? Is there an associated [CWE](https://cwe.mitre.org/) that could be tagged as well?

3
.github/cherry-pick-bot.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
enabled: true
preservePullRequestTitle: true

View File

@@ -1,15 +0,0 @@
module.exports = {
platform: 'github',
gitAuthor: 'renovate[bot] <renovate[bot]@users.noreply.github.com>',
autodiscover: false,
allowPostUpgradeCommandTemplating: true,
allowedPostUpgradeCommands: ["make mockgen"],
extends: [
"github>argoproj/argo-cd//renovate-presets/commons.json5",
"github>argoproj/argo-cd//renovate-presets/custom-managers/shell.json5",
"github>argoproj/argo-cd//renovate-presets/custom-managers/yaml.json5",
"github>argoproj/argo-cd//renovate-presets/fix/disable-all-updates.json5",
"github>argoproj/argo-cd//renovate-presets/devtool.json5",
"github>argoproj/argo-cd//renovate-presets/docs.json5"
]
}

View File

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

View File

@@ -8,7 +8,7 @@ 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. * [ ] 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 states what changed and the related issues number (used for the release note).
* [ ] The title of the PR conforms to the [Title of the PR](https://argo-cd.readthedocs.io/en/latest/developer-guide/submit-your-pr/#title-of-the-pr) * [ ] 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 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. * [ ] 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? * [ ] Does this PR require documentation updates?

View File

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

View File

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

View File

@@ -1,121 +0,0 @@
name: Cherry Pick Single
on:
workflow_call:
inputs:
merge_commit_sha:
required: true
type: string
description: "The merge commit SHA to cherry-pick"
version_number:
required: true
type: string
description: "The version number (from cherry-pick/ label)"
pr_number:
required: true
type: string
description: "The original PR number"
pr_title:
required: true
type: string
description: "The original PR title"
secrets:
CHERRYPICK_APP_ID:
required: true
CHERRYPICK_APP_PRIVATE_KEY:
required: true
jobs:
cherry-pick:
name: Cherry Pick to ${{ inputs.version_number }}
runs-on: ubuntu-latest
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1
with:
app-id: ${{ secrets.CHERRYPICK_APP_ID }}
private-key: ${{ secrets.CHERRYPICK_APP_PRIVATE_KEY }}
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
token: ${{ steps.generate-token.outputs.token }}
- name: Configure Git
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Cherry pick commit
id: cherry-pick
run: |
set -e
MERGE_COMMIT="${{ inputs.merge_commit_sha }}"
TARGET_BRANCH="release-${{ inputs.version_number }}"
echo "🍒 Cherry-picking commit $MERGE_COMMIT to branch $TARGET_BRANCH"
# Check if target branch exists
if ! git show-ref --verify --quiet "refs/remotes/origin/$TARGET_BRANCH"; then
echo "❌ Target branch '$TARGET_BRANCH' does not exist"
exit 1
fi
# Create new branch for cherry-pick
CHERRY_PICK_BRANCH="cherry-pick-${{ inputs.pr_number }}-to-${TARGET_BRANCH}"
git checkout -b "$CHERRY_PICK_BRANCH" "origin/$TARGET_BRANCH"
# Perform cherry-pick
if git cherry-pick -m 1 "$MERGE_COMMIT"; then
echo "✅ Cherry-pick successful"
# Extract Signed-off-by from the cherry-pick commit
SIGNOFF=$(git log -1 --pretty=format:"%B" | grep -E '^Signed-off-by:' || echo "")
# Push the new branch
git push origin "$CHERRY_PICK_BRANCH"
# Save data for PR creation
echo "branch_name=$CHERRY_PICK_BRANCH" >> "$GITHUB_OUTPUT"
echo "signoff=$SIGNOFF" >> "$GITHUB_OUTPUT"
echo "target_branch=$TARGET_BRANCH" >> "$GITHUB_OUTPUT"
else
echo "❌ Cherry-pick failed due to conflicts"
git cherry-pick --abort
exit 1
fi
- name: Create Pull Request
run: |
# Create cherry-pick PR
TITLE="${PR_TITLE} (cherry-pick #${{ inputs.pr_number }} for ${{ inputs.version_number }})"
BODY=$(cat <<EOF
Cherry-picked ${PR_TITLE} (#${{ inputs.pr_number }})
${{ steps.cherry-pick.outputs.signoff }}
EOF
)
gh pr create \
--title "$TITLE" \
--body "$BODY" \
--base "${{ steps.cherry-pick.outputs.target_branch }}" \
--head "${{ steps.cherry-pick.outputs.branch_name }}"
# Comment on original PR
gh pr comment ${{ inputs.pr_number }} \
--body "🍒 Cherry-pick PR created for ${{ inputs.version_number }}: #$(gh pr list --head ${{ steps.cherry-pick.outputs.branch_name }} --json number --jq '.[0].number')"
env:
PR_TITLE: ${{ inputs.pr_title }}
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
- name: Comment on failure
if: failure()
run: |
gh pr comment ${{ inputs.pr_number }} \
--body "❌ Cherry-pick failed for ${{ inputs.version_number }}. Please check the [workflow logs](https://github.com/argoproj/argo-cd/actions/runs/${{ github.run_id }}) for details."
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}

View File

@@ -1,53 +0,0 @@
name: Cherry Pick
on:
pull_request_target:
branches:
- master
types: ["labeled", "closed"]
jobs:
find-labels:
name: Find Cherry Pick Labels
if: |
github.event.pull_request.merged == true && (
(github.event.action == 'labeled' && startsWith(github.event.label.name, 'cherry-pick/')) ||
(github.event.action == 'closed' && contains(toJSON(github.event.pull_request.labels.*.name), 'cherry-pick/'))
)
runs-on: ubuntu-latest
outputs:
labels: ${{ steps.extract-labels.outputs.labels }}
steps:
- name: Extract cherry-pick labels
id: extract-labels
run: |
if [[ "${{ github.event.action }}" == "labeled" ]]; then
# Label was just added - use it directly
LABEL_NAME="${{ github.event.label.name }}"
VERSION="${LABEL_NAME#cherry-pick/}"
CHERRY_PICK_DATA='[{"label":"'$LABEL_NAME'","version":"'$VERSION'"}]'
else
# PR was closed - find all cherry-pick labels
CHERRY_PICK_DATA=$(echo '${{ toJSON(github.event.pull_request.labels) }}' | jq -c '[.[] | select(.name | startswith("cherry-pick/")) | {label: .name, version: (.name | sub("cherry-pick/"; ""))}]')
fi
echo "labels=$CHERRY_PICK_DATA" >> "$GITHUB_OUTPUT"
echo "Found cherry-pick data: $CHERRY_PICK_DATA"
cherry-pick:
name: Cherry Pick
needs: find-labels
if: needs.find-labels.outputs.labels != '[]'
strategy:
matrix:
include: ${{ fromJSON(needs.find-labels.outputs.labels) }}
fail-fast: false
uses: ./.github/workflows/cherry-pick-single.yml
with:
merge_commit_sha: ${{ github.event.pull_request.merge_commit_sha }}
version_number: ${{ matrix.version }}
pr_number: ${{ github.event.pull_request.number }}
pr_title: ${{ github.event.pull_request.title }}
secrets:
CHERRYPICK_APP_ID: ${{ vars.CHERRYPICK_APP_ID }}
CHERRYPICK_APP_PRIVATE_KEY: ${{ secrets.CHERRYPICK_APP_PRIVATE_KEY }}

View File

@@ -14,11 +14,11 @@ on:
env: env:
# Golang version to use across CI steps # Golang version to use across CI steps
# renovate: datasource=golang-version packageName=golang # renovate: datasource=golang-version packageName=golang
GOLANG_VERSION: '1.25.3' GOLANG_VERSION: '1.23.3'
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} cancel-in-progress: true
permissions: permissions:
contents: read contents: read
@@ -31,15 +31,15 @@ jobs:
frontend: ${{ steps.filter.outputs.frontend_any_changed }} frontend: ${{ steps.filter.outputs.frontend_any_changed }}
docs: ${{ steps.filter.outputs.docs_any_changed }} docs: ${{ steps.filter.outputs.docs_any_changed }}
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0 - uses: tj-actions/changed-files@bab30c2299617f6615ec02a68b9a40d10bd21366 # v45.0.5
id: filter id: filter
with: with:
# Any file which is not under docs/, ui/ or is not a markdown file is counted as a backend file # Any file which is not under docs/, ui/ or is not a markdown file is counted as a backend file
files_yaml: | files_yaml: |
backend: backend:
- '!ui/**' - '!ui/**'
- '!**.md' - '!**.md'
- '!**/*.md' - '!**/*.md'
- '!docs/**' - '!docs/**'
frontend: frontend:
@@ -55,9 +55,9 @@ jobs:
- changes - changes
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Download all Go modules - name: Download all Go modules
@@ -67,6 +67,7 @@ jobs:
run: | run: |
go mod tidy go mod tidy
git diff --exit-code -- . git diff --exit-code -- .
build-go: build-go:
name: Build & cache Go code name: Build & cache Go code
if: ${{ needs.changes.outputs.backend == 'true' }} if: ${{ needs.changes.outputs.backend == 'true' }}
@@ -75,13 +76,13 @@ jobs:
- changes - changes
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Restore go build cache - name: Restore go build cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with: with:
path: ~/.cache/go-build path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }} key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -93,8 +94,8 @@ jobs:
lint-go: lint-go:
permissions: permissions:
contents: read # for actions/checkout to fetch code contents: read # for actions/checkout to fetch code
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
name: Lint Go code name: Lint Go code
if: ${{ needs.changes.outputs.backend == 'true' }} if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
@@ -102,16 +103,16 @@ jobs:
- changes - changes
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Run golangci-lint - name: Run golangci-lint
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1
with: with:
# renovate: datasource=go packageName=github.com/golangci/golangci-lint versioning=regex:^v(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)?$ # renovate: datasource=go packageName=github.com/golangci/golangci-lint versioning=regex:^v(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)?$
version: v2.5.0 version: v1.62.2
args: --verbose args: --verbose
test-go: test-go:
@@ -128,11 +129,11 @@ jobs:
- name: Create checkout directory - name: Create checkout directory
run: mkdir -p ~/go/src/github.com/argoproj run: mkdir -p ~/go/src/github.com/argoproj
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Create symlink in GOPATH - name: Create symlink in GOPATH
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Install required packages - name: Install required packages
@@ -152,7 +153,7 @@ jobs:
run: | run: |
echo "/usr/local/bin" >> $GITHUB_PATH echo "/usr/local/bin" >> $GITHUB_PATH
- name: Restore go build cache - name: Restore go build cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with: with:
path: ~/.cache/go-build path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }} key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -173,7 +174,7 @@ jobs:
- name: Run all unit tests - name: Run all unit tests
run: make test-local run: make test-local
- name: Generate test results artifacts - name: Generate test results artifacts
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with: with:
name: test-results name: test-results
path: test-results path: test-results
@@ -192,11 +193,11 @@ jobs:
- name: Create checkout directory - name: Create checkout directory
run: mkdir -p ~/go/src/github.com/argoproj run: mkdir -p ~/go/src/github.com/argoproj
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Create symlink in GOPATH - name: Create symlink in GOPATH
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Install required packages - name: Install required packages
@@ -216,7 +217,7 @@ jobs:
run: | run: |
echo "/usr/local/bin" >> $GITHUB_PATH echo "/usr/local/bin" >> $GITHUB_PATH
- name: Restore go build cache - name: Restore go build cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with: with:
path: ~/.cache/go-build path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }} key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
@@ -237,7 +238,7 @@ jobs:
- name: Run all unit tests - name: Run all unit tests
run: make test-race-local run: make test-race-local
- name: Generate test results artifacts - name: Generate test results artifacts
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with: with:
name: race-results name: race-results
path: test-results/ path: test-results/
@@ -250,9 +251,9 @@ jobs:
- changes - changes
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Create symlink in GOPATH - name: Create symlink in GOPATH
@@ -302,15 +303,15 @@ jobs:
- changes - changes
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup NodeJS - name: Setup NodeJS
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with: with:
# renovate: datasource=node-version packageName=node versioning=node # renovate: datasource=node-version packageName=node versioning=node
node-version: '22.9.0' node-version: '22.9.0'
- name: Restore node dependency cache - name: Restore node dependency cache
id: cache-dependencies id: cache-dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with: with:
path: ui/node_modules path: ui/node_modules
key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }} key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }}
@@ -332,15 +333,6 @@ jobs:
run: yarn lint run: yarn lint
working-directory: ui/ working-directory: ui/
shellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- run: |
sudo apt-get install shellcheck
shellcheck -e SC2059 -e SC2154 -e SC2034 -e SC2016 -e SC1091 $(find . -type f -name '*.sh' | grep -v './ui/node_modules') | tee sc.log
test ! -s sc.log
analyze: analyze:
name: Process & analyze test artifacts name: Process & analyze test artifacts
if: ${{ needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true' }} if: ${{ needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true' }}
@@ -354,12 +346,12 @@ jobs:
sonar_secret: ${{ secrets.SONAR_TOKEN }} sonar_secret: ${{ secrets.SONAR_TOKEN }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Restore node dependency cache - name: Restore node dependency cache
id: cache-dependencies id: cache-dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with: with:
path: ui/node_modules path: ui/node_modules
key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }} key: ${{ runner.os }}-node-dep-v2-${{ hashFiles('**/yarn.lock') }}
@@ -367,12 +359,12 @@ jobs:
run: | run: |
rm -rf ui/node_modules/argo-ui/node_modules rm -rf ui/node_modules/argo-ui/node_modules
- name: Get e2e code coverage - name: Get e2e code coverage
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with: with:
name: e2e-code-coverage name: e2e-code-coverage
path: e2e-code-coverage path: e2e-code-coverage
- name: Get unit test code coverage - name: Get unit test code coverage
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with: with:
name: test-results name: test-results
path: test-results path: test-results
@@ -384,15 +376,15 @@ jobs:
run: | run: |
go tool covdata percent -i=test-results,e2e-code-coverage/applicationset-controller,e2e-code-coverage/repo-server,e2e-code-coverage/app-controller,e2e-code-coverage/commit-server -o test-results/full-coverage.out go tool covdata percent -i=test-results,e2e-code-coverage/applicationset-controller,e2e-code-coverage/repo-server,e2e-code-coverage/app-controller,e2e-code-coverage/commit-server -o test-results/full-coverage.out
- name: Upload code coverage information to codecov.io - name: Upload code coverage information to codecov.io
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0
with: with:
files: test-results/full-coverage.out file: test-results/full-coverage.out
fail_ci_if_error: true fail_ci_if_error: true
env: env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- name: Upload test results to Codecov - name: Upload test results to Codecov
if: github.ref == 'refs/heads/master' && github.event_name == 'push' && github.repository == 'argoproj/argo-cd' if: github.ref == 'refs/heads/master' && github.event_name == 'push' && github.repository == 'argoproj/argo-cd'
uses: codecov/test-results-action@47f89e9acb64b76debcd5ea40642d25a4adced9f # v1.1.1 uses: codecov/test-results-action@9739113ad922ea0a9abb4b2c0f8bf6a4aa8ef820 # v1.0.1
with: with:
file: test-results/junit.xml file: test-results/junit.xml
fail_ci_if_error: true fail_ci_if_error: true
@@ -401,54 +393,46 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
uses: SonarSource/sonarqube-scan-action@1a6d90ebcb0e6a6b1d87e37ba693fe453195ae25 # v5.3.1 uses: SonarSource/sonarqube-scan-action@1b442ee39ac3fa7c2acdd410208dcb2bcfaae6c4 # v4.1.0
if: env.sonar_secret != '' if: env.sonar_secret != ''
test-e2e: test-e2e:
name: Run end-to-end tests name: Run end-to-end tests
if: ${{ needs.changes.outputs.backend == 'true' }} if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: oracle-vm-16cpu-64gb-x86-64 runs-on: ubuntu-22.04
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
# latest: true means that this version mush upload the coverage report to codecov.io
# We designate the latest version because we only collect code coverage for that version.
k3s: k3s:
- version: v1.33.1
latest: true
- version: v1.32.1
latest: false
- version: v1.31.0 - version: v1.31.0
latest: false # We designate the latest version because we only collect code coverage for that version.
latest: true
- version: v1.30.4 - version: v1.30.4
latest: false latest: false
- version: v1.29.8
latest: false
- version: v1.28.13
latest: false
needs: needs:
- build-go - build-go
- changes - changes
env: env:
GOPATH: /home/ubuntu/go GOPATH: /home/runner/go
ARGOCD_FAKE_IN_CLUSTER: 'true' ARGOCD_FAKE_IN_CLUSTER: "true"
ARGOCD_SSH_DATA_PATH: '/tmp/argo-e2e/app/config/ssh' ARGOCD_SSH_DATA_PATH: "/tmp/argo-e2e/app/config/ssh"
ARGOCD_TLS_DATA_PATH: '/tmp/argo-e2e/app/config/tls' ARGOCD_TLS_DATA_PATH: "/tmp/argo-e2e/app/config/tls"
ARGOCD_E2E_SSH_KNOWN_HOSTS: '../fixture/certs/ssh_known_hosts' ARGOCD_E2E_SSH_KNOWN_HOSTS: "../fixture/certs/ssh_known_hosts"
ARGOCD_E2E_K3S: 'true' ARGOCD_E2E_K3S: "true"
ARGOCD_IN_CI: 'true' ARGOCD_IN_CI: "true"
ARGOCD_E2E_APISERVER_PORT: '8088' ARGOCD_E2E_APISERVER_PORT: "8088"
ARGOCD_APPLICATION_NAMESPACES: 'argocd-e2e-external,argocd-e2e-external-2' ARGOCD_APPLICATION_NAMESPACES: "argocd-e2e-external,argocd-e2e-external-2"
ARGOCD_SERVER: '127.0.0.1:8088' ARGOCD_SERVER: "127.0.0.1:8088"
GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }} GITLAB_TOKEN: ${{ secrets.E2E_TEST_GITLAB_TOKEN }}
steps: steps:
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
with:
large-packages: false
docker-images: false
swap-storage: false
tool-cache: false
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: GH actions workaround - Kill XSP4 process - name: GH actions workaround - Kill XSP4 process
@@ -461,19 +445,19 @@ jobs:
set -x set -x
curl -sfL https://get.k3s.io | sh - curl -sfL https://get.k3s.io | sh -
sudo chmod -R a+rw /etc/rancher/k3s sudo chmod -R a+rw /etc/rancher/k3s
sudo mkdir -p $HOME/.kube && sudo chown -R ubuntu $HOME/.kube sudo mkdir -p $HOME/.kube && sudo chown -R runner $HOME/.kube
sudo k3s kubectl config view --raw > $HOME/.kube/config sudo k3s kubectl config view --raw > $HOME/.kube/config
sudo chown ubuntu $HOME/.kube/config sudo chown runner $HOME/.kube/config
sudo chmod go-r $HOME/.kube/config sudo chmod go-r $HOME/.kube/config
kubectl version kubectl version
- name: Restore go build cache - name: Restore go build cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with: with:
path: ~/.cache/go-build path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }} key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
- name: Add ~/go/bin to PATH - name: Add ~/go/bin to PATH
run: | run: |
echo "/home/ubuntu/go/bin" >> $GITHUB_PATH echo "/home/runner/go/bin" >> $GITHUB_PATH
- name: Add /usr/local/bin to PATH - name: Add /usr/local/bin to PATH
run: | run: |
echo "/usr/local/bin" >> $GITHUB_PATH echo "/usr/local/bin" >> $GITHUB_PATH
@@ -493,13 +477,13 @@ jobs:
git config --global user.email "john.doe@example.com" git config --global user.email "john.doe@example.com"
- name: Pull Docker image required for tests - name: Pull Docker image required for tests
run: | run: |
docker pull ghcr.io/dexidp/dex:v2.43.0 docker pull ghcr.io/dexidp/dex:v2.41.1
docker pull argoproj/argo-cd-ci-builder:v1.0.0 docker pull argoproj/argo-cd-ci-builder:v1.0.0
docker pull redis:8.2.1-alpine docker pull redis:7.0.15-alpine
- name: Create target directory for binaries in the build-process - name: Create target directory for binaries in the build-process
run: | run: |
mkdir -p dist mkdir -p dist
chown ubuntu dist chown runner dist
- name: Run E2E server and wait for it being available - name: Run E2E server and wait for it being available
timeout-minutes: 30 timeout-minutes: 30
run: | run: |
@@ -525,13 +509,13 @@ jobs:
goreman run stop-all || echo "goreman trouble" goreman run stop-all || echo "goreman trouble"
sleep 30 sleep 30
- name: Upload e2e coverage report - name: Upload e2e coverage report
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with: with:
name: e2e-code-coverage name: e2e-code-coverage
path: /tmp/coverage path: /tmp/coverage
if: ${{ matrix.k3s.latest }} if: ${{ matrix.k3s.latest }}
- name: Upload e2e-server logs - name: Upload e2e-server logs
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with: with:
name: e2e-server-k8s${{ matrix.k3s.version }}.log name: e2e-server-k8s${{ matrix.k3s.version }}.log
path: /tmp/e2e-server.log path: /tmp/e2e-server.log
@@ -558,4 +542,4 @@ jobs:
exit 0 exit 0
else else
exit 1 exit 1
fi fi

View File

@@ -29,11 +29,11 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
# Use correct go version. https://github.com/github/codeql-action/issues/1842#issuecomment-1704398087 # Use correct go version. https://github.com/github/codeql-action/issues/1842#issuecomment-1704398087
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version-file: go.mod go-version-file: go.mod

View File

@@ -17,9 +17,11 @@ on:
platforms: platforms:
required: true required: true
type: string type: string
default: linux/amd64
push: push:
required: true required: true
type: boolean type: boolean
default: false
target: target:
required: false required: false
type: string type: string
@@ -56,27 +58,26 @@ jobs:
image-digest: ${{ steps.image.outputs.digest }} image-digest: ${{ steps.image.outputs.digest }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
if: ${{ github.ref_type == 'tag'}} if: ${{ github.ref_type == 'tag'}}
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
if: ${{ github.ref_type != 'tag'}} if: ${{ github.ref_type != 'tag'}}
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ inputs.go-version }} go-version: ${{ inputs.go-version }}
cache: false
- name: Install cosign - name: Install cosign
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
- uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 - uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1
- name: Setup tags for container image as a CSV type - name: Setup tags for container image as a CSV type
run: | run: |
@@ -103,7 +104,7 @@ jobs:
echo 'EOF' >> $GITHUB_ENV echo 'EOF' >> $GITHUB_ENV
- name: Login to Quay.io - name: Login to Quay.io
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with: with:
registry: quay.io registry: quay.io
username: ${{ secrets.quay_username }} username: ${{ secrets.quay_username }}
@@ -111,7 +112,7 @@ jobs:
if: ${{ inputs.quay_image_name && inputs.push }} if: ${{ inputs.quay_image_name && inputs.push }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ secrets.ghcr_username }} username: ${{ secrets.ghcr_username }}
@@ -119,7 +120,7 @@ jobs:
if: ${{ inputs.ghcr_image_name && inputs.push }} if: ${{ inputs.ghcr_image_name && inputs.push }}
- name: Login to dockerhub Container Registry - name: Login to dockerhub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with: with:
username: ${{ secrets.docker_username }} username: ${{ secrets.docker_username }}
password: ${{ secrets.docker_password }} password: ${{ secrets.docker_password }}
@@ -142,7 +143,7 @@ jobs:
- name: Build and push container image - name: Build and push container image
id: image id: image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 #v6.18.0 uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 #v6.10.0
with: with:
context: . context: .
platforms: ${{ inputs.platforms }} platforms: ${{ inputs.platforms }}

View File

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

View File

@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@@ -64,7 +64,7 @@ jobs:
git stash pop git stash pop
- name: Create pull request - name: Create pull request
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with: with:
commit-message: "Bump version to ${{ inputs.TARGET_VERSION }}" commit-message: "Bump version to ${{ inputs.TARGET_VERSION }}"
title: "Bump version to ${{ inputs.TARGET_VERSION }} on ${{ inputs.TARGET_BRANCH }} branch" title: "Bump version to ${{ inputs.TARGET_VERSION }} on ${{ inputs.TARGET_BRANCH }} branch"

View File

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

View File

@@ -11,7 +11,7 @@ permissions: {}
env: env:
# renovate: datasource=golang-version packageName=golang # renovate: datasource=golang-version packageName=golang
GOLANG_VERSION: '1.25.3' # Note: go-version must also be set in job argocd-image.with.go-version GOLANG_VERSION: '1.23.3' # Note: go-version must also be set in job argocd-image.with.go-version
jobs: jobs:
argocd-image: argocd-image:
@@ -25,81 +25,43 @@ jobs:
quay_image_name: quay.io/argoproj/argocd:${{ github.ref_name }} quay_image_name: quay.io/argoproj/argocd:${{ github.ref_name }}
# Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations) # Note: cannot use env variables to set go-version (https://docs.github.com/en/actions/using-workflows/reusing-workflows#limitations)
# renovate: datasource=golang-version packageName=golang # renovate: datasource=golang-version packageName=golang
go-version: 1.25.3 go-version: 1.23.3
platforms: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le platforms: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
push: true push: true
secrets: secrets:
quay_username: ${{ secrets.RELEASE_QUAY_USERNAME }} quay_username: ${{ secrets.RELEASE_QUAY_USERNAME }}
quay_password: ${{ secrets.RELEASE_QUAY_TOKEN }} quay_password: ${{ secrets.RELEASE_QUAY_TOKEN }}
setup-variables:
name: Setup Release Variables
if: github.repository == 'argoproj/argo-cd'
runs-on: ubuntu-22.04
outputs:
is_pre_release: ${{ steps.var.outputs.is_pre_release }}
is_latest_release: ${{ steps.var.outputs.is_latest_release }}
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup variables
id: var
run: |
set -xue
# Fetch all tag information
git fetch --prune --tags --force
LATEST_RELEASE_TAG=$(git -c 'versionsort.suffix=-rc' tag --list --sort=version:refname | grep -v '-' | tail -n1)
PRE_RELEASE=false
# Check if latest tag is a pre-release
if echo ${{ github.ref_name }} | grep -E -- '-rc[0-9]+$';then
PRE_RELEASE=true
fi
IS_LATEST=false
# Ensure latest release tag matches github.ref_name
if [[ $LATEST_RELEASE_TAG == ${{ github.ref_name }} ]];then
IS_LATEST=true
fi
echo "is_pre_release=$PRE_RELEASE" >> $GITHUB_OUTPUT
echo "is_latest_release=$IS_LATEST" >> $GITHUB_OUTPUT
argocd-image-provenance: argocd-image-provenance:
needs: [argocd-image] needs: [argocd-image]
permissions: permissions:
actions: read # for detecting the Github Actions environment. actions: read # for detecting the Github Actions environment.
id-token: write # for creating OIDC tokens for signing. id-token: write # for creating OIDC tokens for signing.
packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues) packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator # Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
if: github.repository == 'argoproj/argo-cd' if: github.repository == 'argoproj/argo-cd'
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0 uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
with: with:
image: quay.io/argoproj/argocd image: quay.io/argoproj/argocd
digest: ${{ needs.argocd-image.outputs.image-digest }} digest: ${{ needs.argocd-image.outputs.image-digest }}
secrets: secrets:
registry-username: ${{ secrets.RELEASE_QUAY_USERNAME }} registry-username: ${{ secrets.RELEASE_QUAY_USERNAME }}
registry-password: ${{ secrets.RELEASE_QUAY_TOKEN }} registry-password: ${{ secrets.RELEASE_QUAY_TOKEN }}
goreleaser: goreleaser:
needs: needs:
- setup-variables
- argocd-image - argocd-image
- argocd-image-provenance - argocd-image-provenance
permissions: permissions:
contents: write # used for uploading assets contents: write # used for uploading assets
if: github.repository == 'argoproj/argo-cd' if: github.repository == 'argoproj/argo-cd'
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env:
GORELEASER_MAKE_LATEST: ${{ needs.setup-variables.outputs.is_latest_release }}
outputs: outputs:
hashes: ${{ steps.hash.outputs.hashes }} hashes: ${{ steps.hash.outputs.hashes }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@@ -108,16 +70,14 @@ jobs:
run: git fetch --force --tags run: git fetch --force --tags
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
cache: false
- name: Set GORELEASER_PREVIOUS_TAG # Workaround, GoReleaser uses 'git-describe' to determine a previous tag. Our tags are created in release branches. - name: Set GORELEASER_PREVIOUS_TAG # Workaround, GoReleaser uses 'git-describe' to determine a previous tag. Our tags are created in release branches.
run: | run: |
set -xue set -xue
GORELEASER_PREVIOUS_TAG=$(go run hack/get-previous-release/get-previous-version-for-release-notes.go ${{ github.ref_name }}) || exit 1 echo "GORELEASER_PREVIOUS_TAG=$(go run hack/get-previous-release/get-previous-version-for-release-notes.go ${{ github.ref_name }})" >> $GITHUB_ENV
echo "GORELEASER_PREVIOUS_TAG=$GORELEASER_PREVIOUS_TAG" >> $GITHUB_ENV
- name: Set environment variables for ldflags - name: Set environment variables for ldflags
id: set_ldflag id: set_ldflag
@@ -134,7 +94,7 @@ jobs:
tool-cache: false tool-cache: false
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf # v6.1.0
id: run-goreleaser id: run-goreleaser
with: with:
version: latest version: latest
@@ -147,7 +107,7 @@ jobs:
- name: Generate subject for provenance - name: Generate subject for provenance
id: hash id: hash
env: env:
ARTIFACTS: '${{ steps.run-goreleaser.outputs.artifacts }}' ARTIFACTS: "${{ steps.run-goreleaser.outputs.artifacts }}"
run: | run: |
set -euo pipefail set -euo pipefail
@@ -166,10 +126,10 @@ jobs:
contents: write # Needed for release uploads contents: write # Needed for release uploads
if: github.repository == 'argoproj/argo-cd' if: github.repository == 'argoproj/argo-cd'
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator # Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0 uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
with: with:
base64-subjects: '${{ needs.goreleaser.outputs.hashes }}' base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
provenance-name: 'argocd-cli.intoto.jsonl' provenance-name: "argocd-cli.intoto.jsonl"
upload-assets: true upload-assets: true
generate-sbom: generate-sbom:
@@ -180,21 +140,20 @@ jobs:
permissions: permissions:
contents: write # Needed for release uploads contents: write # Needed for release uploads
outputs: outputs:
hashes: ${{ steps.sbom-hash.outputs.hashes }} hashes: ${{ steps.sbom-hash.outputs.hashes}}
if: github.repository == 'argoproj/argo-cd' if: github.repository == 'argoproj/argo-cd'
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
cache: false
- name: Generate SBOM (spdx) - name: Generate SBOM (spdx)
id: spdx-builder id: spdx-builder
@@ -205,7 +164,7 @@ jobs:
SIGS_BOM_VERSION: v0.2.1 SIGS_BOM_VERSION: v0.2.1
# comma delimited list of project relative folders to inspect for package # comma delimited list of project relative folders to inspect for package
# managers (gomod, yarn, npm). # managers (gomod, yarn, npm).
PROJECT_FOLDERS: '.,./ui' PROJECT_FOLDERS: ".,./ui"
# full qualified name of the docker image to be inspected # full qualified name of the docker image to be inspected
DOCKER_IMAGE: quay.io/argoproj/argocd:${{ github.ref_name }} DOCKER_IMAGE: quay.io/argoproj/argocd:${{ github.ref_name }}
run: | run: |
@@ -236,7 +195,7 @@ jobs:
echo "hashes=$(sha256sum /tmp/sbom.tar.gz | base64 -w0)" >> "$GITHUB_OUTPUT" echo "hashes=$(sha256sum /tmp/sbom.tar.gz | base64 -w0)" >> "$GITHUB_OUTPUT"
- name: Upload SBOM - name: Upload SBOM
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 uses: softprops/action-gh-release@7b4da11513bf3f43f9999e90eabced41ab8bb048 # v2.2.0
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
@@ -251,15 +210,14 @@ jobs:
contents: write # Needed for release uploads contents: write # Needed for release uploads
if: github.repository == 'argoproj/argo-cd' if: github.repository == 'argoproj/argo-cd'
# Must be referenced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator # Must be referenced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0 uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
with: with:
base64-subjects: '${{ needs.generate-sbom.outputs.hashes }}' base64-subjects: "${{ needs.generate-sbom.outputs.hashes }}"
provenance-name: 'argocd-sbom.intoto.jsonl' provenance-name: "argocd-sbom.intoto.jsonl"
upload-assets: true upload-assets: true
post-release: post-release:
needs: needs:
- setup-variables
- argocd-image - argocd-image
- goreleaser - goreleaser
- generate-sbom - generate-sbom
@@ -268,11 +226,9 @@ jobs:
pull-requests: write # Needed to create PR for VERSION update. pull-requests: write # Needed to create PR for VERSION update.
if: github.repository == 'argoproj/argo-cd' if: github.repository == 'argoproj/argo-cd'
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env:
TAG_STABLE: ${{ needs.setup-variables.outputs.is_latest_release }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@@ -283,6 +239,27 @@ jobs:
git config --global user.email 'ci@argoproj.com' git config --global user.email 'ci@argoproj.com'
git config --global user.name 'CI' git config --global user.name 'CI'
- name: Check if tag is the latest version and not a pre-release
run: |
set -xue
# Fetch all tag information
git fetch --prune --tags --force
LATEST_TAG=$(git -c 'versionsort.suffix=-rc' tag --list --sort=version:refname | tail -n1)
PRE_RELEASE=false
# Check if latest tag is a pre-release
if echo $LATEST_TAG | grep -E -- '-rc[0-9]+$';then
PRE_RELEASE=true
fi
# Ensure latest tag matches github.ref_name & not a pre-release
if [[ $LATEST_TAG == ${{ github.ref_name }} ]] && [[ $PRE_RELEASE != 'true' ]];then
echo "TAG_STABLE=true" >> $GITHUB_ENV
else
echo "TAG_STABLE=false" >> $GITHUB_ENV
fi
- name: Update stable tag to latest version - name: Update stable tag to latest version
run: | run: |
git tag -f stable ${{ github.ref_name }} git tag -f stable ${{ github.ref_name }}
@@ -316,10 +293,10 @@ jobs:
if: ${{ env.UPDATE_VERSION == 'true' }} if: ${{ env.UPDATE_VERSION == 'true' }}
- name: Create PR to update VERSION on master branch - name: Create PR to update VERSION on master branch
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with: with:
commit-message: Bump version in master commit-message: Bump version in master
title: 'chore: Bump version in master' title: "chore: Bump version in master"
body: All images built from master should indicate which version we are on track for. body: All images built from master should indicate which version we are on track for.
signoff: true signoff: true
branch: update-version branch: update-version

View File

@@ -1,39 +0,0 @@
name: Renovate
on:
schedule:
- cron: '0 * * * *'
workflow_dispatch: {}
permissions:
contents: read
jobs:
renovate:
runs-on: ubuntu-latest
if: github.repository == 'argoproj/argo-cd'
steps:
- name: Get token
id: get_token
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
with:
app-id: ${{ vars.RENOVATE_APP_ID }}
private-key: ${{ secrets.RENOVATE_APP_PRIVATE_KEY }}
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 5.0.0
# Some codegen commands require Go to be setup
- name: Setup Golang
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
# renovate: datasource=golang-version packageName=golang
go-version: 1.25.3
- name: Self-hosted Renovate
uses: renovatebot/github-action@ea850436a5fe75c0925d583c7a02c60a5865461d #43.0.20
with:
configurationFile: .github/configs/renovate-config.js
token: '${{ steps.get_token.outputs.token }}'
env:
LOG_LEVEL: 'debug'
RENOVATE_REPOSITORIES: '${{ github.repository }}'

View File

@@ -30,12 +30,12 @@ jobs:
steps: steps:
- name: "Checkout code" - name: "Checkout code"
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with: with:
persist-credentials: false persist-credentials: false
- name: "Run analysis" - name: "Run analysis"
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
with: with:
results_file: results.sarif results_file: results.sarif
results_format: sarif results_format: sarif
@@ -54,7 +54,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab. # format to the repository Actions tab.
- name: "Upload artifact" - name: "Upload artifact"
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with: with:
name: SARIF file name: SARIF file
path: results.sarif path: results.sarif

View File

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

5
.gitignore vendored
View File

@@ -8,7 +8,6 @@ ui/dist/app/*
!ui/dist/app/gitkeep !ui/dist/app/gitkeep
site/ site/
*.iml *.iml
.tilt-bin/
# delve debug binaries # delve debug binaries
cmd/**/debug cmd/**/debug
debug.test debug.test
@@ -20,7 +19,6 @@ node_modules/
.kube/ .kube/
./test/cmp/*.sock ./test/cmp/*.sock
.envrc.remote .envrc.remote
.mirrord/
.*.swp .*.swp
rerunreport.txt rerunreport.txt
@@ -29,6 +27,3 @@ cmd/argocd/argocd
cmd/argocd-application-controller/argocd-application-controller cmd/argocd-application-controller/argocd-application-controller
cmd/argocd-repo-server/argocd-repo-server cmd/argocd-repo-server/argocd-repo-server
cmd/argocd-server/argocd-server cmd/argocd-server/argocd-server
# ignore generated `.argocd-helm-dep-up` marker file; this should not be committed to git
reposerver/repository/testdata/**/.argocd-helm-dep-up

21
.gitpod.Dockerfile vendored Normal file
View File

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

6
.gitpod.yml Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,9 +12,3 @@
/.github/** @argoproj/argocd-approvers @argoproj/argocd-approvers-ci /.github/** @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
/.goreleaser.yaml @argoproj/argocd-approvers @argoproj/argocd-approvers-ci /.goreleaser.yaml @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
/sonar-project.properties @argoproj/argocd-approvers @argoproj/argocd-approvers-ci /sonar-project.properties @argoproj/argocd-approvers @argoproj/argocd-approvers-ci
# CLI
/cmd/argocd/** @argoproj/argocd-approvers @argoproj/argocd-approvers-cli
/cmd/main.go @argoproj/argocd-approvers @argoproj/argocd-approvers-cli
# Also include @argoproj/argocd-approvers-docs to avoid requiring CLI approvers for docs-only PRs.
/docs/operator-manual/ @argoproj/argocd-approvers @argoproj/argocd-approvers-docs @argoproj/argocd-approvers-cli

View File

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

View File

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

View File

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

View File

@@ -1,18 +1,9 @@
PACKAGE=github.com/argoproj/argo-cd/v3/common PACKAGE=github.com/argoproj/argo-cd/v2/common
CURRENT_DIR=$(shell pwd) CURRENT_DIR=$(shell pwd)
DIST_DIR=${CURRENT_DIR}/dist DIST_DIR=${CURRENT_DIR}/dist
CLI_NAME=argocd CLI_NAME=argocd
BIN_NAME=argocd BIN_NAME=argocd
CGO_FLAG=0
UNAME_S:=$(shell uname)
IS_DARWIN:=$(if $(filter Darwin, $(UNAME_S)),true,false)
# When using OSX/Darwin, you might need to enable CGO for local builds
DEFAULT_CGO_FLAG:=0
ifeq ($(IS_DARWIN),true)
DEFAULT_CGO_FLAG:=1
endif
CGO_FLAG?=${DEFAULT_CGO_FLAG}
GEN_RESOURCES_CLI_NAME=argocd-resources-gen GEN_RESOURCES_CLI_NAME=argocd-resources-gen
@@ -43,17 +34,6 @@ endif
DOCKER_SRCDIR?=$(GOPATH)/src DOCKER_SRCDIR?=$(GOPATH)/src
DOCKER_WORKDIR?=/go/src/github.com/argoproj/argo-cd DOCKER_WORKDIR?=/go/src/github.com/argoproj/argo-cd
# Allows you to control which Docker network the test-util containers attach to.
# This is particularly useful if you are running Kubernetes in Docker (e.g., k3d)
# and want the test containers to reach the Kubernetes API via an already-existing Docker network.
DOCKER_NETWORK ?= default
ifneq ($(DOCKER_NETWORK),default)
DOCKER_NETWORK_ARG := --network $(DOCKER_NETWORK)
else
DOCKER_NETWORK_ARG :=
endif
ARGOCD_PROCFILE?=Procfile ARGOCD_PROCFILE?=Procfile
# pointing to python 3.7 to match https://github.com/argoproj/argo-cd/blob/master/.readthedocs.yml # pointing to python 3.7 to match https://github.com/argoproj/argo-cd/blob/master/.readthedocs.yml
@@ -124,11 +104,11 @@ define run-in-test-server
-v ${GOPATH}/pkg/mod:/go/pkg/mod${VOLUME_MOUNT} \ -v ${GOPATH}/pkg/mod:/go/pkg/mod${VOLUME_MOUNT} \
-v ${GOCACHE}:/tmp/go-build-cache${VOLUME_MOUNT} \ -v ${GOCACHE}:/tmp/go-build-cache${VOLUME_MOUNT} \
-v ${HOME}/.kube:/home/user/.kube${VOLUME_MOUNT} \ -v ${HOME}/.kube:/home/user/.kube${VOLUME_MOUNT} \
-v /tmp:/tmp${VOLUME_MOUNT} \
-w ${DOCKER_WORKDIR} \ -w ${DOCKER_WORKDIR} \
-p ${ARGOCD_E2E_APISERVER_PORT}:8080 \ -p ${ARGOCD_E2E_APISERVER_PORT}:8080 \
-p 4000:4000 \ -p 4000:4000 \
-p 5000:5000 \ -p 5000:5000 \
$(DOCKER_NETWORK_ARG)\
$(PODMAN_ARGS) \ $(PODMAN_ARGS) \
$(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG) \ $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG) \
bash -c "$(1)" bash -c "$(1)"
@@ -149,8 +129,8 @@ define run-in-test-client
-v ${GOPATH}/pkg/mod:/go/pkg/mod${VOLUME_MOUNT} \ -v ${GOPATH}/pkg/mod:/go/pkg/mod${VOLUME_MOUNT} \
-v ${GOCACHE}:/tmp/go-build-cache${VOLUME_MOUNT} \ -v ${GOCACHE}:/tmp/go-build-cache${VOLUME_MOUNT} \
-v ${HOME}/.kube:/home/user/.kube${VOLUME_MOUNT} \ -v ${HOME}/.kube:/home/user/.kube${VOLUME_MOUNT} \
-v /tmp:/tmp${VOLUME_MOUNT} \
-w ${DOCKER_WORKDIR} \ -w ${DOCKER_WORKDIR} \
$(DOCKER_NETWORK_ARG)\
$(PODMAN_ARGS) \ $(PODMAN_ARGS) \
$(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG) \ $(TEST_TOOLS_PREFIX)$(TEST_TOOLS_IMAGE):$(TEST_TOOLS_TAG) \
bash -c "$(1)" bash -c "$(1)"
@@ -167,11 +147,7 @@ PATH:=$(PATH):$(PWD)/hack
DOCKER_PUSH?=false DOCKER_PUSH?=false
IMAGE_NAMESPACE?= IMAGE_NAMESPACE?=
# perform static compilation # perform static compilation
DEFAULT_STATIC_BUILD:=true STATIC_BUILD?=true
ifeq ($(IS_DARWIN),true)
DEFAULT_STATIC_BUILD:=false
endif
STATIC_BUILD?=${DEFAULT_STATIC_BUILD}
# build development images # build development images
DEV_IMAGE?=false DEV_IMAGE?=false
ARGOCD_GPG_ENABLED?=true ARGOCD_GPG_ENABLED?=true
@@ -261,12 +237,8 @@ clidocsgen:
actionsdocsgen: actionsdocsgen:
hack/generate-actions-list.sh hack/generate-actions-list.sh
.PHONY: resourceiconsgen
resourceiconsgen:
hack/generate-icons-typescript.sh
.PHONY: codegen-local .PHONY: codegen-local
codegen-local: mod-vendor-local mockgen gogen protogen clientgen openapigen clidocsgen actionsdocsgen resourceiconsgen manifests-local notification-docs notification-catalog codegen-local: mod-vendor-local mockgen gogen protogen clientgen openapigen clidocsgen actionsdocsgen manifests-local notification-docs notification-catalog
rm -rf vendor/ rm -rf vendor/
.PHONY: codegen-local-fast .PHONY: codegen-local-fast
@@ -383,6 +355,11 @@ mod-vendor: test-tools-image
mod-vendor-local: mod-download-local mod-vendor-local: mod-download-local
go mod vendor go mod vendor
# Deprecated - replace by install-tools-local
.PHONY: install-lint-tools
install-lint-tools:
./hack/install.sh lint-tools
# Run linter on the code # Run linter on the code
.PHONY: lint .PHONY: lint
lint: test-tools-image lint: test-tools-image
@@ -458,7 +435,7 @@ test-e2e:
test-e2e-local: cli-local test-e2e-local: cli-local
# NO_PROXY ensures all tests don't go out through a proxy if one is configured on the test system # NO_PROXY ensures all tests don't go out through a proxy if one is configured on the test system
export GO111MODULE=off export GO111MODULE=off
DIST_DIR=${DIST_DIR} RERUN_FAILS=5 PACKAGES="./test/e2e" ARGOCD_E2E_RECORD=${ARGOCD_E2E_RECORD} ARGOCD_CONFIG_DIR=$(HOME)/.config/argocd-e2e ARGOCD_GPG_ENABLED=true NO_PROXY=* ./hack/test.sh -timeout $(ARGOCD_E2E_TEST_TIMEOUT) -v -args -test.gocoverdir="$(PWD)/test-results" DIST_DIR=${DIST_DIR} RERUN_FAILS=5 PACKAGES="./test/e2e" ARGOCD_E2E_RECORD=${ARGOCD_E2E_RECORD} ARGOCD_GPG_ENABLED=true NO_PROXY=* ./hack/test.sh -timeout $(ARGOCD_E2E_TEST_TIMEOUT) -v -args -test.gocoverdir="$(PWD)/test-results"
# Spawns a shell in the test server container for debugging purposes # Spawns a shell in the test server container for debugging purposes
debug-test-server: test-tools-image debug-test-server: test-tools-image
@@ -513,8 +490,6 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
ARGOCD_APPLICATIONSET_CONTROLLER_TOKENREF_STRICT_MODE=true \ ARGOCD_APPLICATIONSET_CONTROLLER_TOKENREF_STRICT_MODE=true \
ARGOCD_APPLICATIONSET_CONTROLLER_ALLOWED_SCM_PROVIDERS=http://127.0.0.1:8341,http://127.0.0.1:8342,http://127.0.0.1:8343,http://127.0.0.1:8344 \ ARGOCD_APPLICATIONSET_CONTROLLER_ALLOWED_SCM_PROVIDERS=http://127.0.0.1:8341,http://127.0.0.1:8342,http://127.0.0.1:8343,http://127.0.0.1:8344 \
ARGOCD_E2E_TEST=true \ ARGOCD_E2E_TEST=true \
ARGOCD_HYDRATOR_ENABLED=true \
ARGOCD_CLUSTER_CACHE_EVENTS_PROCESSING_INTERVAL=1ms \
goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START} goreman -f $(ARGOCD_PROCFILE) start ${ARGOCD_START}
ls -lrt /tmp/coverage ls -lrt /tmp/coverage
@@ -613,19 +588,16 @@ install-test-tools-local:
./hack/install.sh kustomize ./hack/install.sh kustomize
./hack/install.sh helm ./hack/install.sh helm
./hack/install.sh gotestsum ./hack/install.sh gotestsum
./hack/install.sh oras
# Installs all tools required for running codegen (Linux packages) # Installs all tools required for running codegen (Linux packages)
.PHONY: install-codegen-tools-local .PHONY: install-codegen-tools-local
install-codegen-tools-local: install-codegen-tools-local:
./hack/install.sh codegen-tools ./hack/install.sh codegen-tools
./hack/install.sh codegen-go-tools
# Installs all tools required for running codegen (Go packages) # Installs all tools required for running codegen (Go packages)
.PHONY: install-go-tools-local .PHONY: install-go-tools-local
install-go-tools-local: install-go-tools-local:
./hack/install.sh codegen-go-tools ./hack/install.sh codegen-go-tools
./hack/install.sh lint-tools
.PHONY: dep-ui .PHONY: dep-ui
dep-ui: test-tools-image dep-ui: test-tools-image
@@ -706,6 +678,7 @@ help:
@echo 'debug:' @echo 'debug:'
@echo ' list -- list all make targets' @echo ' list -- list all make targets'
@echo ' install-tools-local -- install all the tools below' @echo ' install-tools-local -- install all the tools below'
@echo ' install-lint-tools(-local)'
@echo @echo
@echo 'codegen:' @echo 'codegen:'
@echo ' codegen(-local) -- if using -local, run the following targets first' @echo ' codegen(-local) -- if using -local, run the following targets first'

View File

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

View File

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

291
Tiltfile
View File

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

View File

@@ -5,10 +5,8 @@ PR with your organization name if you are using Argo CD.
Currently, the following organizations are **officially** using Argo CD: Currently, the following organizations are **officially** using Argo CD:
1. [100ms](https://www.100ms.ai/)
1. [127Labs](https://127labs.com/) 1. [127Labs](https://127labs.com/)
1. [3Rein](https://www.3rein.com/) 1. [3Rein](https://www.3rein.com/)
1. [42 School](https://42.fr/)
1. [4data](https://4data.ch/) 1. [4data](https://4data.ch/)
1. [7shifts](https://www.7shifts.com/) 1. [7shifts](https://www.7shifts.com/)
1. [Adevinta](https://www.adevinta.com/) 1. [Adevinta](https://www.adevinta.com/)
@@ -32,8 +30,7 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Ant Group](https://www.antgroup.com/) 1. [Ant Group](https://www.antgroup.com/)
1. [AppDirect](https://www.appdirect.com) 1. [AppDirect](https://www.appdirect.com)
1. [Arctiq Inc.](https://www.arctiq.ca) 1. [Arctiq Inc.](https://www.arctiq.ca)
1. [Artemis Health by Nomi Health](https://www.artemishealth.com/) 2. [Arturia](https://www.arturia.com)
1. [Arturia](https://www.arturia.com)
1. [ARZ Allgemeines Rechenzentrum GmbH](https://www.arz.at/) 1. [ARZ Allgemeines Rechenzentrum GmbH](https://www.arz.at/)
1. [Augury](https://www.augury.com/) 1. [Augury](https://www.augury.com/)
1. [Autodesk](https://www.autodesk.com) 1. [Autodesk](https://www.autodesk.com)
@@ -42,14 +39,12 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Back Market](https://www.backmarket.com) 1. [Back Market](https://www.backmarket.com)
1. [Bajaj Finserv Health Ltd.](https://www.bajajfinservhealth.in) 1. [Bajaj Finserv Health Ltd.](https://www.bajajfinservhealth.in)
1. [Baloise](https://www.baloise.com) 1. [Baloise](https://www.baloise.com)
1. [Batumbu](https://batumbu.id)
1. [BCDevExchange DevOps Platform](https://bcdevexchange.org/DevOpsPlatform) 1. [BCDevExchange DevOps Platform](https://bcdevexchange.org/DevOpsPlatform)
1. [Beat](https://thebeat.co/en/) 1. [Beat](https://thebeat.co/en/)
1. [Beez Innovation Labs](https://www.beezlabs.com/) 1. [Beez Innovation Labs](https://www.beezlabs.com/)
1. [Bedag Informatik AG](https://www.bedag.ch/) 1. [Bedag Informatik AG](https://www.bedag.ch/)
1. [Beleza Na Web](https://www.belezanaweb.com.br/) 1. [Beleza Na Web](https://www.belezanaweb.com.br/)
1. [Believable Bots](https://believablebots.io) 1. [Believable Bots](https://believablebots.io)
1. [Bayer AG](https://bayer.com)
1. [BigPanda](https://bigpanda.io) 1. [BigPanda](https://bigpanda.io)
1. [BioBox Analytics](https://biobox.io) 1. [BioBox Analytics](https://biobox.io)
1. [BMW Group](https://www.bmwgroup.com/) 1. [BMW Group](https://www.bmwgroup.com/)
@@ -63,7 +58,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Camptocamp](https://camptocamp.com) 1. [Camptocamp](https://camptocamp.com)
1. [Candis](https://www.candis.io) 1. [Candis](https://www.candis.io)
1. [Capital One](https://www.capitalone.com) 1. [Capital One](https://www.capitalone.com)
1. [Capptain LTD](https://capptain.co/)
1. [CARFAX Europe](https://www.carfax.eu) 1. [CARFAX Europe](https://www.carfax.eu)
1. [CARFAX](https://www.carfax.com) 1. [CARFAX](https://www.carfax.com)
1. [Carrefour Group](https://www.carrefour.com) 1. [Carrefour Group](https://www.carrefour.com)
@@ -73,9 +67,7 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Chainnodes](https://chainnodes.org) 1. [Chainnodes](https://chainnodes.org)
1. [Chargetrip](https://chargetrip.com) 1. [Chargetrip](https://chargetrip.com)
1. [Chime](https://www.chime.com) 1. [Chime](https://www.chime.com)
1. [Chronicle Labs](https://chroniclelabs.org)
1. [Cisco ET&I](https://eti.cisco.com/) 1. [Cisco ET&I](https://eti.cisco.com/)
1. [Close](https://www.close.com/)
1. [Cloud Posse](https://www.cloudposse.com/) 1. [Cloud Posse](https://www.cloudposse.com/)
1. [Cloud Scale](https://cloudscaleinc.com/) 1. [Cloud Scale](https://cloudscaleinc.com/)
1. [CloudScript](https://www.cloudscript.com.br/) 1. [CloudScript](https://www.cloudscript.com.br/)
@@ -100,15 +92,11 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Daydream](https://daydream.ing) 1. [Daydream](https://daydream.ing)
1. [Deloitte](https://www.deloitte.com/) 1. [Deloitte](https://www.deloitte.com/)
1. [Deutsche Telekom AG](https://telekom.com) 1. [Deutsche Telekom AG](https://telekom.com)
1. [Deutsche Bank AG](https://www.deutsche-bank.de/)
1. [Devopsi - Poland Software/DevOps Consulting](https://devopsi.pl/) 1. [Devopsi - Poland Software/DevOps Consulting](https://devopsi.pl/)
1. [Devtron Labs](https://github.com/devtron-labs/devtron) 1. [Devtron Labs](https://github.com/devtron-labs/devtron)
1. [DigitalEd](https://www.digitaled.com)
1. [DigitalOcean](https://www.digitalocean.com) 1. [DigitalOcean](https://www.digitalocean.com)
1. [Divar](https://divar.ir)
1. [Divistant](https://divistant.com) 1. [Divistant](https://divistant.com)
1. [Dott](https://ridedott.com) 1. [Dott](https://ridedott.com)
1. [Doubble](https://www.doubble.app)
1. [Doximity](https://www.doximity.com/) 1. [Doximity](https://www.doximity.com/)
1. [EDF Renewables](https://www.edf-re.com/) 1. [EDF Renewables](https://www.edf-re.com/)
1. [edX](https://edx.org) 1. [edX](https://edx.org)
@@ -120,7 +108,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Energisme](https://energisme.com/) 1. [Energisme](https://energisme.com/)
1. [enigmo](https://enigmo.co.jp/) 1. [enigmo](https://enigmo.co.jp/)
1. [Envoy](https://envoy.com/) 1. [Envoy](https://envoy.com/)
1. [eSave](https://esave.es/)
1. [Factorial](https://factorialhr.com/) 1. [Factorial](https://factorialhr.com/)
1. [Farfetch](https://www.farfetch.com) 1. [Farfetch](https://www.farfetch.com)
1. [Faro](https://www.faro.com/) 1. [Faro](https://www.faro.com/)
@@ -159,17 +146,14 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Hazelcast](https://hazelcast.com/) 1. [Hazelcast](https://hazelcast.com/)
1. [Healy](https://www.healyworld.net) 1. [Healy](https://www.healyworld.net)
1. [Helio](https://helio.exchange) 1. [Helio](https://helio.exchange)
1. [hetao101](https://www.hetao101.com/)
1. [Hetki](https://hetki.ai) 1. [Hetki](https://hetki.ai)
1. [hipages](https://hipages.com.au/) 1. [hipages](https://hipages.com.au/)
1. [Hiya](https://hiya.com) 1. [Hiya](https://hiya.com)
1. [Honestbank](https://honestbank.com) 1. [Honestbank](https://honestbank.com)
1. [Hostinger](https://www.hostinger.com) 1. [Hostinger](https://www.hostinger.com)
1. [Hotjar](https://www.hotjar.com)
1. [IABAI](https://www.iab.ai) 1. [IABAI](https://www.iab.ai)
1. [IBM](https://www.ibm.com/) 1. [IBM](https://www.ibm.com/)
1. [Ibotta](https://home.ibotta.com) 1. [Ibotta](https://home.ibotta.com)
1. [Icelandair](https://www.icelandair.com)
1. [IFS](https://www.ifs.com) 1. [IFS](https://www.ifs.com)
1. [IITS-Consulting](https://iits-consulting.de) 1. [IITS-Consulting](https://iits-consulting.de)
1. [IllumiDesk](https://www.illumidesk.com) 1. [IllumiDesk](https://www.illumidesk.com)
@@ -179,7 +163,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Info Support](https://www.infosupport.com/) 1. [Info Support](https://www.infosupport.com/)
1. [InsideBoard](https://www.insideboard.com) 1. [InsideBoard](https://www.insideboard.com)
1. [Instruqt](https://www.instruqt.com) 1. [Instruqt](https://www.instruqt.com)
1. [Intel](https://www.intel.com)
1. [Intuit](https://www.intuit.com/) 1. [Intuit](https://www.intuit.com/)
1. [Jellysmack](https://www.jellysmack.com) 1. [Jellysmack](https://www.jellysmack.com)
1. [Joblift](https://joblift.com/) 1. [Joblift](https://joblift.com/)
@@ -208,10 +191,10 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Lian Chu Securities](https://lczq.com) 1. [Lian Chu Securities](https://lczq.com)
1. [Liatrio](https://www.liatrio.com) 1. [Liatrio](https://www.liatrio.com)
1. [Lightricks](https://www.lightricks.com/) 1. [Lightricks](https://www.lightricks.com/)
1. [LINE](https://linecorp.com/en/)
1. [Loom](https://www.loom.com/) 1. [Loom](https://www.loom.com/)
1. [Lucid Motors](https://www.lucidmotors.com/) 1. [Lucid Motors](https://www.lucidmotors.com/)
1. [Lytt](https://www.lytt.co/) 1. [Lytt](https://www.lytt.co/)
1. [LY Corporation](https://www.lycorp.co.jp/en/)
1. [Magic Leap](https://www.magicleap.com/) 1. [Magic Leap](https://www.magicleap.com/)
1. [Majid Al Futtaim](https://www.majidalfuttaim.com/) 1. [Majid Al Futtaim](https://www.majidalfuttaim.com/)
1. [Major League Baseball](https://mlb.com) 1. [Major League Baseball](https://mlb.com)
@@ -274,7 +257,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Patreon](https://www.patreon.com/) 1. [Patreon](https://www.patreon.com/)
1. [PayIt](https://payitgov.com/) 1. [PayIt](https://payitgov.com/)
1. [PayPay](https://paypay.ne.jp/) 1. [PayPay](https://paypay.ne.jp/)
1. [Paystack](https://paystack.com/)
1. [Peloton Interactive](https://www.onepeloton.com/) 1. [Peloton Interactive](https://www.onepeloton.com/)
1. [Percona](https://percona.com/) 1. [Percona](https://percona.com/)
1. [PGS](https://www.pgs.com) 1. [PGS](https://www.pgs.com)
@@ -286,7 +268,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [PITS Globale Datenrettungsdienste](https://www.pitsdatenrettung.de/) 1. [PITS Globale Datenrettungsdienste](https://www.pitsdatenrettung.de/)
1. [Platform9 Systems](https://platform9.com/) 1. [Platform9 Systems](https://platform9.com/)
1. [Polarpoint.io](https://polarpoint.io) 1. [Polarpoint.io](https://polarpoint.io)
1. [Pollinate](https://www.pollinate.global)
1. [PostFinance](https://github.com/postfinance) 1. [PostFinance](https://github.com/postfinance)
1. [Preferred Networks](https://preferred.jp/en/) 1. [Preferred Networks](https://preferred.jp/en/)
1. [Previder BV](https://previder.nl) 1. [Previder BV](https://previder.nl)
@@ -310,7 +291,7 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Relex Solutions](https://www.relexsolutions.com/) 1. [Relex Solutions](https://www.relexsolutions.com/)
1. [RightRev](https://rightrev.com/) 1. [RightRev](https://rightrev.com/)
1. [Rijkswaterstaat](https://www.rijkswaterstaat.nl/en) 1. [Rijkswaterstaat](https://www.rijkswaterstaat.nl/en)
1. Rise 1. [Rise](https://www.risecard.eu/)
1. [Riskified](https://www.riskified.com/) 1. [Riskified](https://www.riskified.com/)
1. [Robotinfra](https://www.robotinfra.com) 1. [Robotinfra](https://www.robotinfra.com)
1. [Rocket.Chat](https://rocket.chat) 1. [Rocket.Chat](https://rocket.chat)
@@ -321,20 +302,16 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Saloodo! GmbH](https://www.saloodo.com) 1. [Saloodo! GmbH](https://www.saloodo.com)
1. [Sap Labs](http://sap.com) 1. [Sap Labs](http://sap.com)
1. [Sauce Labs](https://saucelabs.com/) 1. [Sauce Labs](https://saucelabs.com/)
1. [Schneider Electric](https://www.se.com)
1. [Schwarz IT](https://jobs.schwarz/it-mission) 1. [Schwarz IT](https://jobs.schwarz/it-mission)
1. [SCRM Lidl International Hub](https://scrm.lidl) 1. [SCRM Lidl International Hub](https://scrm.lidl)
1. [SEEK](https://seek.com.au) 1. [SEEK](https://seek.com.au)
1. [SEKAI](https://www.sekai.io/) 1. [SEKAI](https://www.sekai.io/)
1. [Semgrep](https://semgrep.com) 1. [Semgrep](https://semgrep.com)
1. [Seznam.cz](https://o-seznam.cz/)
1. [Shield](https://shield.com) 1. [Shield](https://shield.com)
1. [Shipfox](https://www.shipfox.io)
1. [Shock Media](https://www.shockmedia.nl)
1. [SI Analytics](https://si-analytics.ai) 1. [SI Analytics](https://si-analytics.ai)
1. [Sidewalk Entertainment](https://sidewalkplay.com/) 1. [Sidewalk Entertainment](https://sidewalkplay.com/)
1. [Skit](https://skit.ai/) 1. [Skit](https://skit.ai/)
1. [Skribble](https://skribble.com) 1. [Skribble](https://skribble.com)
1. [Skyscanner](https://www.skyscanner.net/) 1. [Skyscanner](https://www.skyscanner.net/)
1. [Smart Pension](https://www.smartpension.co.uk/) 1. [Smart Pension](https://www.smartpension.co.uk/)
1. [Smilee.io](https://smilee.io) 1. [Smilee.io](https://smilee.io)
@@ -343,7 +320,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Snapp](https://snapp.ir/) 1. [Snapp](https://snapp.ir/)
1. [Snyk](https://snyk.io/) 1. [Snyk](https://snyk.io/)
1. [Softway Medical](https://www.softwaymedical.fr/) 1. [Softway Medical](https://www.softwaymedical.fr/)
1. [Sophotech](https://sopho.tech)
1. [South China Morning Post (SCMP)](https://www.scmp.com/) 1. [South China Morning Post (SCMP)](https://www.scmp.com/)
1. [Speee](https://speee.jp/) 1. [Speee](https://speee.jp/)
1. [Spendesk](https://spendesk.com/) 1. [Spendesk](https://spendesk.com/)
@@ -356,13 +332,10 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Sumo Logic](https://sumologic.com/) 1. [Sumo Logic](https://sumologic.com/)
1. [Sutpc](http://www.sutpc.com/) 1. [Sutpc](http://www.sutpc.com/)
1. [Swiss Post](https://github.com/swisspost) 1. [Swiss Post](https://github.com/swisspost)
1. [Swissblock Technologies](https://swissblock.net/)
1. [Swisscom](https://www.swisscom.ch) 1. [Swisscom](https://www.swisscom.ch)
1. [Swissquote](https://github.com/swissquote) 1. [Swissquote](https://github.com/swissquote)
1. [Syncier](https://syncier.com/) 1. [Syncier](https://syncier.com/)
1. [Synergy](https://synergy.net.au)
1. [Syself](https://syself.com) 1. [Syself](https://syself.com)
1. [T-ROC Global](https://trocglobal.com/)
1. [TableCheck](https://tablecheck.com/) 1. [TableCheck](https://tablecheck.com/)
1. [Tailor Brands](https://www.tailorbrands.com) 1. [Tailor Brands](https://www.tailorbrands.com)
1. [Tamkeen Technologies](https://tamkeentech.sa/) 1. [Tamkeen Technologies](https://tamkeentech.sa/)
@@ -400,11 +373,9 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Vinted](https://vinted.com/) 1. [Vinted](https://vinted.com/)
1. [Virtuo](https://www.govirtuo.com/) 1. [Virtuo](https://www.govirtuo.com/)
1. [VISITS Technologies](https://visits.world/en) 1. [VISITS Technologies](https://visits.world/en)
1. [Viya](https://viya.me)
1. [Volvo Cars](https://www.volvocars.com/) 1. [Volvo Cars](https://www.volvocars.com/)
1. [Voyager Digital](https://www.investvoyager.com/) 1. [Voyager Digital](https://www.investvoyager.com/)
1. [VSHN - The DevOps Company](https://vshn.ch/) 1. [VSHN - The DevOps Company](https://vshn.ch/)
1. [Wakacje.pl](https://www.wakacje.pl/)
1. [Walkbase](https://www.walkbase.com/) 1. [Walkbase](https://www.walkbase.com/)
1. [Webstores](https://www.webstores.nl) 1. [Webstores](https://www.webstores.nl)
1. [Wehkamp](https://www.wehkamp.nl/) 1. [Wehkamp](https://www.wehkamp.nl/)
@@ -416,12 +387,10 @@ Currently, the following organizations are **officially** using Argo CD:
1. [WooliesX](https://wooliesx.com.au/) 1. [WooliesX](https://wooliesx.com.au/)
1. [Woolworths Group](https://www.woolworthsgroup.com.au/) 1. [Woolworths Group](https://www.woolworthsgroup.com.au/)
1. [WSpot](https://www.wspot.com.br/) 1. [WSpot](https://www.wspot.com.br/)
1. [X3M ads](https://x3mads.com)
1. [Yieldlab](https://www.yieldlab.de/) 1. [Yieldlab](https://www.yieldlab.de/)
1. [Youverify](https://youverify.co/) 1. [Youverify](https://youverify.co/)
1. [Yubo](https://www.yubo.live/) 1. [Yubo](https://www.yubo.live/)
1. [Yuno](https://y.uno/)
1. [ZDF](https://www.zdf.de/) 1. [ZDF](https://www.zdf.de/)
1. [Zimpler](https://www.zimpler.com/) 1. [Zimpler](https://www.zimpler.com/)
1. [ZipRecruiter](https://www.ziprecruiter.com/) 1. [ZipRecuiter](https://www.ziprecruiter.com/)
1. [ZOZO](https://corp.zozo.com/) 1. [ZOZO](https://corp.zozo.com/)

View File

@@ -1 +1 @@
3.3.0 2.14.0

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
package template package template
import ( import (
"errors" "fmt"
"maps" "maps"
"testing" "testing"
@@ -13,12 +13,12 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"github.com/argoproj/argo-cd/v3/applicationset/generators" "github.com/argoproj/argo-cd/v2/applicationset/generators"
genmock "github.com/argoproj/argo-cd/v3/applicationset/generators/mocks" genmock "github.com/argoproj/argo-cd/v2/applicationset/generators/mocks"
"github.com/argoproj/argo-cd/v3/applicationset/utils" "github.com/argoproj/argo-cd/v2/applicationset/utils"
rendmock "github.com/argoproj/argo-cd/v3/applicationset/utils/mocks" rendmock "github.com/argoproj/argo-cd/v2/applicationset/utils/mocks"
"github.com/argoproj/argo-cd/v3/pkg/apis/application" "github.com/argoproj/argo-cd/v2/pkg/apis/application"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
func TestGenerateApplications(t *testing.T) { func TestGenerateApplications(t *testing.T) {
@@ -31,7 +31,7 @@ func TestGenerateApplications(t *testing.T) {
for _, c := range []struct { for _, c := range []struct {
name string name string
params []map[string]any params []map[string]interface{}
template v1alpha1.ApplicationSetTemplate template v1alpha1.ApplicationSetTemplate
generateParamsError error generateParamsError error
rendererError error rendererError error
@@ -40,7 +40,7 @@ func TestGenerateApplications(t *testing.T) {
}{ }{
{ {
name: "Generate two applications", name: "Generate two applications",
params: []map[string]any{{"name": "app1"}, {"name": "app2"}}, params: []map[string]interface{}{{"name": "app1"}, {"name": "app2"}},
template: v1alpha1.ApplicationSetTemplate{ template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "name", Name: "name",
@@ -53,13 +53,13 @@ func TestGenerateApplications(t *testing.T) {
}, },
{ {
name: "Handles error from the generator", name: "Handles error from the generator",
generateParamsError: errors.New("error"), generateParamsError: fmt.Errorf("error"),
expectErr: true, expectErr: true,
expectedReason: v1alpha1.ApplicationSetReasonApplicationParamsGenerationError, expectedReason: v1alpha1.ApplicationSetReasonApplicationParamsGenerationError,
}, },
{ {
name: "Handles error from the render", name: "Handles error from the render",
params: []map[string]any{{"name": "app1"}, {"name": "app2"}}, params: []map[string]interface{}{{"name": "app1"}, {"name": "app2"}},
template: v1alpha1.ApplicationSetTemplate{ template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "name", Name: "name",
@@ -68,7 +68,7 @@ func TestGenerateApplications(t *testing.T) {
}, },
Spec: v1alpha1.ApplicationSpec{}, Spec: v1alpha1.ApplicationSpec{},
}, },
rendererError: errors.New("error"), rendererError: fmt.Errorf("error"),
expectErr: true, expectErr: true,
expectedReason: v1alpha1.ApplicationSetReasonRenderTemplateParamsError, expectedReason: v1alpha1.ApplicationSetReasonRenderTemplateParamsError,
}, },
@@ -86,28 +86,28 @@ func TestGenerateApplications(t *testing.T) {
} }
t.Run(cc.name, func(t *testing.T) { t.Run(cc.name, func(t *testing.T) {
generatorMock := &genmock.Generator{} generatorMock := genmock.Generator{}
generator := v1alpha1.ApplicationSetGenerator{ generator := v1alpha1.ApplicationSetGenerator{
List: &v1alpha1.ListGenerator{}, List: &v1alpha1.ListGenerator{},
} }
generatorMock.EXPECT().GenerateParams(&generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything). generatorMock.On("GenerateParams", &generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).
Return(cc.params, cc.generateParamsError) Return(cc.params, cc.generateParamsError)
generatorMock.EXPECT().GetTemplate(&generator). generatorMock.On("GetTemplate", &generator).
Return(&v1alpha1.ApplicationSetTemplate{}) Return(&v1alpha1.ApplicationSetTemplate{})
rendererMock := &rendmock.Renderer{} rendererMock := rendmock.Renderer{}
var expectedApps []v1alpha1.Application var expectedApps []v1alpha1.Application
if cc.generateParamsError == nil { if cc.generateParamsError == nil {
for _, p := range cc.params { for _, p := range cc.params {
if cc.rendererError != nil { if cc.rendererError != nil {
rendererMock.EXPECT().RenderTemplateParams(GetTempApplication(cc.template), mock.AnythingOfType("*v1alpha1.ApplicationSetSyncPolicy"), p, false, []string(nil)). rendererMock.On("RenderTemplateParams", GetTempApplication(cc.template), mock.AnythingOfType("*v1alpha1.ApplicationSetSyncPolicy"), p, false, []string(nil)).
Return(nil, cc.rendererError) Return(nil, cc.rendererError)
} else { } else {
rendererMock.EXPECT().RenderTemplateParams(GetTempApplication(cc.template), mock.AnythingOfType("*v1alpha1.ApplicationSetSyncPolicy"), p, false, []string(nil)). rendererMock.On("RenderTemplateParams", GetTempApplication(cc.template), mock.AnythingOfType("*v1alpha1.ApplicationSetSyncPolicy"), p, false, []string(nil)).
Return(&app, nil) Return(&app, nil)
expectedApps = append(expectedApps, app) expectedApps = append(expectedApps, app)
} }
@@ -115,9 +115,9 @@ func TestGenerateApplications(t *testing.T) {
} }
generators := map[string]generators.Generator{ generators := map[string]generators.Generator{
"List": generatorMock, "List": &generatorMock,
} }
renderer := rendererMock renderer := &rendererMock
got, reason, err := GenerateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{ got, reason, err := GenerateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@@ -153,7 +153,7 @@ func TestGenerateApplications(t *testing.T) {
func TestMergeTemplateApplications(t *testing.T) { func TestMergeTemplateApplications(t *testing.T) {
for _, c := range []struct { for _, c := range []struct {
name string name string
params []map[string]any params []map[string]interface{}
template v1alpha1.ApplicationSetTemplate template v1alpha1.ApplicationSetTemplate
overrideTemplate v1alpha1.ApplicationSetTemplate overrideTemplate v1alpha1.ApplicationSetTemplate
expectedMerged v1alpha1.ApplicationSetTemplate expectedMerged v1alpha1.ApplicationSetTemplate
@@ -161,7 +161,7 @@ func TestMergeTemplateApplications(t *testing.T) {
}{ }{
{ {
name: "Generate app", name: "Generate app",
params: []map[string]any{{"name": "app1"}}, params: []map[string]interface{}{{"name": "app1"}},
template: v1alpha1.ApplicationSetTemplate{ template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "name", Name: "name",
@@ -200,26 +200,26 @@ func TestMergeTemplateApplications(t *testing.T) {
cc := c cc := c
t.Run(cc.name, func(t *testing.T) { t.Run(cc.name, func(t *testing.T) {
generatorMock := &genmock.Generator{} generatorMock := genmock.Generator{}
generator := v1alpha1.ApplicationSetGenerator{ generator := v1alpha1.ApplicationSetGenerator{
List: &v1alpha1.ListGenerator{}, List: &v1alpha1.ListGenerator{},
} }
generatorMock.EXPECT().GenerateParams(&generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything). generatorMock.On("GenerateParams", &generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).
Return(cc.params, nil) Return(cc.params, nil)
generatorMock.EXPECT().GetTemplate(&generator). generatorMock.On("GetTemplate", &generator).
Return(&cc.overrideTemplate) Return(&cc.overrideTemplate)
rendererMock := &rendmock.Renderer{} rendererMock := rendmock.Renderer{}
rendererMock.EXPECT().RenderTemplateParams(GetTempApplication(cc.expectedMerged), mock.AnythingOfType("*v1alpha1.ApplicationSetSyncPolicy"), cc.params[0], false, []string(nil)). rendererMock.On("RenderTemplateParams", GetTempApplication(cc.expectedMerged), mock.AnythingOfType("*v1alpha1.ApplicationSetSyncPolicy"), cc.params[0], false, []string(nil)).
Return(&cc.expectedApps[0], nil) Return(&cc.expectedApps[0], nil)
generators := map[string]generators.Generator{ generators := map[string]generators.Generator{
"List": generatorMock, "List": &generatorMock,
} }
renderer := rendererMock renderer := &rendererMock
got, _, _ := GenerateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{ got, _, _ := GenerateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@@ -245,13 +245,13 @@ func TestMergeTemplateApplications(t *testing.T) {
func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) { func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) {
for _, cases := range []struct { for _, cases := range []struct {
name string name string
params []map[string]any params []map[string]interface{}
template v1alpha1.ApplicationSetTemplate template v1alpha1.ApplicationSetTemplate
expectedApp []v1alpha1.Application expectedApp []v1alpha1.Application
}{ }{
{ {
name: "Generate an application from a go template application set manifest using a pull request generator", name: "Generate an application from a go template application set manifest using a pull request generator",
params: []map[string]any{ params: []map[string]interface{}{
{ {
"number": "1", "number": "1",
"title": "title1", "title": "title1",
@@ -312,19 +312,19 @@ func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) {
}, },
} { } {
t.Run(cases.name, func(t *testing.T) { t.Run(cases.name, func(t *testing.T) {
generatorMock := &genmock.Generator{} generatorMock := genmock.Generator{}
generator := v1alpha1.ApplicationSetGenerator{ generator := v1alpha1.ApplicationSetGenerator{
PullRequest: &v1alpha1.PullRequestGenerator{}, PullRequest: &v1alpha1.PullRequestGenerator{},
} }
generatorMock.EXPECT().GenerateParams(&generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything). generatorMock.On("GenerateParams", &generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).
Return(cases.params, nil) Return(cases.params, nil)
generatorMock.EXPECT().GetTemplate(&generator). generatorMock.On("GetTemplate", &generator).
Return(&cases.template) Return(&cases.template, nil)
generators := map[string]generators.Generator{ generators := map[string]generators.Generator{
"PullRequest": generatorMock, "PullRequest": &generatorMock,
} }
renderer := &utils.Render{} renderer := &utils.Render{}
@@ -341,10 +341,10 @@ func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) {
renderer, renderer,
nil, nil,
) )
assert.Equal(t, cases.expectedApp[0].Name, gotApp[0].Name) assert.EqualValues(t, cases.expectedApp[0].ObjectMeta.Name, gotApp[0].ObjectMeta.Name)
assert.Equal(t, cases.expectedApp[0].Spec.Source.TargetRevision, gotApp[0].Spec.Source.TargetRevision) assert.EqualValues(t, cases.expectedApp[0].Spec.Source.TargetRevision, gotApp[0].Spec.Source.TargetRevision)
assert.Equal(t, cases.expectedApp[0].Spec.Destination.Namespace, gotApp[0].Spec.Destination.Namespace) assert.EqualValues(t, cases.expectedApp[0].Spec.Destination.Namespace, gotApp[0].Spec.Destination.Namespace)
assert.True(t, maps.Equal(cases.expectedApp[0].Labels, gotApp[0].Labels)) assert.True(t, maps.Equal(cases.expectedApp[0].ObjectMeta.Labels, gotApp[0].ObjectMeta.Labels))
}) })
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,25 +2,23 @@ package generators
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net/http"
"strconv" "strconv"
"time" "time"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/gosimple/slug" "github.com/gosimple/slug"
log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v3/applicationset/services" pullrequest "github.com/argoproj/argo-cd/v2/applicationset/services/pull_request"
pullrequest "github.com/argoproj/argo-cd/v3/applicationset/services/pull_request" "github.com/argoproj/argo-cd/v2/applicationset/utils"
"github.com/argoproj/argo-cd/v3/applicationset/utils" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
) )
var _ Generator = (*PullRequestGenerator)(nil)
const ( const (
DefaultPullRequestRequeueAfter = 30 * time.Minute DefaultPullRequestRequeueAfterSeconds = 30 * time.Minute
) )
type PullRequestGenerator struct { type PullRequestGenerator struct {
@@ -45,24 +43,20 @@ func (g *PullRequestGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alph
return time.Duration(*appSetGenerator.PullRequest.RequeueAfterSeconds) * time.Second return time.Duration(*appSetGenerator.PullRequest.RequeueAfterSeconds) * time.Second
} }
return DefaultPullRequestRequeueAfter return DefaultPullRequestRequeueAfterSeconds
}
func (g *PullRequestGenerator) GetContinueOnRepoNotFoundError(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) bool {
return appSetGenerator.PullRequest.ContinueOnRepoNotFoundError
} }
func (g *PullRequestGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate { func (g *PullRequestGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
return &appSetGenerator.PullRequest.Template return &appSetGenerator.PullRequest.Template
} }
func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) { func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet, _ client.Client) ([]map[string]interface{}, error) {
if appSetGenerator == nil { if appSetGenerator == nil {
return nil, ErrEmptyAppSetGenerator return nil, EmptyAppSetGeneratorError
} }
if appSetGenerator.PullRequest == nil { if appSetGenerator.PullRequest == nil {
return nil, ErrEmptyAppSetGenerator return nil, EmptyAppSetGeneratorError
} }
ctx := context.Background() ctx := context.Background()
@@ -72,15 +66,10 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
} }
pulls, err := pullrequest.ListPullRequests(ctx, svc, appSetGenerator.PullRequest.Filters) pulls, err := pullrequest.ListPullRequests(ctx, svc, appSetGenerator.PullRequest.Filters)
params := make([]map[string]any, 0, len(pulls))
if err != nil { if err != nil {
if pullrequest.IsRepositoryNotFoundError(err) && g.GetContinueOnRepoNotFoundError(appSetGenerator) {
log.WithError(err).WithField("generator", g).
Warn("Skipping params generation for this repository since it was not found.")
return params, nil
}
return nil, fmt.Errorf("error listing repos: %w", err) return nil, fmt.Errorf("error listing repos: %w", err)
} }
params := make([]map[string]interface{}, 0, len(pulls))
// In order to follow the DNS label standard as defined in RFC 1123, // In order to follow the DNS label standard as defined in RFC 1123,
// we need to limit the 'branch' to 50 to give room to append/suffix-ing it // we need to limit the 'branch' to 50 to give room to append/suffix-ing it
@@ -106,7 +95,7 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
shortSHALength7 = len(pull.HeadSHA) shortSHALength7 = len(pull.HeadSHA)
} }
paramMap := map[string]any{ paramMap := map[string]interface{}{
"number": strconv.Itoa(pull.Number), "number": strconv.Itoa(pull.Number),
"title": pull.Title, "title": pull.Title,
"branch": pull.Branch, "branch": pull.Branch,
@@ -123,11 +112,6 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
if applicationSetInfo != nil && applicationSetInfo.Spec.GoTemplate { if applicationSetInfo != nil && applicationSetInfo.Spec.GoTemplate {
paramMap["labels"] = pull.Labels paramMap["labels"] = pull.Labels
} }
err := appendTemplatedValues(appSetGenerator.PullRequest.Values, paramMap, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions)
if err != nil {
return nil, fmt.Errorf("failed to append templated values: %w", err)
}
params = append(params, paramMap) params = append(params, paramMap)
} }
return params, nil return params, nil
@@ -159,7 +143,7 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err) return nil, fmt.Errorf("error fetching Secret token: %w", err)
} }
return pullrequest.NewGitLabService(token, providerConfig.API, providerConfig.Project, providerConfig.Labels, providerConfig.PullRequestState, g.scmRootCAPath, providerConfig.Insecure, caCerts) return pullrequest.NewGitLabService(ctx, token, providerConfig.API, providerConfig.Project, providerConfig.Labels, providerConfig.PullRequestState, g.scmRootCAPath, providerConfig.Insecure, caCerts)
} }
if generatorConfig.Gitea != nil { if generatorConfig.Gitea != nil {
providerConfig := generatorConfig.Gitea providerConfig := generatorConfig.Gitea
@@ -167,8 +151,7 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err) return nil, fmt.Errorf("error fetching Secret token: %w", err)
} }
return pullrequest.NewGiteaService(ctx, token, providerConfig.API, providerConfig.Owner, providerConfig.Repo, providerConfig.Insecure)
return pullrequest.NewGiteaService(token, providerConfig.API, providerConfig.Owner, providerConfig.Repo, providerConfig.Labels, providerConfig.Insecure)
} }
if generatorConfig.BitbucketServer != nil { if generatorConfig.BitbucketServer != nil {
providerConfig := generatorConfig.BitbucketServer providerConfig := generatorConfig.BitbucketServer
@@ -192,8 +175,9 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
return nil, fmt.Errorf("error fetching Secret token: %w", err) return nil, fmt.Errorf("error fetching Secret token: %w", err)
} }
return pullrequest.NewBitbucketServiceBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.Repo, g.scmRootCAPath, providerConfig.Insecure, caCerts) return pullrequest.NewBitbucketServiceBasicAuth(ctx, providerConfig.BasicAuth.Username, password, providerConfig.API, providerConfig.Project, providerConfig.Repo, g.scmRootCAPath, providerConfig.Insecure, caCerts)
} else {
return pullrequest.NewBitbucketServiceNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.Repo, g.scmRootCAPath, providerConfig.Insecure, caCerts)
} }
return pullrequest.NewBitbucketServiceNoAuth(ctx, providerConfig.API, providerConfig.Project, providerConfig.Repo, g.scmRootCAPath, providerConfig.Insecure, caCerts)
} }
if generatorConfig.Bitbucket != nil { if generatorConfig.Bitbucket != nil {
providerConfig := generatorConfig.Bitbucket providerConfig := generatorConfig.Bitbucket
@@ -209,8 +193,9 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
return nil, fmt.Errorf("error fetching Secret token: %w", err) return nil, fmt.Errorf("error fetching Secret token: %w", err)
} }
return pullrequest.NewBitbucketCloudServiceBasicAuth(providerConfig.API, providerConfig.BasicAuth.Username, password, providerConfig.Owner, providerConfig.Repo) return pullrequest.NewBitbucketCloudServiceBasicAuth(providerConfig.API, providerConfig.BasicAuth.Username, password, providerConfig.Owner, providerConfig.Repo)
} else {
return pullrequest.NewBitbucketCloudServiceNoAuth(providerConfig.API, providerConfig.Owner, providerConfig.Repo)
} }
return pullrequest.NewBitbucketCloudServiceNoAuth(providerConfig.API, providerConfig.Owner, providerConfig.Repo)
} }
if generatorConfig.AzureDevOps != nil { if generatorConfig.AzureDevOps != nil {
providerConfig := generatorConfig.AzureDevOps providerConfig := generatorConfig.AzureDevOps
@@ -218,33 +203,18 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err) return nil, fmt.Errorf("error fetching Secret token: %w", err)
} }
return pullrequest.NewAzureDevOpsService(token, providerConfig.API, providerConfig.Organization, providerConfig.Project, providerConfig.Repo, providerConfig.Labels) return pullrequest.NewAzureDevOpsService(ctx, token, providerConfig.API, providerConfig.Organization, providerConfig.Project, providerConfig.Repo, providerConfig.Labels)
} }
return nil, errors.New("no Pull Request provider implementation configured") return nil, fmt.Errorf("no Pull Request provider implementation configured")
} }
func (g *PullRequestGenerator) github(ctx context.Context, cfg *argoprojiov1alpha1.PullRequestGeneratorGithub, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) { func (g *PullRequestGenerator) github(ctx context.Context, cfg *argoprojiov1alpha1.PullRequestGeneratorGithub, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
var metricsCtx *services.MetricsContext
var httpClient *http.Client
if g.enableGitHubAPIMetrics {
metricsCtx = &services.MetricsContext{
AppSetNamespace: applicationSetInfo.Namespace,
AppSetName: applicationSetInfo.Name,
}
httpClient = services.NewGitHubMetricsClient(metricsCtx)
}
// use an app if it was configured // use an app if it was configured
if cfg.AppSecretName != "" { if cfg.AppSecretName != "" {
auth, err := g.GitHubApps.GetAuthSecret(ctx, cfg.AppSecretName) auth, err := g.GitHubApps.GetAuthSecret(ctx, cfg.AppSecretName)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting GitHub App secret: %w", err) return nil, fmt.Errorf("error getting GitHub App secret: %w", err)
} }
if g.enableGitHubAPIMetrics {
return pullrequest.NewGithubAppService(*auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels, httpClient)
}
return pullrequest.NewGithubAppService(*auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels) return pullrequest.NewGithubAppService(*auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
} }
@@ -253,9 +223,5 @@ func (g *PullRequestGenerator) github(ctx context.Context, cfg *argoprojiov1alph
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching Secret token: %w", err) return nil, fmt.Errorf("error fetching Secret token: %w", err)
} }
return pullrequest.NewGithubService(ctx, token, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
if g.enableGitHubAPIMetrics {
return pullrequest.NewGithubService(token, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels, httpClient)
}
return pullrequest.NewGithubService(token, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
} }

View File

@@ -2,26 +2,24 @@ package generators
import ( import (
"context" "context"
"errors" "fmt"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
pullrequest "github.com/argoproj/argo-cd/v3/applicationset/services/pull_request" pullrequest "github.com/argoproj/argo-cd/v2/applicationset/services/pull_request"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
) )
func TestPullRequestGithubGenerateParams(t *testing.T) { func TestPullRequestGithubGenerateParams(t *testing.T) {
ctx := t.Context() ctx := context.Background()
cases := []struct { cases := []struct {
selectFunc func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) selectFunc func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error)
values map[string]string expected []map[string]interface{}
expected []map[string]any expectedErr error
expectedErr error applicationSet argoprojiov1alpha1.ApplicationSet
applicationSet argoprojiov1alpha1.ApplicationSet
continueOnRepoNotFoundError bool
}{ }{
{ {
selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) { selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
@@ -40,7 +38,7 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
nil, nil,
) )
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"number": "1", "number": "1",
"title": "title1", "title": "title1",
@@ -73,7 +71,7 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
nil, nil,
) )
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"number": "2", "number": "2",
"title": "title2", "title": "title2",
@@ -106,7 +104,7 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
nil, nil,
) )
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"number": "1", "number": "1",
"title": "title1", "title": "title1",
@@ -126,75 +124,12 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) { selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
return pullrequest.NewFakeService( return pullrequest.NewFakeService(
ctx, ctx,
[]*pullrequest.PullRequest{
{
Number: 1,
Title: "title1",
Branch: "my_branch",
TargetBranch: "master",
HeadSHA: "abcd",
Author: "testName",
},
},
nil, nil,
) fmt.Errorf("fake error"),
},
values: map[string]string{
"foo": "bar",
"pr_branch": "{{ branch }}",
},
expected: []map[string]any{
{
"number": "1",
"title": "title1",
"branch": "my_branch",
"branch_slug": "my-branch",
"target_branch": "master",
"target_branch_slug": "master",
"head_sha": "abcd",
"head_short_sha": "abcd",
"head_short_sha_7": "abcd",
"author": "testName",
"values.foo": "bar",
"values.pr_branch": "my_branch",
},
},
expectedErr: nil,
},
{
selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
return pullrequest.NewFakeService(
ctx,
nil,
errors.New("fake error"),
) )
}, },
expected: nil, expected: nil,
expectedErr: errors.New("error listing repos: fake error"), expectedErr: fmt.Errorf("error listing repos: fake error"),
},
{
selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
return pullrequest.NewFakeService(
ctx,
nil,
pullrequest.NewRepositoryNotFoundError(errors.New("repository not found")),
)
},
expected: []map[string]any{},
expectedErr: nil,
continueOnRepoNotFoundError: true,
},
{
selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
return pullrequest.NewFakeService(
ctx,
nil,
pullrequest.NewRepositoryNotFoundError(errors.New("repository not found")),
)
},
expected: nil,
expectedErr: errors.New("error listing repos: repository not found"),
continueOnRepoNotFoundError: false,
}, },
{ {
selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) { selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
@@ -214,7 +149,7 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
nil, nil,
) )
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"number": "1", "number": "1",
"title": "title1", "title": "title1",
@@ -255,7 +190,7 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
nil, nil,
) )
}, },
expected: []map[string]any{ expected: []map[string]interface{}{
{ {
"number": "1", "number": "1",
"title": "title1", "title": "title1",
@@ -277,51 +212,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
}, },
}, },
}, },
{
selectFunc: func(context.Context, *argoprojiov1alpha1.PullRequestGenerator, *argoprojiov1alpha1.ApplicationSet) (pullrequest.PullRequestService, error) {
return pullrequest.NewFakeService(
ctx,
[]*pullrequest.PullRequest{
{
Number: 1,
Title: "title1",
Branch: "my_branch",
TargetBranch: "master",
HeadSHA: "abcd",
Author: "testName",
Labels: []string{"preview", "preview:team1"},
},
},
nil,
)
},
values: map[string]string{
"preview_env": "{{ regexFind \"(team1|team2)\" (.labels | join \",\") }}",
},
expected: []map[string]any{
{
"number": "1",
"title": "title1",
"branch": "my_branch",
"branch_slug": "my-branch",
"target_branch": "master",
"target_branch_slug": "master",
"head_sha": "abcd",
"head_short_sha": "abcd",
"head_short_sha_7": "abcd",
"author": "testName",
"labels": []string{"preview", "preview:team1"},
"values": map[string]string{"preview_env": "team1"},
},
},
expectedErr: nil,
applicationSet: argoprojiov1alpha1.ApplicationSet{
Spec: argoprojiov1alpha1.ApplicationSetSpec{
// Application set is using fasttemplate.
GoTemplate: true,
},
},
},
} }
for _, c := range cases { for _, c := range cases {
@@ -329,15 +219,12 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
selectServiceProviderFunc: c.selectFunc, selectServiceProviderFunc: c.selectFunc,
} }
generatorConfig := argoprojiov1alpha1.ApplicationSetGenerator{ generatorConfig := argoprojiov1alpha1.ApplicationSetGenerator{
PullRequest: &argoprojiov1alpha1.PullRequestGenerator{ PullRequest: &argoprojiov1alpha1.PullRequestGenerator{},
Values: c.values,
ContinueOnRepoNotFoundError: c.continueOnRepoNotFoundError,
},
} }
got, gotErr := gen.GenerateParams(&generatorConfig, &c.applicationSet, nil) got, gotErr := gen.GenerateParams(&generatorConfig, &c.applicationSet, nil)
if c.expectedErr != nil { if c.expectedErr != nil {
require.EqualError(t, gotErr, c.expectedErr.Error()) assert.Equal(t, c.expectedErr.Error(), gotErr.Error())
} else { } else {
require.NoError(t, gotErr) require.NoError(t, gotErr)
} }
@@ -346,8 +233,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
} }
func TestAllowedSCMProviderPullRequest(t *testing.T) { func TestAllowedSCMProviderPullRequest(t *testing.T) {
t.Parallel()
cases := []struct { cases := []struct {
name string name string
providerConfig *argoprojiov1alpha1.PullRequestGenerator providerConfig *argoprojiov1alpha1.PullRequestGenerator
@@ -398,7 +283,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
"gitea.myorg.com", "gitea.myorg.com",
"bitbucket.myorg.com", "bitbucket.myorg.com",
"azuredevops.myorg.com", "azuredevops.myorg.com",
}, true, true, nil, true)) }, true, nil, true))
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@@ -421,7 +306,7 @@ func TestAllowedSCMProviderPullRequest(t *testing.T) {
} }
func TestSCMProviderDisabled_PRGenerator(t *testing.T) { func TestSCMProviderDisabled_PRGenerator(t *testing.T) {
generator := NewPullRequestGenerator(nil, NewSCMConfig("", []string{}, false, true, nil, true)) generator := NewPullRequestGenerator(nil, NewSCMConfig("", []string{}, false, nil, true))
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,15 +5,12 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7" "github.com/microsoft/azure-devops-go-api/azuredevops"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/core" core "github.com/microsoft/azure-devops-go-api/azuredevops/core"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/git" git "github.com/microsoft/azure-devops-go-api/azuredevops/git"
) )
const ( const AZURE_DEVOPS_DEFAULT_URL = "https://dev.azure.com"
AZURE_DEVOPS_DEFAULT_URL = "https://dev.azure.com"
AZURE_DEVOPS_PROJECT_NOT_FOUND_ERROR = "The following project does not exist"
)
type AzureDevOpsClientFactory interface { type AzureDevOpsClientFactory interface {
// Returns an Azure Devops Client interface. // Returns an Azure Devops Client interface.
@@ -44,14 +41,14 @@ var (
_ AzureDevOpsClientFactory = &devopsFactoryImpl{} _ AzureDevOpsClientFactory = &devopsFactoryImpl{}
) )
func NewAzureDevOpsService(token, url, organization, project, repo string, labels []string) (PullRequestService, error) { func NewAzureDevOpsService(ctx context.Context, token, url, organization, project, repo string, labels []string) (PullRequestService, error) {
organizationURL := buildURL(url, organization) organizationUrl := buildURL(url, organization)
var connection *azuredevops.Connection var connection *azuredevops.Connection
if token == "" { if token == "" {
connection = azuredevops.NewAnonymousConnection(organizationURL) connection = azuredevops.NewAnonymousConnection(organizationUrl)
} else { } else {
connection = azuredevops.NewPatConnection(organizationURL, token) connection = azuredevops.NewPatConnection(organizationUrl, token)
} }
return &AzureDevOpsService{ return &AzureDevOpsService{
@@ -73,22 +70,13 @@ func (a *AzureDevOpsService) List(ctx context.Context) ([]*PullRequest, error) {
SearchCriteria: &git.GitPullRequestSearchCriteria{}, SearchCriteria: &git.GitPullRequestSearchCriteria{},
} }
pullRequests := []*PullRequest{}
azurePullRequests, err := client.GetPullRequestsByProject(ctx, args) azurePullRequests, err := client.GetPullRequestsByProject(ctx, args)
if err != nil { if err != nil {
// A standard Http 404 error is not returned for Azure DevOps,
// so checking the error message for a specific pattern.
// NOTE: Since the repos are filtered later, only existence of the project
// is relevant for AzureDevOps
if strings.Contains(err.Error(), AZURE_DEVOPS_PROJECT_NOT_FOUND_ERROR) {
// return a custom error indicating that the repository is not found,
// but also return the empty result since the decision to continue or not in this case is made by the caller
return pullRequests, NewRepositoryNotFoundError(err)
}
return nil, fmt.Errorf("failed to get pull requests by project: %w", err) return nil, fmt.Errorf("failed to get pull requests by project: %w", err)
} }
pullRequests := []*PullRequest{}
for _, pr := range *azurePullRequests { for _, pr := range *azurePullRequests {
if pr.Repository == nil || if pr.Repository == nil ||
pr.Repository.Name == nil || pr.Repository.Name == nil ||

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