Compare commits

..

1 Commits

Author SHA1 Message Date
Dan Garfield
37efa1880b Update argocd_appset_generate.md
Signed-off-by: Dan Garfield <dan@codefresh.io>
2025-03-13 11:42:19 -06:00
2206 changed files with 71328 additions and 291543 deletions

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

@@ -9,78 +9,18 @@ assignees: ''
Target RC1 date: ___. __, ____ Target RC1 date: ___. __, ____
Target GA date: ___. __, ____ Target GA date: ___. __, ____
## RC1 Release Checklist - [ ] 1wk before feature freeze post in #argo-contributors that PRs must be merged by DD-MM-YYYY to be included in the release - ask approvers to drop items from milestone they cant merge
- [ ] 1wk before feature freeze post in #argo-contributors that PRs must be merged by DD-MM-YYYY to be included in the release - ask approvers to drop items from milestone they can't merge
- [ ] At least two days before RC1 date, draft RC blog post and submit it for review (or delegate this task) - [ ] At least two days before RC1 date, draft RC blog post and submit it for review (or delegate this task)
- [ ] Create new release branch (or delegate this task to an Approver)
- [ ] Add the release branch to ReadTheDocs
- [ ] Cut RC1 (or delegate this task to an Approver and coordinate timing) - [ ] Cut RC1 (or delegate this task to an Approver and coordinate timing)
- [ ] Run the [Init ArgoCD Release workflow](https://github.com/argoproj/argo-cd/actions/workflows/init-release.yaml) from the release branch - [ ] Create new release branch
- [ ] Review and merge the generated version bump PR - [ ] Add the release branch to ReadTheDocs
- [ ] Run `./hack/trigger-release.sh` to push the release tag - [ ] Confirm that tweet and blog post are ready
- [ ] Monitor the [Publish ArgoCD Release workflow](https://github.com/argoproj/argo-cd/actions/workflows/release.yaml) - [ ] Trigger the release
- [ ] Verify the release on [GitHub releases](https://github.com/argoproj/argo-cd/releases) - [ ] After the release is finished, publish tweet and blog post
- [ ] Verify the container image on [Quay.io](https://quay.io/repository/argoproj/argocd?tab=tags) - [ ] Post in #argo-cd and #argo-announcements with lots of emojis announcing the release and requesting help testing
- [ ] Confirm the new version appears in [Read the Docs](https://argo-cd.readthedocs.io/) - [ ] Monitor support channels for issues, cherry-picking bugfixes and docs fixes as appropriate (or delegate this task to an Approver and coordinate timing)
- [ ] Verify the docs release build in https://app.readthedocs.org/projects/argo-cd/ succeeded and retry if failed (requires an Approver with admin creds to readthedocs) - [ ] 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)
- [ ] Announce RC1 release - [ ] 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)
- [ ] Confirm that tweet and blog post are ready - [ ] 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)
- [ ] Publish tweet and blog post
- [ ] Post in #argo-cd and #argo-announcements requesting help testing:
```
:mega: Argo CD v{MAJOR}.{MINOR}.{PATCH}-rc{RC_NUMBER} is OUT NOW! :argocd::tada:
Please go through the following resources to know more about the release:
Release notes: https://github.com/argoproj/argo-cd/releases/tag/v{VERSION}
Blog: {BLOG_POST_URL}
We'd love your help testing this release candidate! Please try it out in your environments and report any issues you find. This helps us ensure a stable GA release.
Thanks to all the folks who spent their time contributing to this release in any way possible!
```
- [ ] Monitor support channels for issues, cherry-picking bugfixes and docs fixes as appropriate during the RC period (or delegate this task to an Approver and coordinate timing)
## GA Release Checklist
- [ ] At GA release date, evaluate if any bugs justify delaying the release
- [ ] Prepare for EOL version (version that is 3 releases old)
- [ ] 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)
- [ ] Edit the final patch release on GitHub and add the following notice at the top:
```markdown
> [!IMPORTANT]
> **END OF LIFE NOTICE**
>
> This is the final release of the {EOL_SERIES} release series. As of {GA_DATE}, this version has reached end of life and will no longer receive bug fixes or security updates.
>
> **Action Required**: Please upgrade to a [supported version](https://argo-cd.readthedocs.io/en/stable/operator-manual/upgrading/overview/) (v{SUPPORTED_VERSION_1}, v{SUPPORTED_VERSION_2}, or v{NEW_VERSION}).
```
- [ ] Cut GA release (or delegate this task to an Approver and coordinate timing)
- [ ] Run the [Init ArgoCD Release workflow](https://github.com/argoproj/argo-cd/actions/workflows/init-release.yaml) from the release branch
- [ ] Review and merge the generated version bump PR
- [ ] Run `./hack/trigger-release.sh` to push the release tag
- [ ] Monitor the [Publish ArgoCD Release workflow](https://github.com/argoproj/argo-cd/actions/workflows/release.yaml)
- [ ] Verify the release on [GitHub releases](https://github.com/argoproj/argo-cd/releases)
- [ ] Verify the container image on [Quay.io](https://quay.io/repository/argoproj/argocd?tab=tags)
- [ ] Verify the `stable` tag has been updated
- [ ] Confirm the new version appears in [Read the Docs](https://argo-cd.readthedocs.io/)
- [ ] Verify the docs release build in https://app.readthedocs.org/projects/argo-cd/ succeeded and retry if failed (requires an Approver with admin creds to readthedocs)
- [ ] Announce GA release with EOL notice
- [ ] Confirm that tweet and blog post are ready
- [ ] Publish tweet and blog post
- [ ] Post in #argo-cd and #argo-announcements announcing the release and EOL:
```
:mega: Argo CD v{MAJOR}.{MINOR} is OUT NOW! :argocd::tada:
Please go through the following resources to know more about the release:
Upgrade instructions: https://argo-cd.readthedocs.io/en/latest/operator-manual/upgrading/{PREV_MINOR}-{MAJOR}.{MINOR}/
Blog: {BLOG_POST_URL}
:warning: IMPORTANT: With the release of Argo CD v{MAJOR}.{MINOR}, support for Argo CD v{EOL_VERSION} has officially reached End of Life (EOL).
Thanks to all the folks who spent their time contributing to this release in any way possible!
```
- [ ] (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

@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@@ -37,7 +37,7 @@ jobs:
working-directory: /home/runner/go/src/github.com/argoproj/argo-cd working-directory: /home/runner/go/src/github.com/argoproj/argo-cd
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Add ~/go/bin to PATH - name: Add ~/go/bin to PATH
@@ -74,7 +74,7 @@ jobs:
rsync -a --exclude=.git /home/runner/go/src/github.com/argoproj/argo-cd/ ../argo-cd rsync -a --exclude=.git /home/runner/go/src/github.com/argoproj/argo-cd/ ../argo-cd
- name: Create pull request - name: Create pull request
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0 uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with: with:
commit-message: "Bump major version to ${{ steps.get-target-version.outputs.TARGET_VERSION }}" 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 }}" title: "Bump major version to ${{ steps.get-target-version.outputs.TARGET_VERSION }}"

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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
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.5' GOLANG_VERSION: '1.24.1'
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,8 +31,8 @@ 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1 - 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
@@ -55,9 +55,9 @@ jobs:
- changes - changes
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.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@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.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 }}
@@ -102,16 +103,16 @@ jobs:
- changes - changes
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.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@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.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.64.7
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 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@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.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@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 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@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.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@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.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,16 +251,15 @@ jobs:
- changes - changes
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.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
# generalizing repo name for forks: ${{ github.event.repository.name }}
run: | run: |
mkdir -p ~/go/src/github.com/argoproj mkdir -p ~/go/src/github.com/argoproj
cp -a ../${{ github.event.repository.name }} ~/go/src/github.com/argoproj cp -a ../argo-cd ~/go/src/github.com/argoproj
- name: Add ~/go/bin to PATH - name: Add ~/go/bin to PATH
run: | run: |
echo "/home/runner/go/bin" >> $GITHUB_PATH echo "/home/runner/go/bin" >> $GITHUB_PATH
@@ -271,14 +271,12 @@ jobs:
# We need to vendor go modules for codegen yet # We need to vendor go modules for codegen yet
go mod download go mod download
go mod vendor -v go mod vendor -v
# generalizing repo name for forks: ${{ github.event.repository.name }} working-directory: /home/runner/go/src/github.com/argoproj/argo-cd
working-directory: /home/runner/go/src/github.com/argoproj/${{ github.event.repository.name }}
- name: Install toolchain for codegen - name: Install toolchain for codegen
run: | run: |
make install-codegen-tools-local make install-codegen-tools-local
make install-go-tools-local make install-go-tools-local
# generalizing repo name for forks: ${{ github.event.repository.name }} working-directory: /home/runner/go/src/github.com/argoproj/argo-cd
working-directory: /home/runner/go/src/github.com/argoproj/${{ github.event.repository.name }}
# We install kustomize in the dist directory # We install kustomize in the dist directory
- name: Add dist to PATH - name: Add dist to PATH
run: | run: |
@@ -289,14 +287,12 @@ jobs:
export GOPATH=$(go env GOPATH) export GOPATH=$(go env GOPATH)
git checkout -- go.mod go.sum git checkout -- go.mod go.sum
make codegen-local make codegen-local
# generalizing repo name for forks: ${{ github.event.repository.name }} working-directory: /home/runner/go/src/github.com/argoproj/argo-cd
working-directory: /home/runner/go/src/github.com/argoproj/${{ github.event.repository.name }}
- name: Check nothing has changed - name: Check nothing has changed
run: | run: |
set -xo pipefail set -xo pipefail
git diff --exit-code -- . ':!go.sum' ':!go.mod' ':!assets/swagger.json' | tee codegen.patch git diff --exit-code -- . ':!go.sum' ':!go.mod' ':!assets/swagger.json' | tee codegen.patch
# generalizing repo name for forks: ${{ github.event.repository.name }} working-directory: /home/runner/go/src/github.com/argoproj/argo-cd
working-directory: /home/runner/go/src/github.com/argoproj/${{ github.event.repository.name }}
build-ui: build-ui:
name: Build, test & lint UI code name: Build, test & lint UI code
@@ -307,15 +303,15 @@ jobs:
- changes - changes
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup NodeJS - name: Setup NodeJS
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.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@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.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') }}
@@ -337,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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- 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' }}
@@ -359,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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 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@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.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') }}
@@ -372,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
@@ -389,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@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 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
@@ -406,31 +393,35 @@ 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@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9 # v7.0.0 uses: SonarSource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203 # v4.2.1
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: ${{ github.repository == 'argoproj/argo-cd' && 'oracle-vm-16cpu-64gb-x86-64' || 'ubuntu-22.04' }} 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 # 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. # We designate the latest version because we only collect code coverage for that version.
k3s: k3s:
- version: v1.34.2
latest: true
- version: v1.33.1
latest: false
- version: v1.32.1 - version: v1.32.1
latest: false latest: true
- version: v1.31.0 - version: v1.31.0
latest: false latest: false
- version: v1.30.4
latest: false
- version: v1.29.8
latest: false
needs: needs:
- build-go - build-go
- changes - changes
env: env:
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_TLS_DATA_PATH: '/tmp/argo-e2e/app/config/tls'
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'
@@ -447,14 +438,11 @@ jobs:
swap-storage: false swap-storage: false
tool-cache: false tool-cache: false
- name: Checkout code - name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version: ${{ env.GOLANG_VERSION }} go-version: ${{ env.GOLANG_VERSION }}
- name: Set GOPATH
run: |
echo "GOPATH=$HOME/go" >> $GITHUB_ENV
- name: GH actions workaround - Kill XSP4 process - name: GH actions workaround - Kill XSP4 process
run: | run: |
sudo pkill mono || true sudo pkill mono || true
@@ -465,19 +453,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 $(whoami) $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 $(whoami) $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@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.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/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
@@ -497,13 +485,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.3-alpine docker pull redis:7.2.7-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 $(whoami) 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: |
@@ -529,13 +517,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

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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 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@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with: with:
go-version-file: go.mod go-version-file: go.mod

View File

@@ -56,27 +56,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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 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@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.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@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
- name: Setup tags for container image as a CSV type - name: Setup tags for container image as a CSV type
run: | run: |
@@ -103,7 +102,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 +110,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 +118,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 +141,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

@@ -19,49 +19,16 @@ jobs:
set-vars: set-vars:
permissions: permissions:
contents: read contents: read
# Always run to calculate variables - other jobs check outputs if: github.repository == 'argoproj/argo-cd'
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
outputs: outputs:
image-tag: ${{ steps.image.outputs.tag}} image-tag: ${{ steps.image.outputs.tag}}
platforms: ${{ steps.platforms.outputs.platforms }} platforms: ${{ steps.platforms.outputs.platforms }}
image_namespace: ${{ steps.image.outputs.image_namespace }}
image_repository: ${{ steps.image.outputs.image_repository }}
quay_image_name: ${{ steps.image.outputs.quay_image_name }}
ghcr_image_name: ${{ steps.image.outputs.ghcr_image_name }}
ghcr_provenance_image: ${{ steps.image.outputs.ghcr_provenance_image }}
allow_ghcr_publish: ${{ steps.image.outputs.allow_ghcr_publish }}
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
- name: Set image tag and names - name: Set image tag for ghcr
run: | run: echo "tag=$(cat ./VERSION)-${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
# Calculate image tag
TAG="$(cat ./VERSION)-${GITHUB_SHA::8}"
echo "tag=$TAG" >> $GITHUB_OUTPUT
# Calculate image names with defaults
IMAGE_NAMESPACE="${{ vars.IMAGE_NAMESPACE || 'argoproj' }}"
IMAGE_REPOSITORY="${{ vars.IMAGE_REPOSITORY || 'argocd' }}"
GHCR_NAMESPACE="${{ vars.GHCR_NAMESPACE || github.repository }}"
GHCR_REPOSITORY="${{ vars.GHCR_REPOSITORY || 'argocd' }}"
echo "image_namespace=$IMAGE_NAMESPACE" >> $GITHUB_OUTPUT
echo "image_repository=$IMAGE_REPOSITORY" >> $GITHUB_OUTPUT
# Construct image name
echo "quay_image_name=quay.io/$IMAGE_NAMESPACE/$IMAGE_REPOSITORY:latest" >> $GITHUB_OUTPUT
ALLOW_GHCR_PUBLISH=false
if [[ "${{ github.repository }}" == "argoproj/argo-cd" || "$GHCR_NAMESPACE" != argoproj/* ]]; then
ALLOW_GHCR_PUBLISH=true
echo "ghcr_image_name=ghcr.io/$GHCR_NAMESPACE/$GHCR_REPOSITORY:$TAG" >> $GITHUB_OUTPUT
echo "ghcr_provenance_image=ghcr.io/$GHCR_NAMESPACE/$GHCR_REPOSITORY" >> $GITHUB_OUTPUT
else
echo "GhCR publish skipped: refusing to push to namespace '$GHCR_NAMESPACE'. Please override GHCR_* for forks." >&2
echo "ghcr_image_name=" >> $GITHUB_OUTPUT
echo "ghcr_provenance_image=" >> $GITHUB_OUTPUT
fi
echo "allow_ghcr_publish=$ALLOW_GHCR_PUBLISH" >> $GITHUB_OUTPUT
id: image id: image
- name: Determine image platforms to use - name: Determine image platforms to use
@@ -81,12 +48,12 @@ jobs:
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' || needs.set-vars.outputs.image_namespace != 'argoproj') && 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.5 go-version: 1.24.1
platforms: ${{ needs.set-vars.outputs.platforms }} platforms: ${{ needs.set-vars.outputs.platforms }}
push: false push: false
@@ -96,14 +63,14 @@ jobs:
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' || needs.set-vars.outputs.image_namespace != 'argoproj') && 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:
quay_image_name: ${{ needs.set-vars.outputs.quay_image_name }} quay_image_name: quay.io/argoproj/argocd:latest
ghcr_image_name: ${{ needs.set-vars.outputs.ghcr_image_name }} 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.5 go-version: 1.24.1
platforms: ${{ needs.set-vars.outputs.platforms }} platforms: ${{ needs.set-vars.outputs.platforms }}
push: true push: true
secrets: secrets:
@@ -114,17 +81,16 @@ jobs:
build-and-publish-provenance: # Push attestations to GHCR, latest image is polluting quay.io build-and-publish-provenance: # Push attestations to GHCR, latest image is polluting quay.io
needs: needs:
- set-vars
- build-and-publish - build-and-publish
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)
if: ${{ (github.repository == 'argoproj/argo-cd' || needs.set-vars.outputs.image_namespace != 'argoproj') && github.event_name == 'push' && needs.set-vars.outputs.allow_ghcr_publish == 'true'}} 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: ${{ needs.set-vars.outputs.ghcr_provenance_image }} image: ghcr.io/argoproj/argo-cd/argocd
digest: ${{ needs.build-and-publish.outputs.image-digest }} digest: ${{ needs.build-and-publish.outputs.image-digest }}
registry-username: ${{ github.actor }} registry-username: ${{ github.actor }}
secrets: secrets:
@@ -140,7 +106,7 @@ jobs:
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - 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 }}

View File

@@ -21,15 +21,9 @@ jobs:
pull-requests: write # for peter-evans/create-pull-request to create a PR pull-requests: write # for peter-evans/create-pull-request to create a PR
name: Automatically generate version and manifests on ${{ inputs.TARGET_BRANCH }} name: Automatically generate version and manifests on ${{ inputs.TARGET_BRANCH }}
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env:
# Calculate image names with defaults, this will be used in the make manifests-local command
# to generate the correct image name in the manifests
IMAGE_REGISTRY: ${{ vars.IMAGE_REGISTRY || 'quay.io' }}
IMAGE_NAMESPACE: ${{ vars.IMAGE_NAMESPACE || 'argoproj' }}
IMAGE_REPOSITORY: ${{ vars.IMAGE_REPOSITORY || 'argocd' }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@@ -70,7 +64,7 @@ jobs:
git stash pop git stash pop
- name: Create pull request - name: Create pull request
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0 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

@@ -11,99 +11,38 @@ permissions: {}
env: env:
# renovate: datasource=golang-version packageName=golang # renovate: datasource=golang-version packageName=golang
GOLANG_VERSION: '1.25.5' # Note: go-version must also be set in job argocd-image.with.go-version GOLANG_VERSION: '1.24.1' # Note: go-version must also be set in job argocd-image.with.go-version
jobs: jobs:
argocd-image: argocd-image:
needs: [setup-variables]
permissions: permissions:
contents: read contents: read
id-token: write # for creating OIDC tokens for signing. id-token: write # for creating OIDC tokens for signing.
packages: write # used to push images to `ghcr.io` if used. packages: write # used to push images to `ghcr.io` if used.
if: github.repository == 'argoproj/argo-cd' || needs.setup-variables.outputs.allow_fork_release == 'true' if: github.repository == 'argoproj/argo-cd'
uses: ./.github/workflows/image-reuse.yaml uses: ./.github/workflows/image-reuse.yaml
with: with:
quay_image_name: ${{ needs.setup-variables.outputs.quay_image_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.5 go-version: 1.24.1
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' || (github.repository_owner != 'argoproj' && vars.ENABLE_FORK_RELEASES == 'true' && vars.IMAGE_NAMESPACE && vars.IMAGE_NAMESPACE != 'argoproj')
runs-on: ubuntu-22.04
outputs:
is_pre_release: ${{ steps.var.outputs.is_pre_release }}
is_latest_release: ${{ steps.var.outputs.is_latest_release }}
enable_fork_releases: ${{ steps.var.outputs.enable_fork_releases }}
image_namespace: ${{ steps.var.outputs.image_namespace }}
image_repository: ${{ steps.var.outputs.image_repository }}
quay_image_name: ${{ steps.var.outputs.quay_image_name }}
provenance_image: ${{ steps.var.outputs.provenance_image }}
allow_fork_release: ${{ steps.var.outputs.allow_fork_release }}
steps:
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
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
# Calculate configuration with defaults
ENABLE_FORK_RELEASES="${{ vars.ENABLE_FORK_RELEASES || 'false' }}"
IMAGE_NAMESPACE="${{ vars.IMAGE_NAMESPACE || 'argoproj' }}"
IMAGE_REPOSITORY="${{ vars.IMAGE_REPOSITORY || 'argocd' }}"
echo "enable_fork_releases=$ENABLE_FORK_RELEASES" >> $GITHUB_OUTPUT
echo "image_namespace=$IMAGE_NAMESPACE" >> $GITHUB_OUTPUT
echo "image_repository=$IMAGE_REPOSITORY" >> $GITHUB_OUTPUT
echo "quay_image_name=quay.io/$IMAGE_NAMESPACE/$IMAGE_REPOSITORY:${{ github.ref_name }}" >> $GITHUB_OUTPUT
echo "provenance_image=quay.io/$IMAGE_NAMESPACE/$IMAGE_REPOSITORY" >> $GITHUB_OUTPUT
ALLOW_FORK_RELEASE=false
if [[ "${{ github.repository_owner }}" != "argoproj" && "$ENABLE_FORK_RELEASES" == "true" && "$IMAGE_NAMESPACE" != "argoproj" && "${{ github.ref }}" == refs/tags/* ]]; then
ALLOW_FORK_RELEASE=true
fi
echo "allow_fork_release=$ALLOW_FORK_RELEASE" >> $GITHUB_OUTPUT
argocd-image-provenance: argocd-image-provenance:
needs: [setup-variables, 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' || needs.setup-variables.outputs.allow_fork_release == 'true' 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: ${{ needs.setup-variables.outputs.provenance_image }} 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 }}
@@ -111,20 +50,18 @@ jobs:
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' || needs.setup-variables.outputs.allow_fork_release == 'true' 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@@ -133,16 +70,14 @@ jobs:
run: git fetch --force --tags run: git fetch --force --tags
- name: Setup Golang - name: Setup Golang
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.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
@@ -159,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
@@ -168,8 +103,6 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
KUBECTL_VERSION: ${{ env.KUBECTL_VERSION }} KUBECTL_VERSION: ${{ env.KUBECTL_VERSION }}
GIT_TREE_STATE: ${{ env.GIT_TREE_STATE }} GIT_TREE_STATE: ${{ env.GIT_TREE_STATE }}
# Used to determine the current repository in the goreleaser config to display correct manifest links
GORELEASER_CURRENT_REPOSITORY: ${{ github.repository }}
- name: Generate subject for provenance - name: Generate subject for provenance
id: hash id: hash
@@ -186,14 +119,14 @@ jobs:
echo "hashes=$hashes" >> $GITHUB_OUTPUT echo "hashes=$hashes" >> $GITHUB_OUTPUT
goreleaser-provenance: goreleaser-provenance:
needs: [goreleaser, setup-variables] needs: [goreleaser]
permissions: permissions:
actions: read # for detecting the Github Actions environment actions: read # for detecting the Github Actions environment
id-token: write # Needed for provenance signing and ID id-token: write # Needed for provenance signing and ID
contents: write # Needed for release uploads contents: write # Needed for release uploads
if: github.repository == 'argoproj/argo-cd' || needs.setup-variables.outputs.allow_fork_release == 'true' 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'
@@ -204,25 +137,23 @@ jobs:
needs: needs:
- argocd-image - argocd-image
- goreleaser - goreleaser
- setup-variables
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' || needs.setup-variables.outputs.allow_fork_release == 'true' 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 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@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.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
@@ -235,7 +166,7 @@ jobs:
# 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: ${{ needs.setup-variables.outputs.quay_image_name }} DOCKER_IMAGE: quay.io/argoproj/argocd:${{ github.ref_name }}
run: | run: |
yarn install --cwd ./ui yarn install --cwd ./ui
go install github.com/spdx/spdx-sbom-generator/cmd/generator@$SPDX_GEN_VERSION go install github.com/spdx/spdx-sbom-generator/cmd/generator@$SPDX_GEN_VERSION
@@ -264,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@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
@@ -272,14 +203,14 @@ jobs:
/tmp/sbom.tar.gz /tmp/sbom.tar.gz
sbom-provenance: sbom-provenance:
needs: [generate-sbom, setup-variables] needs: [generate-sbom]
permissions: permissions:
actions: read # for detecting the Github Actions environment actions: read # for detecting the Github Actions environment
id-token: write # Needed for provenance signing and ID id-token: write # Needed for provenance signing and ID
contents: write # Needed for release uploads contents: write # Needed for release uploads
if: github.repository == 'argoproj/argo-cd' || needs.setup-variables.outputs.allow_fork_release == 'true' 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'
@@ -287,20 +218,17 @@ jobs:
post-release: post-release:
needs: needs:
- setup-variables
- argocd-image - argocd-image
- goreleaser - goreleaser
- generate-sbom - generate-sbom
permissions: permissions:
contents: write # Needed to push commit to update stable tag contents: write # Needed to push commit to update stable tag
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' || needs.setup-variables.outputs.allow_fork_release == 'true' 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@@ -311,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 }}
@@ -344,7 +293,7 @@ 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@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0 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'

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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # 6.0.1
# Some codegen commands require Go to be setup
- name: Setup Golang
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
with:
# renovate: datasource=golang-version packageName=golang
go-version: 1.25.5
- name: Self-hosted Renovate
uses: renovatebot/github-action@5712c6a41dea6cdf32c72d92a763bd417e6606aa #44.0.5
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 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:a47a68ee7f9da10cd889ccce4661bc73f2c0d5a98d3d087e8bdfc0230b27964c
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,156 @@
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
- gofumpt
- goimports
- gomodguard - gomodguard
- gosimple
- govet - govet
- importas - importas
- ineffassign
- misspell - misspell
- noctx # Disabled because of https://github.com/argoproj/argo-cd/issues/21705
# - nolintlint
- perfsprint - perfsprint
- revive - revive
- staticcheck - staticcheck
- testifylint - testifylint
- thelper - thelper
- tparallel
- unparam - unparam
- unused
- usestdlibvars - usestdlibvars
- usetesting - usetesting
- whitespace - whitespace
linters-settings:
exclusions: gocritic:
disabled-checks:
- appendAssign
- assignOp # Keep it disabled for readability
- exitAfterDefer
- mapKey
- typeSwitchVar
goimports:
local-prefixes: github.com/argoproj/argo-cd/v3
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
importas:
alias:
- alias: jwtgo
pkg: github.com/golang-jwt/jwt/v5
- alias: appsv1
pkg: k8s.io/api/apps/v1
- alias: corev1
pkg: k8s.io/api/core/v1
- alias: rbacv1
pkg: k8s.io/api/rbac/v1
- alias: apierrors
pkg: k8s.io/apimachinery/pkg/api/errors
- alias: apiextensionsv1
pkg: k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1
- alias: metav1
pkg: k8s.io/apimachinery/pkg/apis/meta/v1
- alias: informersv1
pkg: k8s.io/client-go/informers/core/v1
- alias: stderrors
pkg: errors
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: rules:
- linters: - name: bool-literal-in-expr
- unparam - name: blank-imports
path: (.+)_test\.go disabled: true
- name: context-as-argument
presets: arguments:
- comments - allowTypesBefore: '*testing.T,testing.TB'
- common-false-positives - name: context-keys-type
- legacy disabled: true
- std-error-handling - name: dot-imports
disabled: true
warn-unused: true - name: duplicated-imports
- name: early-return
settings: arguments:
gocritic: - 'preserveScope'
enable-all: true - name: empty-block
# Most of these should probably be enabled one-by-one. disabled: true
disabled-checks: - name: error-naming
- appendAssign disabled: true
- appendCombine # Leave disabled, multi-line assigns can be more readable. - name: error-return
- assignOp # Leave disabled, assign operations can be more confusing than helpful. - name: error-strings
- commentedOutCode disabled: true
- deferInLoop - name: errorf
- exitAfterDefer - name: identical-branches
- hugeParam - name: if-return
- importShadow - name: increment-decrement
- paramTypeCombine # Leave disabled, there are too many failures to be worth fixing. - name: indent-error-flow
- rangeValCopy arguments:
- tooManyResultsChecker - 'preserveScope'
- unnamedResult - name: modifies-parameter
- whyNoLint - name: optimize-operands-order
- name: range
gomodguard: - name: receiver-naming
blocked: - name: redefines-builtin-id
modules: disabled: true
- github.com/golang-jwt/jwt/v4: - name: redundant-import-alias
recommendations: - name: superfluous-else
- github.com/golang-jwt/jwt/v5 arguments:
- 'preserveScope'
- github.com/imdario/mergo: - name: time-equal
recommendations: - name: time-naming
- dario.cat/mergo disabled: true
reason: '`github.com/imdario/mergo` has been renamed.' - name: unexported-return
disabled: true
- github.com/pkg/errors: - name: unnecessary-stmt
recommendations: - name: unreachable-code
- errors - name: unused-parameter
- name: use-any
govet: - name: useless-break
disable: - name: var-declaration
- fieldalignment - name: var-naming
- shadow arguments:
enable-all: true - ["ID"]
- ["VM"]
importas: - - skipPackageNameChecks: true
alias: upperCaseConst: true
- pkg: github.com/golang-jwt/jwt/v5 testifylint:
alias: jwtgo enable-all: true
disable:
- pkg: k8s.io/api/apps/v1 - go-require
alias: appsv1 usetesting:
os-mkdir-temp: false
- pkg: k8s.io/api/core/v1 run:
alias: corev1 timeout: 50m
- 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

@@ -24,8 +24,8 @@ builds:
- -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: |
@@ -66,14 +65,14 @@ release:
```shell ```shell
kubectl create namespace argocd kubectl create namespace argocd
kubectl apply -n argocd --server-side --force-conflicts -f https://raw.githubusercontent.com/{{ .Env.GORELEASER_CURRENT_REPOSITORY }}/{{.Tag}}/manifests/install.yaml kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/{{.Tag}}/manifests/install.yaml
``` ```
### HA: ### HA:
```shell ```shell
kubectl create namespace argocd kubectl create namespace argocd
kubectl apply -n argocd --server-side --force-conflicts -f https://raw.githubusercontent.com/{{ .Env.GORELEASER_CURRENT_REPOSITORY }}/{{.Tag}}/manifests/ha/install.yaml kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/{{.Tag}}/manifests/ha/install.yaml
``` ```
## Release Signatures and Provenance ## Release Signatures and Provenance
@@ -81,27 +80,26 @@ 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 ## 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) 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-v2-14-release-candidate-57a664791e2a)
## 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/{{ .Env.GORELEASER_CURRENT_REPOSITORY }}/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 +119,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,96 +1,82 @@
dir: '{{.InterfaceDir}}/mocks' # global config
filename: '{{.InterfaceName}}.go' filename: "{{.InterfaceName}}.go"
include-auto-generated: true # Needed since mockery 3.6.1 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/v3/applicationset/generators:
interfaces: interfaces:
Generator: {} Generator:
github.com/argoproj/argo-cd/v3/applicationset/services: github.com/argoproj/argo-cd/v3/applicationset/services:
interfaces: interfaces:
Repos: {} Repos:
github.com/argoproj/argo-cd/v3/applicationset/services/scm_provider: github.com/argoproj/argo-cd/v3/applicationset/services/scm_provider:
config:
dir: "applicationset/services/scm_provider/aws_codecommit/mocks"
interfaces: interfaces:
AWSCodeCommitClient: {} AWSCodeCommitClient:
AWSTaggingClient: {} AWSTaggingClient:
AzureDevOpsClientFactory: {} github.com/microsoft/azure-devops-go-api/azuredevops/git:
config:
dir: "applicationset/services/scm_provider/azure_devops/git/mocks"
interfaces:
Client:
github.com/argoproj/argo-cd/v3/applicationset/utils: github.com/argoproj/argo-cd/v3/applicationset/utils:
interfaces: interfaces:
Renderer: {} Renderer:
github.com/argoproj/argo-cd/v3/commitserver/apiclient:
interfaces:
CommitServiceClient: {}
github.com/argoproj/argo-cd/v3/commitserver/commit: github.com/argoproj/argo-cd/v3/commitserver/commit:
interfaces: interfaces:
RepoClientFactory: {} RepoClientFactory:
github.com/argoproj/argo-cd/v3/commitserver/apiclient:
interfaces:
CommitServiceClient:
Clientset:
github.com/argoproj/argo-cd/v3/controller/cache: github.com/argoproj/argo-cd/v3/controller/cache:
interfaces: interfaces:
LiveStateCache: {} 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/project:
interfaces:
ProjectServiceClient: {}
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: github.com/argoproj/argo-cd/v3/reposerver/apiclient:
interfaces: interfaces:
RepoServerServiceClient: {} RepoServerServiceClient:
RepoServerService_GenerateManifestWithFilesClient: {} RepoServerService_GenerateManifestWithFilesClient:
github.com/argoproj/argo-cd/v3/server/application: github.com/argoproj/argo-cd/v3/server/application:
interfaces: interfaces:
Broadcaster: {} Broadcaster:
github.com/argoproj/argo-cd/v3/server/extension: github.com/argoproj/argo-cd/v3/server/extension:
interfaces: interfaces:
ApplicationGetter: {} ApplicationGetter:
ExtensionMetricsRegistry: {} ExtensionMetricsRegistry:
ProjectGetter: {} ProjectGetter:
RbacEnforcer: {} RbacEnforcer:
SettingsGetter: {} SettingsGetter:
UserGetter: {} UserGetter:
github.com/argoproj/argo-cd/v3/util/db: github.com/argoproj/argo-cd/v3/util/db:
interfaces: interfaces:
ArgoDB: {} ArgoDB:
RepoCredsDB: {}
github.com/argoproj/argo-cd/v3/util/git: github.com/argoproj/argo-cd/v3/util/git:
interfaces: interfaces:
Client: {} Client:
github.com/argoproj/argo-cd/v3/util/helm: github.com/argoproj/argo-cd/v3/util/helm:
interfaces: interfaces:
Client: {} Client:
github.com/argoproj/argo-cd/v3/util/io: github.com/argoproj/argo-cd/v3/util/io:
interfaces: interfaces:
TempPaths: {} TempPaths:
github.com/argoproj/argo-cd/v3/util/notification/argocd: github.com/argoproj/argo-cd/v3/util/notification/argocd:
interfaces: interfaces:
Service: {} Service:
github.com/argoproj/argo-cd/v3/util/oci:
interfaces:
Client: {}
github.com/argoproj/argo-cd/v3/util/workloadidentity: github.com/argoproj/argo-cd/v3/util/workloadidentity:
interfaces: interfaces:
TokenProvider: {} TokenProvider:
github.com/argoproj/gitops-engine/pkg/cache: # These mocks are not currently used, but they are part of the public API of this package.
github.com/argoproj/argo-cd/v3/pkg/apiclient/session:
interfaces: interfaces:
ClusterCache: {} SessionServiceServer:
github.com/argoproj/gitops-engine/pkg/diff: SessionServiceClient:
github.com/argoproj/argo-cd/v3/pkg/apiclient/cluster:
interfaces: interfaces:
ServerSideDryRunner: {} ClusterServiceServer:
github.com/microsoft/azure-devops-go-api/azuredevops/v7/git: github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned/typed/application/v1alpha1:
config:
dir: applicationset/services/scm_provider/azure_devops/git/mocks
interfaces: interfaces:
Client: {} AppProjectInterface:
pkgname: mocks
structname: '{{.InterfaceName}}'
template-data:
unroll-variadic: true

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.10@sha256:5922638447b1e3ba114332c896a2c7288c876bb94adec923d70d58a17d2fec5e ARG BASE_IMAGE=docker.io/library/ubuntu:24.04@sha256:80dd3c3b9c6cecb9f1667e9290b3bc61b78c2678c02cbdae5f0fea92cc6734ab
#################################################################################################### ####################################################################################################
# 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.5@sha256:31c1e53dfc1cc2d269deec9c83f58729fa3c53dc9a576f6426109d1e319e9e9a AS builder FROM docker.io/library/golang:1.24.1@sha256:c5adecdb7b3f8c5ca3c88648a861882849cc8b02fed68ece31e25de88ad13418 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
@@ -85,7 +83,7 @@ WORKDIR /home/argocd
#################################################################################################### ####################################################################################################
# Argo CD UI stage # Argo CD UI stage
#################################################################################################### ####################################################################################################
FROM --platform=$BUILDPLATFORM docker.io/library/node:23.0.0@sha256:9d09fa506f5b8465c5221cbd6f980e29ae0ce9a3119e2b9bc0842e6a3f37bb59 AS argocd-ui FROM --platform=$BUILDPLATFORM docker.io/library/node:23.0.0@sha256:e643c0b70dca9704dff42e12b17f5b719dbe4f95e6392fc2dfa0c5f02ea8044d AS argocd-ui
WORKDIR /src WORKDIR /src
COPY ["ui/package.json", "ui/yarn.lock", "./"] COPY ["ui/package.json", "ui/yarn.lock", "./"]
@@ -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.5@sha256:31c1e53dfc1cc2d269deec9c83f58729fa3c53dc9a576f6426109d1e319e9e9a AS argocd-build FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.24.1@sha256:c5adecdb7b3f8c5ca3c88648a861882849cc8b02fed68ece31e25de88ad13418 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.5@sha256:31c1e53dfc1cc2d269deec9c83f58729fa3c53dc9a576f6426109d1e319e9e9a
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

@@ -3,16 +3,7 @@ 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,21 +34,10 @@ 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.12 to match https://github.com/argoproj/argo-cd/blob/master/.readthedocs.yaml # pointing to python 3.7 to match https://github.com/argoproj/argo-cd/blob/master/.readthedocs.yml
MKDOCS_DOCKER_IMAGE?=python:3.12-alpine MKDOCS_DOCKER_IMAGE?=python:3.7-alpine
MKDOCS_RUN_ARGS?= MKDOCS_RUN_ARGS?=
# Configuration for building argocd-test-tools image # Configuration for building argocd-test-tools image
@@ -76,10 +56,8 @@ ARGOCD_E2E_REDIS_PORT?=6379
ARGOCD_E2E_DEX_PORT?=5556 ARGOCD_E2E_DEX_PORT?=5556
ARGOCD_E2E_YARN_HOST?=localhost ARGOCD_E2E_YARN_HOST?=localhost
ARGOCD_E2E_DISABLE_AUTH?= ARGOCD_E2E_DISABLE_AUTH?=
ARGOCD_E2E_DIR?=/tmp/argo-e2e
ARGOCD_E2E_TEST_TIMEOUT?=90m ARGOCD_E2E_TEST_TIMEOUT?=90m
ARGOCD_E2E_RERUN_FAILS?=5
ARGOCD_IN_CI?=false ARGOCD_IN_CI?=false
ARGOCD_TEST_E2E?=true ARGOCD_TEST_E2E?=true
@@ -126,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)"
@@ -151,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)"
@@ -169,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
@@ -200,7 +174,7 @@ endif
ifneq (${GIT_TAG},) ifneq (${GIT_TAG},)
IMAGE_TAG=${GIT_TAG} IMAGE_TAG=${GIT_TAG}
override LDFLAGS += -X ${PACKAGE}.gitTag=${GIT_TAG} LDFLAGS += -X ${PACKAGE}.gitTag=${GIT_TAG}
else else
IMAGE_TAG?=latest IMAGE_TAG?=latest
endif endif
@@ -215,10 +189,6 @@ ifdef IMAGE_NAMESPACE
IMAGE_PREFIX=${IMAGE_NAMESPACE}/ IMAGE_PREFIX=${IMAGE_NAMESPACE}/
endif endif
ifndef IMAGE_REGISTRY
IMAGE_REGISTRY="quay.io"
endif
.PHONY: all .PHONY: all
all: cli image all: cli image
@@ -267,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
@@ -314,11 +280,12 @@ endif
.PHONY: manifests-local .PHONY: manifests-local
manifests-local: manifests-local:
./hack/update-manifests.sh ./hack/update-manifests.sh
.PHONY: manifests .PHONY: manifests
manifests: test-tools-image manifests: test-tools-image
$(call run-in-test-client,make manifests-local IMAGE_REGISTRY='${IMAGE_REGISTRY}' IMAGE_NAMESPACE='${IMAGE_NAMESPACE}' IMAGE_REPOSITORY='${IMAGE_REPOSITORY}' IMAGE_TAG='${IMAGE_TAG}') $(call run-in-test-client,make manifests-local IMAGE_NAMESPACE='${IMAGE_NAMESPACE}' IMAGE_TAG='${IMAGE_TAG}')
# consolidated binary for cli, util, server, repo-server, controller
# consolidated binary for cli, util, server, repo-server, controller
.PHONY: argocd-all .PHONY: argocd-all
argocd-all: clean-debug argocd-all: clean-debug
CGO_ENABLED=${CGO_FLAG} GOOS=${GOOS} GOARCH=${GOARCH} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${BIN_NAME} ./cmd CGO_ENABLED=${CGO_FLAG} GOOS=${GOOS} GOARCH=${GOARCH} GODEBUG="tarinsecurepath=0,zipinsecurepath=0" go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/${BIN_NAME} ./cmd
@@ -463,7 +430,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=$(ARGOCD_E2E_RERUN_FAILS) 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_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"
# 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
@@ -487,13 +454,13 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
kubectl create ns argocd-e2e-external || true kubectl create ns argocd-e2e-external || true
kubectl create ns argocd-e2e-external-2 || true kubectl create ns argocd-e2e-external-2 || true
kubectl config set-context --current --namespace=argocd-e2e kubectl config set-context --current --namespace=argocd-e2e
kustomize build test/manifests/base | kubectl apply --server-side --force-conflicts -f - kustomize build test/manifests/base | kubectl apply -f -
kubectl apply -f https://raw.githubusercontent.com/open-cluster-management/api/a6845f2ebcb186ec26b832f60c988537a58f3859/cluster/v1alpha1/0000_04_clusters.open-cluster-management.io_placementdecisions.crd.yaml kubectl apply -f https://raw.githubusercontent.com/open-cluster-management/api/a6845f2ebcb186ec26b832f60c988537a58f3859/cluster/v1alpha1/0000_04_clusters.open-cluster-management.io_placementdecisions.crd.yaml
# Create GPG keys and source directories # Create GPG keys and source directories
if test -d $(ARGOCD_E2E_DIR)/app/config/gpg; then rm -rf $(ARGOCD_E2E_DIR)/app/config/gpg/*; fi if test -d /tmp/argo-e2e/app/config/gpg; then rm -rf /tmp/argo-e2e/app/config/gpg/*; fi
mkdir -p $(ARGOCD_E2E_DIR)/app/config/gpg/keys && chmod 0700 $(ARGOCD_E2E_DIR)/app/config/gpg/keys mkdir -p /tmp/argo-e2e/app/config/gpg/keys && chmod 0700 /tmp/argo-e2e/app/config/gpg/keys
mkdir -p $(ARGOCD_E2E_DIR)/app/config/gpg/source && chmod 0700 $(ARGOCD_E2E_DIR)/app/config/gpg/source mkdir -p /tmp/argo-e2e/app/config/gpg/source && chmod 0700 /tmp/argo-e2e/app/config/gpg/source
mkdir -p $(ARGOCD_E2E_DIR)/app/config/plugin && chmod 0700 $(ARGOCD_E2E_DIR)/app/config/plugin mkdir -p /tmp/argo-e2e/app/config/plugin && chmod 0700 /tmp/argo-e2e/app/config/plugin
# create folders to hold go coverage results for each component # create folders to hold go coverage results for each component
mkdir -p /tmp/coverage/app-controller mkdir -p /tmp/coverage/app-controller
mkdir -p /tmp/coverage/api-server mkdir -p /tmp/coverage/api-server
@@ -502,15 +469,13 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
mkdir -p /tmp/coverage/notification mkdir -p /tmp/coverage/notification
mkdir -p /tmp/coverage/commit-server mkdir -p /tmp/coverage/commit-server
# set paths for locally managed ssh known hosts and tls certs data # set paths for locally managed ssh known hosts and tls certs data
ARGOCD_E2E_DIR=$(ARGOCD_E2E_DIR) \ ARGOCD_SSH_DATA_PATH=/tmp/argo-e2e/app/config/ssh \
ARGOCD_SSH_DATA_PATH=$(ARGOCD_E2E_DIR)/app/config/ssh \ ARGOCD_TLS_DATA_PATH=/tmp/argo-e2e/app/config/tls \
ARGOCD_TLS_DATA_PATH=$(ARGOCD_E2E_DIR)/app/config/tls \ ARGOCD_GPG_DATA_PATH=/tmp/argo-e2e/app/config/gpg/source \
ARGOCD_GPG_DATA_PATH=$(ARGOCD_E2E_DIR)/app/config/gpg/source \ ARGOCD_GNUPGHOME=/tmp/argo-e2e/app/config/gpg/keys \
ARGOCD_GNUPGHOME=$(ARGOCD_E2E_DIR)/app/config/gpg/keys \
ARGOCD_GPG_ENABLED=$(ARGOCD_GPG_ENABLED) \ ARGOCD_GPG_ENABLED=$(ARGOCD_GPG_ENABLED) \
ARGOCD_PLUGINCONFIGFILEPATH=$(ARGOCD_E2E_DIR)/app/config/plugin \ ARGOCD_PLUGINCONFIGFILEPATH=/tmp/argo-e2e/app/config/plugin \
ARGOCD_PLUGINSOCKFILEPATH=$(ARGOCD_E2E_DIR)/app/config/plugin \ ARGOCD_PLUGINSOCKFILEPATH=/tmp/argo-e2e/app/config/plugin \
ARGOCD_GIT_CONFIG=$(PWD)/test/e2e/fixture/gitconfig \
ARGOCD_E2E_DISABLE_AUTH=false \ ARGOCD_E2E_DISABLE_AUTH=false \
ARGOCD_ZJWT_FEATURE_FLAG=always \ ARGOCD_ZJWT_FEATURE_FLAG=always \
ARGOCD_IN_CI=$(ARGOCD_IN_CI) \ ARGOCD_IN_CI=$(ARGOCD_IN_CI) \
@@ -521,7 +486,6 @@ start-e2e-local: mod-vendor-local dep-ui-local cli-local
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_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
@@ -587,7 +551,7 @@ build-docs-local:
.PHONY: build-docs .PHONY: build-docs
build-docs: build-docs:
$(DOCKER) run ${MKDOCS_RUN_ARGS} --rm -it -v ${CURRENT_DIR}:/docs -w /docs --entrypoint "" ${MKDOCS_DOCKER_IMAGE} sh -c 'pip install -r docs/requirements.txt; mkdocs build' $(DOCKER) run ${MKDOCS_RUN_ARGS} --rm -it -v ${CURRENT_DIR}:/docs -w /docs --entrypoint "" ${MKDOCS_DOCKER_IMAGE} sh -c 'pip install mkdocs; pip install $$(mkdocs get-deps); mkdocs build'
.PHONY: serve-docs-local .PHONY: serve-docs-local
serve-docs-local: serve-docs-local:
@@ -595,7 +559,7 @@ serve-docs-local:
.PHONY: serve-docs .PHONY: serve-docs
serve-docs: serve-docs:
$(DOCKER) run ${MKDOCS_RUN_ARGS} --rm -it -p 8000:8000 -v ${CURRENT_DIR}:/docs -w /docs --entrypoint "" ${MKDOCS_DOCKER_IMAGE} sh -c 'pip install -r docs/requirements.txt; mkdocs serve -a $$(ip route get 1 | awk '\''{print $$7}'\''):8000' $(DOCKER) run ${MKDOCS_RUN_ARGS} --rm -it -p 8000:8000 -v ${CURRENT_DIR}:/docs -w /docs --entrypoint "" ${MKDOCS_DOCKER_IMAGE} sh -c 'pip install mkdocs; pip install $$(mkdocs get-deps); mkdocs serve -a $$(ip route get 1 | awk '\''{print $$7}'\''):8000'
# Verify that kubectl can connect to your K8s cluster from Docker # Verify that kubectl can connect to your K8s cluster from Docker
.PHONY: verify-kube-connect .PHONY: verify-kube-connect
@@ -620,13 +584,11 @@ 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

View File

@@ -2,13 +2,12 @@ controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run
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:-''} --hydrator-enabled=${ARGOCD_HYDRATOR_ENABLED:='false'}"
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/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"
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 "export PATH=./dist:\$PATH && [ -n \"\$ARGOCD_GIT_CONFIG\" ] && export GIT_CONFIG_GLOBAL=\$ARGOCD_GIT_CONFIG && export GIT_CONFIG_NOSYSTEM=1; 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}"
commit-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/commit-server} FORCE_LOG_COLORS=1 ARGOCD_BINARY_NAME=argocd-commit-server $COMMAND --loglevel debug --port ${ARGOCD_E2E_COMMITSERVER_PORT:-8086}" commit-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/commit-server} FORCE_LOG_COLORS=1 ARGOCD_BINARY_NAME=argocd-commit-server $COMMAND --loglevel debug --port ${ARGOCD_E2E_COMMITSERVER_PORT:-8086}"
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:

314
Tiltfile
View File

@@ -1,314 +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'
],
resource_deps=['build']
)
# 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'
],
resource_deps=['build']
)
# 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',
],
resource_deps=['build']
)
# 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'
],
resource_deps=['build']
)
# 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',
],
resource_deps=['build']
)
# 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',
],
resource_deps=['build']
)
# 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',
],
resource_deps=['build']
)
# 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',
],
resource_deps=['build']
)
# ui dependencies
local_resource(
'node-modules',
'yarn',
dir='ui',
deps = [
'ui/package.json',
'ui/yarn.lock',
],
allow_parallel=True,
)
# 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',
],
resource_deps=['node-modules'],
)
# 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,
resource_deps=['node-modules'],
)
local_resource(
'vendor',
'go mod vendor',
deps = [
'go.mod',
'go.sum',
],
allow_parallel=True,
)

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/)
@@ -31,9 +29,7 @@ Currently, the following organizations are **officially** using Argo CD:
1. [ANSTO - Australian Synchrotron](https://www.synchrotron.org.au/) 1. [ANSTO - Australian Synchrotron](https://www.synchrotron.org.au/)
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. [Arcadia](https://www.arcadia.io)
1. [Arctiq Inc.](https://www.arctiq.ca) 1. [Arctiq Inc.](https://www.arctiq.ca)
1. [Artemis Health by Nomi Health](https://www.artemishealth.com/)
1. [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/)
@@ -43,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/)
@@ -64,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)
@@ -74,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/)
@@ -87,7 +78,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Codefresh](https://www.codefresh.io/) 1. [Codefresh](https://www.codefresh.io/)
1. [Codility](https://www.codility.com/) 1. [Codility](https://www.codility.com/)
1. [Cognizant](https://www.cognizant.com/) 1. [Cognizant](https://www.cognizant.com/)
1. [Collins Aerospace](https://www.collinsaerospace.com/)
1. [Commonbond](https://commonbond.co/) 1. [Commonbond](https://commonbond.co/)
1. [Compatio.AI](https://compatio.ai/) 1. [Compatio.AI](https://compatio.ai/)
1. [Contlo](https://contlo.com/) 1. [Contlo](https://contlo.com/)
@@ -101,18 +91,13 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Datarisk](https://www.datarisk.io/) 1. [Datarisk](https://www.datarisk.io/)
1. [Daydream](https://daydream.ing) 1. [Daydream](https://daydream.ing)
1. [Deloitte](https://www.deloitte.com/) 1. [Deloitte](https://www.deloitte.com/)
1. [Dematic](https://www.dematic.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. [Divar](https://divar.ir)
1. [Divistant](https://divistant.com) 1. [Divistant](https://divistant.com)
2. [DocNetwork](https://docnetwork.org/)
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)
@@ -124,8 +109,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. [Expedia](https://www.expedia.com)
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/)
@@ -170,11 +153,9 @@ Currently, the following organizations are **officially** using Argo CD:
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)
@@ -184,9 +165,7 @@ 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. [IQVIA](https://www.iqvia.com/)
1. [Jellysmack](https://www.jellysmack.com) 1. [Jellysmack](https://www.jellysmack.com)
1. [Joblift](https://joblift.com/) 1. [Joblift](https://joblift.com/)
1. [JovianX](https://www.jovianx.com/) 1. [JovianX](https://www.jovianx.com/)
@@ -238,7 +217,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [mixi Group](https://mixi.co.jp/) 1. [mixi Group](https://mixi.co.jp/)
1. [Moengage](https://www.moengage.com/) 1. [Moengage](https://www.moengage.com/)
1. [Money Forward](https://corp.moneyforward.com/en/) 1. [Money Forward](https://corp.moneyforward.com/en/)
1. [MongoDB](https://www.mongodb.com/)
1. [MOO Print](https://www.moo.com/) 1. [MOO Print](https://www.moo.com/)
1. [Mozilla](https://www.mozilla.org) 1. [Mozilla](https://www.mozilla.org)
1. [MTN Group](https://www.mtn.com/) 1. [MTN Group](https://www.mtn.com/)
@@ -281,7 +259,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)
@@ -317,8 +294,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. [RISK IDENT](https://riskident.com/)
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)
@@ -329,20 +305,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)
@@ -351,7 +323,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/)
@@ -364,13 +335,11 @@ 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. [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/)
@@ -385,7 +354,6 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Ticketmaster](https://ticketmaster.com) 1. [Ticketmaster](https://ticketmaster.com)
1. [Tiger Analytics](https://www.tigeranalytics.com/) 1. [Tiger Analytics](https://www.tigeranalytics.com/)
1. [Tigera](https://www.tigera.io/) 1. [Tigera](https://www.tigera.io/)
1. [Topicus.Education](https://topicus.nl/en/sectors/education)
1. [Toss](https://toss.im/en) 1. [Toss](https://toss.im/en)
1. [Trendyol](https://www.trendyol.com/) 1. [Trendyol](https://www.trendyol.com/)
1. [tru.ID](https://tru.id) 1. [tru.ID](https://tru.id)
@@ -413,7 +381,6 @@ Currently, the following organizations are **officially** using Argo CD:
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/)
@@ -429,8 +396,8 @@ Currently, the following organizations are **officially** using Argo CD:
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.2 3.0.0

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,6 @@ 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/applicationset/utils"
"github.com/argoproj/argo-cd/v3/common" "github.com/argoproj/argo-cd/v3/common"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
) )
@@ -23,9 +22,8 @@ import (
// requeue any related ApplicationSets. // requeue any related ApplicationSets.
type clusterSecretEventHandler struct { type clusterSecretEventHandler struct {
// handler.EnqueueRequestForOwner // handler.EnqueueRequestForOwner
Log log.FieldLogger Log log.FieldLogger
Client client.Client Client client.Client
ApplicationSetNamespaces []string
} }
func (h *clusterSecretEventHandler) Create(ctx context.Context, e event.CreateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { func (h *clusterSecretEventHandler) Create(ctx context.Context, e event.CreateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
@@ -70,10 +68,6 @@ func (h *clusterSecretEventHandler) queueRelatedAppGenerators(ctx context.Contex
h.Log.WithField("count", len(appSetList.Items)).Info("listed ApplicationSets") h.Log.WithField("count", len(appSetList.Items)).Info("listed ApplicationSets")
for _, appSet := range appSetList.Items { for _, appSet := range appSetList.Items {
if !utils.IsNamespaceAllowed(h.ApplicationSetNamespaces, appSet.GetNamespace()) {
// Ignore it as not part of the allowed list of namespaces in which to watch Appsets
continue
}
foundClusterGenerator := false foundClusterGenerator := false
for _, generator := range appSet.Spec.Generators { for _, generator := range appSet.Spec.Generators {
if generator.Clusters != nil { if generator.Clusters != nil {

View File

@@ -20,15 +20,6 @@ import (
argov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argov1alpha1 "github.com/argoproj/argo-cd/v3/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)
@@ -137,7 +128,7 @@ func TestClusterEventHandler(t *testing.T) {
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "my-app-set", Name: "my-app-set",
Namespace: "argocd", Namespace: "another-namespace",
}, },
Spec: argov1alpha1.ApplicationSetSpec{ Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{ Generators: []argov1alpha1.ApplicationSetGenerator{
@@ -171,37 +162,9 @@ func TestClusterEventHandler(t *testing.T) {
}, },
}, },
expectedRequests: []reconcile.Request{ expectedRequests: []reconcile.Request{
{NamespacedName: types.NamespacedName{Namespace: "argocd", Name: "my-app-set"}}, {NamespacedName: types.NamespacedName{Namespace: "another-namespace", Name: "my-app-set"}},
}, },
}, },
{
name: "cluster generators in other namespaces should not match",
items: []argov1alpha1.ApplicationSet{
{
ObjectMeta: metav1.ObjectMeta{
Name: "my-app-set",
Namespace: "my-namespace-not-allowed",
},
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{
{
Clusters: &argov1alpha1.ClusterGenerator{},
},
},
},
},
},
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "argocd",
Name: "my-secret",
Labels: map[string]string{
argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
},
},
},
expectedRequests: []reconcile.Request{},
},
{ {
name: "non-argo cd secret should not match", name: "non-argo cd secret should not match",
items: []argov1alpha1.ApplicationSet{ items: []argov1alpha1.ApplicationSet{
@@ -580,9 +543,8 @@ func TestClusterEventHandler(t *testing.T) {
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithLists(&appSetList).Build() fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithLists(&appSetList).Build()
handler := &clusterSecretEventHandler{ handler := &clusterSecretEventHandler{
Client: fakeClient, Client: fakeClient,
Log: log.WithField("type", "createSecretEventHandler"), Log: log.WithField("type", "createSecretEventHandler"),
ApplicationSetNamespaces: []string{"argocd"},
} }
mockAddRateLimitingInterface := mockAddRateLimitingInterface{} mockAddRateLimitingInterface := mockAddRateLimitingInterface{}
@@ -594,6 +556,15 @@ func TestClusterEventHandler(t *testing.T) {
} }
} }
// 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

@@ -56,7 +56,7 @@ 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(ctx, k8sClient, appClientset, "argocd"),
@@ -129,7 +129,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 +144,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 +154,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

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

@@ -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{
@@ -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{
@@ -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,6 +7,8 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v3/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"
@@ -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(ctx context.Context, c client.Client, 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
} }
@@ -53,11 +59,11 @@ func (g *ClusterGenerator) GetTemplate(appSetGenerator *argoappsetv1alpha1.Appli
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]any, 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
@@ -79,10 +85,14 @@ 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]any{}
logCtx.Debugf("Using flat mode = %t for cluster generator", paramHolder.isFlatMode)
secretsFound := []corev1.Secret{} secretsFound := []corev1.Secret{}
isFlatMode := appSetGenerator.Clusters.FlatList
logCtx.Debugf("Using flat mode = %t for cluster generator", isFlatMode)
clustersParams := make([]map[string]any, 0)
for _, cluster := range clustersFromArgoCD { for _, cluster := range clustersFromArgoCD {
// 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.
@@ -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]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.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["metadata.annotations."+key] = value
}
for key, value := range cluster.ObjectMeta.Labels {
params["metadata.labels."+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]any{
"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

@@ -10,8 +10,9 @@ 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/v3/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"
@@ -25,18 +26,22 @@ 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
} }
@@ -57,12 +62,12 @@ func (g *DuckTypeGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Appl
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]any, 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 +97,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, errors.New("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, errors.New("There is a problem with the definition of the ClusterDecisionResource generator")
} }
// Split up the apiVersion // Split up the apiVersion
@@ -131,51 +136,18 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
// 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)
if len(clusterDecisions) == 0 {
log.Warningf("clusterDecisionResource status.%s missing", statusListKey)
return nil, nil
}
res := []map[string]any{} 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{} clusterDecisions := []any{}
// Build the decision slice // Build the decision slice
@@ -192,38 +164,60 @@ func buildClusterDecisions(duckResources *unstructured.UnstructuredList, statusL
clusterDecisions = append(clusterDecisions, duckResource.Object["status"].(map[string]any)[statusListKey].([]any)...) clusterDecisions = append(clusterDecisions, duckResource.Object["status"].(map[string]any)[statusListKey].([]any)...)
} }
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 { if len(clusterDecisions) == 0 {
log.Infof("cluster: %v", cluster) log.Warningf("clusterDecisionResource status.%s missing", statusListKey)
matchValue := cluster.(map[string]any)[matchKey] return nil, nil
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
} }
for _, cluster := range clusterDecisions {
// generated instance of cluster params
params := map[string]any{}
strMatchValue := matchValue.(string) log.Infof("cluster: %v", cluster)
log.WithField(matchKey, strMatchValue).Debug("validate against ArgoCD") matchValue := cluster.(map[string]any)[matchKey]
if matchValue == nil || matchValue.(string) == "" {
for _, argoCluster := range clustersFromArgoCD { log.Warningf("matchKey=%v not found in \"%v\" list: %v\n", matchKey, statusListKey, cluster.(map[string]any))
if argoCluster.Name == strMatchValue { continue
log.WithField(matchKey, argoCluster.Name).Info("matched cluster in ArgoCD")
return &argoCluster
} }
}
log.WithField(matchKey, strMatchValue).Warning("unmatched cluster in ArgoCD") strMatchValue := matchValue.(string)
return nil log.WithField(matchKey, strMatchValue).Debug("validate against ArgoCD")
}
func collectParams(appSet *argoprojiov1alpha1.ApplicationSet, params map[string]any, key string, value string) { found := false
if appSet.Spec.GoTemplate {
if params["values"] == nil { for _, argoCluster := range clustersFromArgoCD {
params["values"] = map[string]string{} 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
}
} }
params["values"].(map[string]string)[key] = value
} else { if !found {
params["values."+key] = value log.WithField(matchKey, strMatchValue).Warning("unmatched cluster in ArgoCD")
continue
}
for key, value := range cluster.(map[string]any) {
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["values."+key] = value
}
}
res = append(res, params)
} }
return res, nil
} }

View File

@@ -159,7 +159,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
resource: duckType, resource: duckType,
values: nil, values: nil,
expected: []map[string]any{}, expected: []map[string]any{},
expectedError: errors.New("there is a problem with the definition of the ClusterDecisionResource generator"), expectedError: errors.New("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.
{ {
@@ -270,7 +270,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: errors.New("There is a problem with the definition of the ClusterDecisionResource generator"),
}, },
} }
@@ -455,7 +455,7 @@ func TestGenerateParamsForDuckTypeGoTemplate(t *testing.T) {
resource: duckType, resource: duckType,
values: nil, values: nil,
expected: []map[string]any{}, expected: []map[string]any{},
expectedError: errors.New("there is a problem with the definition of the ClusterDecisionResource generator"), expectedError: errors.New("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.
{ {
@@ -566,7 +566,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: errors.New("There is a problem with the definition of the ClusterDecisionResource generator"),
}, },
} }

View File

@@ -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(context.Background(), fakeClient, 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(),
} }
@@ -551,7 +551,7 @@ func TestInterpolateGeneratorError(t *testing.T) {
}, },
useGoTemplate: true, useGoTemplate: true,
goTemplateOptions: []string{}, goTemplateOptions: []string{},
}, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: "failed to replace parameters in generator: failed to execute go template {{ index .rmap (default .override .test) }}: template: base:1:3: executing \"base\" at <index .rmap (default .override .test)>: error calling index: index of untyped nil"}, }, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: "failed to replace parameters in generator: failed to execute go template {{ index .rmap (default .override .test) }}: template: :1:3: executing \"\" at <index .rmap (default .override .test)>: error calling index: index of untyped nil"},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@@ -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.
// 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) { 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,15 +67,14 @@ 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
@@ -101,7 +93,7 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
case len(appSetGenerator.Git.Files) != 0: case len(appSetGenerator.Git.Files) != 0:
res, err = g.generateParamsForGitFiles(appSetGenerator, noRevisionCache, verifyCommit, appSet.Spec.GoTemplate, project, appSet.Spec.GoTemplateOptions) res, err = g.generateParamsForGitFiles(appSetGenerator, noRevisionCache, verifyCommit, appSet.Spec.GoTemplate, project, appSet.Spec.GoTemplateOptions)
default: default:
return nil, ErrEmptyAppSetGenerator return nil, EmptyAppSetGeneratorError
} }
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,10 +102,8 @@ 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.
// It fetches all directories from the given Git repository and revision, optionally using a revision cache and verifying commits.
// It then filters the directories based on the generator's configuration and renders parameters for the resulting applications
func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit, useGoTemplate bool, project string, goTemplateOptions []string) ([]map[string]any, error) { func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit, useGoTemplate bool, project string, goTemplateOptions []string) ([]map[string]any, error) {
// Directories, not files
allPaths, err := g.repos.GetDirectories(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, project, noRevisionCache, verifyCommit) 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,103 +127,57 @@ func (g *GitGenerator) generateParamsForGitDirectories(appSetGenerator *argoproj
return res, nil return res, nil
} }
// generateParamsForGitFiles generates parameters for an ApplicationSet using a file-based Git generator.
// It retrieves and processes specified files from the Git repository, supporting both YAML and JSON formats,
// and returns a list of parameter maps extracted from the content.
func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit, useGoTemplate bool, project string, goTemplateOptions []string) ([]map[string]any, error) { func (g *GitGenerator) generateParamsForGitFiles(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, noRevisionCache, verifyCommit, useGoTemplate bool, project string, goTemplateOptions []string) ([]map[string]any, error) {
// fileContentMap maps absolute file paths to their byte content // Get all files that match the requested path string, removing duplicates
fileContentMap := make(map[string][]byte) allFiles := make(map[string][]byte)
var includePatterns []string for _, requestedPath := range appSetGenerator.Git.Files {
var excludePatterns []string files, err := g.repos.GetFiles(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision, project, requestedPath.Path, noRevisionCache, verifyCommit)
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]any{}
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.
// The file can contain a single YAML/JSON object or an array of such objects. Depending on the useGoTemplate flag,
// 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) { 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{} 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]any)
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]any{})
} }
res := []map[string]any{} res := []map[string]any{}
@@ -277,7 +221,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 +238,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,9 +266,6 @@ func (g *GitGenerator) filterApps(directories []argoprojiov1alpha1.GitDirectoryG
return res return res
} }
// generateParamsFromApps generates a list of parameter maps based on the given app paths.
// Each app path is converted into a parameter object with path metadata (basename, segments, etc.).
// 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) { func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, useGoTemplate bool, goTemplateOptions []string) ([]map[string]any, error) {
res := make([]map[string]any, len(requestedApps)) res := make([]map[string]any, len(requestedApps))
for i, a := range requestedApps { for i, a := range requestedApps {
@@ -352,7 +291,7 @@ func (g *GitGenerator) generateParamsFromApps(requestedApps []string, appSetGene
params[pathParamName+".basename"] = path.Base(a) params[pathParamName+".basename"] = path.Base(a)
params[pathParamName+".basenameNormalized"] = utils.SanitizeName(path.Base(a)) params[pathParamName+".basenameNormalized"] = utils.SanitizeName(path.Base(a))
for k, v := range strings.Split(params[pathParamName].(string), "/") { for k, v := range strings.Split(params[pathParamName].(string), "/") {
if v != "" { if len(v) > 0 {
params[pathParamName+"["+strconv.Itoa(k)+"]"] = v params[pathParamName+"["+strconv.Itoa(k)+"]"] = v
} }
} }
@@ -369,7 +308,6 @@ 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 { func resolveProjectName(project string) string {
if strings.Contains(project, "{{") { if strings.Contains(project, "{{") {
return "" return ""

File diff suppressed because it is too large Load Diff

View File

@@ -27,15 +27,15 @@ type Generator interface {
} }
var ( var (
ErrEmptyAppSetGenerator = errors.New("ApplicationSet is empty") EmptyAppSetGeneratorError = errors.New("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

@@ -31,11 +31,11 @@ func (g *ListGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Applicat
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]any, 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]any, len(appSetGenerator.List.Elements))
@@ -77,7 +77,7 @@ 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]any
err := yaml.Unmarshal([]byte(appSetGenerator.List.ElementsYaml), &yamlElements) err := yaml.Unmarshal([]byte(appSetGenerator.List.ElementsYaml), &yamlElements)
if err != nil { if err != nil {

View File

@@ -34,7 +34,7 @@ func NewMatrixGenerator(supportedGenerators map[string]Generator) Generator {
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]any, 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 {
@@ -71,7 +71,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))
} }
} }
} }

View File

@@ -12,12 +12,12 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/argoproj/argo-cd/v3/applicationset/services/mocks"
"github.com/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"
servicesMocks "github.com/argoproj/argo-cd/v3/applicationset/services/mocks"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
) )
@@ -136,7 +136,7 @@ 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 := &v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "set", Name: "set",
@@ -149,7 +149,7 @@ func TestMatrixGenerate(t *testing.T) {
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]any{
{ {
"path": "app1", "path": "app1",
"path.basename": "app1", "path.basename": "app1",
@@ -162,7 +162,7 @@ func TestMatrixGenerate(t *testing.T) {
}, },
}, nil) }, nil)
genMock.EXPECT().GetTemplate(&gitGeneratorSpec). genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&v1alpha1.ApplicationSetTemplate{}) Return(&v1alpha1.ApplicationSetTemplate{})
} }
@@ -343,7 +343,7 @@ 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 := &v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "set", Name: "set",
@@ -358,7 +358,7 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
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]any{
{ {
"path": map[string]string{ "path": map[string]string{
"path": "app1", "path": "app1",
@@ -375,7 +375,7 @@ func TestMatrixGenerateGoTemplate(t *testing.T) {
}, },
}, nil) }, nil)
genMock.EXPECT().GetTemplate(&gitGeneratorSpec). genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&v1alpha1.ApplicationSetTemplate{}) Return(&v1alpha1.ApplicationSetTemplate{})
} }
@@ -507,7 +507,7 @@ 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 := v1alpha1.ApplicationSetGenerator{
@@ -517,7 +517,7 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
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(
@@ -634,7 +634,7 @@ 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 := &v1alpha1.ApplicationSet{}
appClientset := kubefake.NewSimpleClientset(runtimeClusters...) appClientset := kubefake.NewSimpleClientset(runtimeClusters...)
@@ -650,7 +650,7 @@ func TestInterpolatedMatrixGenerate(t *testing.T) {
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]any{
{ {
"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,7 +662,7 @@ 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(&v1alpha1.ApplicationSetTemplate{})
} }
matrixGenerator := NewMatrixGenerator( matrixGenerator := NewMatrixGenerator(
@@ -813,7 +813,7 @@ 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 := &v1alpha1.ApplicationSet{
Spec: v1alpha1.ApplicationSetSpec{ Spec: v1alpha1.ApplicationSetSpec{
GoTemplate: true, GoTemplate: true,
@@ -833,7 +833,7 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
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]any{
{ {
"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,7 +849,7 @@ func TestInterpolatedMatrixGenerateGoTemplate(t *testing.T) {
}, },
}, },
}, nil) }, nil)
genMock.EXPECT().GetTemplate(&gitGeneratorSpec). genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&v1alpha1.ApplicationSetTemplate{}) Return(&v1alpha1.ApplicationSetTemplate{})
} }
matrixGenerator := NewMatrixGenerator( matrixGenerator := NewMatrixGenerator(
@@ -969,7 +969,7 @@ 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 := &v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "set", Name: "set",
@@ -984,7 +984,7 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
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]any{
"bar": []any{ "bar": []any{
map[string]any{ map[string]any{
@@ -1009,7 +1009,7 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
}, },
}, },
}}, nil) }}, nil)
genMock.EXPECT().GetTemplate(&gitGeneratorSpec). genMock.On("GetTemplate", &gitGeneratorSpec).
Return(&v1alpha1.ApplicationSetTemplate{}) Return(&v1alpha1.ApplicationSetTemplate{})
} }
@@ -1037,6 +1037,28 @@ func TestMatrixGenerateListElementsYaml(t *testing.T) {
} }
} }
type generatorMock struct {
mock.Mock
}
func (g *generatorMock) GetTemplate(appSetGenerator *v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate {
args := g.Called(appSetGenerator)
return args.Get(0).(*v1alpha1.ApplicationSetTemplate)
}
func (g *generatorMock) GenerateParams(appSetGenerator *v1alpha1.ApplicationSetGenerator, appSet *v1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) {
args := g.Called(appSetGenerator, appSet)
return args.Get(0).([]map[string]any), args.Error(1)
}
func (g *generatorMock) GetRequeueAfter(appSetGenerator *v1alpha1.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,11 +1072,11 @@ 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]any{
{"some": "value"}, {"some": "value"},
}, nil) }, nil)
listGeneratorMock.EXPECT().GetTemplate(mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator")).Return(&v1alpha1.ApplicationSetTemplate{}) listGeneratorMock.On("GetTemplate", mock.AnythingOfType("*v1alpha1.ApplicationSetGenerator")).Return(&v1alpha1.ApplicationSetTemplate{})
gitGeneratorSpec := &v1alpha1.GitGenerator{ gitGeneratorSpec := &v1alpha1.GitGenerator{
RepoURL: "https://git.example.com", RepoURL: "https://git.example.com",
@@ -1063,10 +1085,10 @@ func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
}, },
} }
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, 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{

View File

@@ -4,12 +4,12 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"maps"
"time" "time"
"dario.cat/mergo" "dario.cat/mergo"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/argoproj/argo-cd/v3/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
) )
@@ -52,7 +52,7 @@ 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]any, 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,8 +83,11 @@ 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)
} }
} }
} }

View File

@@ -50,8 +50,6 @@ func listOfMapsToSet(maps []map[string]any) (map[string]bool, error) {
} }
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
@@ -212,8 +210,6 @@ 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

View File

@@ -1,17 +1,90 @@
// Code generated by mockery; DO NOT EDIT. // Code generated by mockery v2.52.4. 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/v3/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

@@ -10,6 +10,7 @@ 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/v3/pkg/apis/application/v1alpha1"
@@ -19,19 +20,23 @@ import (
) )
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(ctx context.Context, client client.Client, 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,7 +49,7 @@ 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 {
@@ -53,11 +58,11 @@ func (g *PluginGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Applic
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]any, 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()

View File

@@ -14,6 +14,8 @@ import (
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" 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"
@@ -626,6 +628,8 @@ func TestPluginGenerateParams(t *testing.T) {
}, },
} }
ctx := t.Context()
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(ctx, fakeClientWithCache, fakeClient, "default")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{ applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{

View File

@@ -4,23 +4,22 @@ import (
"context" "context"
"errors" "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/v3/applicationset/services/pull_request" pullrequest "github.com/argoproj/argo-cd/v3/applicationset/services/pull_request"
"github.com/argoproj/argo-cd/v3/applicationset/utils" "github.com/argoproj/argo-cd/v3/applicationset/utils"
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/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,11 +44,7 @@ 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 {
@@ -58,11 +53,11 @@ func (g *PullRequestGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.A
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]any, 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 +67,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]any, 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
@@ -107,7 +97,7 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
} }
paramMap := map[string]any{ paramMap := map[string]any{
"number": strconv.FormatInt(pull.Number, 10), "number": strconv.Itoa(pull.Number),
"title": pull.Title, "title": pull.Title,
"branch": pull.Branch, "branch": pull.Branch,
"branch_slug": slug.Make(pull.Branch), "branch_slug": slug.Make(pull.Branch),
@@ -119,15 +109,15 @@ func (g *PullRequestGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
"author": pull.Author, "author": pull.Author,
} }
// PR lables will only be supported for Go Template appsets, since fasttemplate will be deprecated.
if applicationSetInfo != nil && applicationSetInfo.Spec.GoTemplate {
paramMap["labels"] = pull.Labels
}
err := appendTemplatedValues(appSetGenerator.PullRequest.Values, paramMap, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions) err := appendTemplatedValues(appSetGenerator.PullRequest.Values, paramMap, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to append templated values: %w", err) return nil, fmt.Errorf("failed to append templated values: %w", err)
} }
// PR lables will only be supported for Go Template appsets, since fasttemplate will be deprecated.
if applicationSetInfo != nil && applicationSetInfo.Spec.GoTemplate {
paramMap["labels"] = pull.Labels
}
params = append(params, paramMap) params = append(params, paramMap)
} }
return params, nil return params, nil
@@ -167,8 +157,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(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
@@ -224,28 +213,13 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
} }
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)
} }
return pullrequest.NewGithubAppService(*auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
if g.enableGitHubAPIMetrics {
return pullrequest.NewGithubAppService(ctx, *auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels, httpClient)
}
return pullrequest.NewGithubAppService(ctx, *auth, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
} }
// always default to token, even if not set (public access) // always default to token, even if not set (public access)
@@ -253,9 +227,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)
} }
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) return pullrequest.NewGithubService(token, cfg.API, cfg.Owner, cfg.Repo, cfg.Labels)
} }

View File

@@ -16,12 +16,11 @@ import (
func TestPullRequestGithubGenerateParams(t *testing.T) { func TestPullRequestGithubGenerateParams(t *testing.T) {
ctx := t.Context() ctx := t.Context()
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 values map[string]string
expected []map[string]any 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) {
@@ -172,30 +171,6 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
expected: nil, expected: nil,
expectedErr: errors.New("error listing repos: fake error"), expectedErr: errors.New("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) {
return pullrequest.NewFakeService( return pullrequest.NewFakeService(
@@ -277,51 +252,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 {
@@ -330,8 +260,7 @@ func TestPullRequestGithubGenerateParams(t *testing.T) {
} }
generatorConfig := argoprojiov1alpha1.ApplicationSetGenerator{ generatorConfig := argoprojiov1alpha1.ApplicationSetGenerator{
PullRequest: &argoprojiov1alpha1.PullRequestGenerator{ PullRequest: &argoprojiov1alpha1.PullRequestGenerator{
Values: c.values, Values: c.values,
ContinueOnRepoNotFoundError: c.continueOnRepoNotFoundError,
}, },
} }
@@ -346,8 +275,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 +325,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 +348,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,7 +11,6 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v3/applicationset/services"
"github.com/argoproj/argo-cd/v3/applicationset/services/github_app_auth" "github.com/argoproj/argo-cd/v3/applicationset/services/github_app_auth"
"github.com/argoproj/argo-cd/v3/applicationset/services/scm_provider" "github.com/argoproj/argo-cd/v3/applicationset/services/scm_provider"
"github.com/argoproj/argo-cd/v3/applicationset/utils" "github.com/argoproj/argo-cd/v3/applicationset/utils"
@@ -23,7 +21,7 @@ import (
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 {
@@ -122,11 +118,11 @@ func ScmProviderAllowed(applicationSetInfo *argoprojiov1alpha1.ApplicationSet, g
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]any, 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 {
@@ -257,7 +253,6 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha
params := map[string]any{ params := map[string]any{
"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 +273,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(ctx, *auth, github.Organization, github.API, github.AllBranches, httpClient) *auth,
} github.Organization,
return scm_provider.NewGithubAppProviderFor(ctx, *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)
} }
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) return scm_provider.NewGithubProvider(github.Organization, token, github.API, github.AllBranches)
} }

View File

@@ -12,8 +12,6 @@ import (
) )
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
@@ -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,7 +33,6 @@ 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",
@@ -46,7 +42,6 @@ func TestSCMProviderGenerateParams(t *testing.T) {
{ {
"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",
@@ -90,7 +83,6 @@ func TestSCMProviderGenerateParams(t *testing.T) {
{ {
"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

@@ -10,15 +10,15 @@ import (
"github.com/argoproj/argo-cd/v3/applicationset/services" "github.com/argoproj/argo-cd/v3/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(ctx, c, 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(ctx, c, k8sClient, namespace),
} }
nestedGenerators := map[string]Generator{ nestedGenerators := map[string]Generator{

View File

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

@@ -58,7 +58,8 @@ func NewApplicationsetMetrics(appsetLister applisters.ApplicationSetLister, apps
metrics.Registry.MustRegister(reconcileHistogram) metrics.Registry.MustRegister(reconcileHistogram)
metrics.Registry.MustRegister(appsetCollector) metrics.Registry.MustRegister(appsetCollector)
kubectl.RegisterWithClientGo() kubectlMetricsServer := kubectl.NewKubectlMetrics()
kubectlMetricsServer.RegisterWithClientGo()
kubectl.RegisterWithPrometheus(metrics.Registry) kubectl.RegisterWithPrometheus(metrics.Registry)
return ApplicationsetMetrics{ return ApplicationsetMetrics{

View File

@@ -174,7 +174,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 +216,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

@@ -1,74 +1,35 @@
package github_app package github_app
import ( import (
"context"
"errors"
"fmt" "fmt"
"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/v3/applicationset/services/github_app_auth"
"github.com/argoproj/argo-cd/v3/util/git"
) )
// getInstallationClient creates a new GitHub client with the specified installation ID. // Client builds a github client for the given app authentication.
// It also returns a ghinstallation.Transport, which can be used for git requests. func Client(g github_app_auth.Authentication, url string) (*github.Client, error) {
func getInstallationClient(g github_app_auth.Authentication, url string, httpClient ...*http.Client) (*github.Client, error) { rt, err := ghinstallation.New(http.DefaultTransport, g.Id, g.InstallationId, []byte(g.PrivateKey))
if g.InstallationId <= 0 {
return nil, errors.New("installation ID is required for github")
}
// Use provided HTTP client's transport or default
var transport http.RoundTripper
if len(httpClient) > 0 && httpClient[0] != nil && httpClient[0].Transport != nil {
transport = httpClient[0].Transport
} else {
transport = http.DefaultTransport
}
itr, 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 installation transport: %w", err) return nil, fmt.Errorf("failed to create github app install: %w", err)
} }
if url == "" { if url == "" {
url = g.EnterpriseBaseURL url = g.EnterpriseBaseURL
} }
var client *github.Client var client *github.Client
if url == "" { if url == "" {
client = github.NewClient(&http.Client{Transport: itr}) httpClient := http.Client{Transport: rt}
return client, nil client = github.NewClient(&httpClient)
} } else {
rt.BaseURL = url
itr.BaseURL = url httpClient := http.Client{Transport: rt}
client, err = github.NewClient(&http.Client{Transport: itr}).WithEnterpriseURLs(url, url) 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)
}
} }
return client, nil return client, nil
} }
// Client builds a github client for the given app authentication.
func Client(ctx context.Context, g github_app_auth.Authentication, url, org string, optionalHTTPClient ...*http.Client) (*github.Client, error) {
if url == "" {
url = g.EnterpriseBaseURL
}
// If an installation ID is already provided, use it directly.
if g.InstallationId != 0 {
return getInstallationClient(g, url, optionalHTTPClient...)
}
// Auto-discover installation ID using shared utility
// Pass optional HTTP client for metrics tracking
installationId, err := git.DiscoverGitHubAppInstallationID(ctx, g.Id, g.PrivateKey, url, org, optionalHTTPClient...)
if err != nil {
return nil, err
}
g.InstallationId = installationId
return getInstallationClient(g, url, optionalHTTPClient...)
}

View File

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

View File

@@ -1,15 +1,78 @@
// Code generated by mockery; DO NOT EDIT. // Code generated by mockery v2.52.4. 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, project, noRevisionCache, verifyCommit
func (_m *Repos) GetDirectories(ctx context.Context, repoURL string, revision string, project string, noRevisionCache bool, verifyCommit bool) ([]string, error) {
ret := _m.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 rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool, bool) ([]string, error)); ok {
return rf(ctx, repoURL, revision, project, noRevisionCache, verifyCommit)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool, bool) []string); ok {
r0 = rf(ctx, repoURL, revision, project, noRevisionCache, verifyCommit)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, bool, bool) error); ok {
r1 = rf(ctx, repoURL, revision, project, noRevisionCache, verifyCommit)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetFiles provides a mock function with given fields: ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit
func (_m *Repos) GetFiles(ctx context.Context, repoURL string, revision string, project string, pattern string, noRevisionCache bool, verifyCommit bool) (map[string][]byte, error) {
ret := _m.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 rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, bool, bool) (map[string][]byte, error)); ok {
return rf(ctx, repoURL, revision, project, pattern, noRevisionCache, verifyCommit)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, bool, bool) map[string][]byte); ok {
r0 = rf(ctx, repoURL, revision, project, 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, string, bool, bool) error); ok {
r1 = rf(ctx, repoURL, revision, project, 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,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" "github.com/microsoft/azure-devops-go-api/azuredevops/core"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/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.
@@ -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 ||
@@ -107,7 +95,7 @@ func (a *AzureDevOpsService) List(ctx context.Context) ([]*PullRequest, error) {
if *pr.Repository.Name == a.repo { if *pr.Repository.Name == a.repo {
pullRequests = append(pullRequests, &PullRequest{ pullRequests = append(pullRequests, &PullRequest{
Number: int64(*pr.PullRequestId), Number: *pr.PullRequestId,
Title: *pr.Title, Title: *pr.Title,
Branch: strings.Replace(*pr.SourceRefName, "refs/heads/", "", 1), Branch: strings.Replace(*pr.SourceRefName, "refs/heads/", "", 1),
TargetBranch: strings.Replace(*pr.TargetRefName, "refs/heads/", "", 1), TargetBranch: strings.Replace(*pr.TargetRefName, "refs/heads/", "", 1),

View File

@@ -1,18 +1,18 @@
package pull_request package pull_request
import ( import (
"errors" "context"
"testing" "testing"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/core" "github.com/microsoft/azure-devops-go-api/azuredevops/webapi"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/git"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/webapi" "github.com/microsoft/azure-devops-go-api/azuredevops/core"
"github.com/microsoft/azure-devops-go-api/azuredevops/git"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
azureMock "github.com/argoproj/argo-cd/v3/applicationset/services/scm_provider/azure_devops/git/mocks" azureMock "github.com/argoproj/argo-cd/v3/applicationset/services/scm_provider/azure_devops/git/mocks"
"github.com/argoproj/argo-cd/v3/applicationset/services/scm_provider/mocks"
) )
func createBoolPtr(x bool) *bool { func createBoolPtr(x bool) *bool {
@@ -35,6 +35,29 @@ func createUniqueNamePtr(x string) *string {
return &x return &x
} }
type AzureClientFactoryMock struct {
mock *mock.Mock
}
func (m *AzureClientFactoryMock) GetClient(ctx context.Context) (git.Client, error) {
args := m.mock.Called(ctx)
var client git.Client
c := args.Get(0)
if c != nil {
client = c.(git.Client)
}
var err error
if len(args) > 1 {
if e, ok := args.Get(1).(error); ok {
err = e
}
}
return client, err
}
func TestListPullRequest(t *testing.T) { func TestListPullRequest(t *testing.T) {
teamProject := "myorg_project" teamProject := "myorg_project"
repoName := "myorg_project_repo" repoName := "myorg_project_repo"
@@ -68,10 +91,10 @@ func TestListPullRequest(t *testing.T) {
SearchCriteria: &git.GitPullRequestSearchCriteria{}, SearchCriteria: &git.GitPullRequestSearchCriteria{},
} }
gitClientMock := &azureMock.Client{} gitClientMock := azureMock.Client{}
clientFactoryMock := &mocks.AzureDevOpsClientFactory{} clientFactoryMock := &AzureClientFactoryMock{mock: &mock.Mock{}}
clientFactoryMock.EXPECT().GetClient(mock.Anything).Return(gitClientMock, nil) clientFactoryMock.mock.On("GetClient", mock.Anything).Return(&gitClientMock, nil)
gitClientMock.EXPECT().GetPullRequestsByProject(mock.Anything, args).Return(&pullRequestMock, nil) gitClientMock.On("GetPullRequestsByProject", ctx, args).Return(&pullRequestMock, nil)
provider := AzureDevOpsService{ provider := AzureDevOpsService{
clientFactory: clientFactoryMock, clientFactory: clientFactoryMock,
@@ -87,7 +110,7 @@ func TestListPullRequest(t *testing.T) {
assert.Equal(t, "main", list[0].TargetBranch) assert.Equal(t, "main", list[0].TargetBranch)
assert.Equal(t, prHeadSha, list[0].HeadSHA) assert.Equal(t, prHeadSha, list[0].HeadSHA)
assert.Equal(t, "feat(123)", list[0].Title) assert.Equal(t, "feat(123)", list[0].Title)
assert.Equal(t, int64(prID), list[0].Number) assert.Equal(t, prID, list[0].Number)
assert.Equal(t, uniqueName, list[0].Author) assert.Equal(t, uniqueName, list[0].Author)
} }
@@ -213,36 +236,3 @@ func TestBuildURL(t *testing.T) {
}) })
} }
} }
func TestAzureDevOpsListReturnsRepositoryNotFoundError(t *testing.T) {
args := git.GetPullRequestsByProjectArgs{
Project: createStringPtr("nonexistent"),
SearchCriteria: &git.GitPullRequestSearchCriteria{},
}
pullRequestMock := []git.GitPullRequest{}
gitClientMock := &azureMock.Client{}
clientFactoryMock := &mocks.AzureDevOpsClientFactory{}
clientFactoryMock.EXPECT().GetClient(mock.Anything).Return(gitClientMock, nil)
// Mock the GetPullRequestsByProject to return an error containing "404"
gitClientMock.EXPECT().GetPullRequestsByProject(mock.Anything, args).Return(&pullRequestMock,
errors.New("The following project does not exist:"))
provider := AzureDevOpsService{
clientFactory: clientFactoryMock,
project: "nonexistent",
repo: "nonexistent",
labels: nil,
}
prs, err := provider.List(t.Context())
// Should return empty pull requests list
assert.Empty(t, prs)
// Should return RepositoryNotFoundError
require.Error(t, err)
assert.True(t, IsRepositoryNotFoundError(err), "Expected RepositoryNotFoundError but got: %v", err)
}

View File

@@ -6,7 +6,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/url" "net/url"
"strings"
"github.com/ktrysmt/go-bitbucket" "github.com/ktrysmt/go-bitbucket"
) )
@@ -18,19 +17,10 @@ type BitbucketCloudService struct {
} }
type BitbucketCloudPullRequest struct { type BitbucketCloudPullRequest struct {
ID int `json:"id"` ID int `json:"id"`
Title string `json:"title"` Title string `json:"title"`
Source BitbucketCloudPullRequestSource `json:"source"` Source BitbucketCloudPullRequestSource `json:"source"`
Author BitbucketCloudPullRequestAuthor `json:"author"` Author BitbucketCloudPullRequestAuthor `json:"author"`
Destination BitbucketCloudPullRequestDestination `json:"destination"`
}
type BitbucketCloudPullRequestDestination struct {
Branch BitbucketCloudPullRequestDestinationBranch `json:"branch"`
}
type BitbucketCloudPullRequestDestinationBranch struct {
Name string `json:"name"`
} }
type BitbucketCloudPullRequestSource struct { type BitbucketCloudPullRequestSource struct {
@@ -81,10 +71,7 @@ func NewBitbucketCloudServiceBasicAuth(baseURL, username, password, owner, repos
return nil, fmt.Errorf("error parsing base url of %s for %s/%s: %w", baseURL, owner, repositorySlug, err) return nil, fmt.Errorf("error parsing base url of %s for %s/%s: %w", baseURL, owner, repositorySlug, err)
} }
bitbucketClient, err := bitbucket.NewBasicAuth(username, password) bitbucketClient := bitbucket.NewBasicAuth(username, password)
if err != nil {
return nil, fmt.Errorf("error creating BitBucket Cloud client with basic auth: %w", err)
}
bitbucketClient.SetApiBaseURL(*url) bitbucketClient.SetApiBaseURL(*url)
return &BitbucketCloudService{ return &BitbucketCloudService{
@@ -100,13 +87,14 @@ func NewBitbucketCloudServiceBearerToken(baseURL, bearerToken, owner, repository
return nil, fmt.Errorf("error parsing base url of %s for %s/%s: %w", baseURL, owner, repositorySlug, err) return nil, fmt.Errorf("error parsing base url of %s for %s/%s: %w", baseURL, owner, repositorySlug, err)
} }
bitbucketClient, err := bitbucket.NewOAuthbearerToken(bearerToken) bitbucketClient := bitbucket.NewOAuthbearerToken(bearerToken)
if err != nil {
return nil, fmt.Errorf("error creating BitBucket Cloud client with oauth bearer token: %w", err)
}
bitbucketClient.SetApiBaseURL(*url) bitbucketClient.SetApiBaseURL(*url)
return &BitbucketCloudService{client: bitbucketClient, owner: owner, repositorySlug: repositorySlug}, nil return &BitbucketCloudService{
client: bitbucketClient,
owner: owner,
repositorySlug: repositorySlug,
}, nil
} }
func NewBitbucketCloudServiceNoAuth(baseURL, owner, repositorySlug string) (PullRequestService, error) { func NewBitbucketCloudServiceNoAuth(baseURL, owner, repositorySlug string) (PullRequestService, error) {
@@ -120,17 +108,8 @@ func (b *BitbucketCloudService) List(_ context.Context) ([]*PullRequest, error)
RepoSlug: b.repositorySlug, RepoSlug: b.repositorySlug,
} }
pullRequests := []*PullRequest{}
response, err := b.client.Repositories.PullRequests.Gets(opts) response, err := b.client.Repositories.PullRequests.Gets(opts)
if err != nil { if err != nil {
// A standard Http 404 error is not returned for Bitbucket Cloud,
// so checking the error message for a specific pattern
if strings.Contains(err.Error(), "404 Not Found") {
// 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("error listing pull requests for %s/%s: %w", b.owner, b.repositorySlug, err) return nil, fmt.Errorf("error listing pull requests for %s/%s: %w", b.owner, b.repositorySlug, err)
} }
@@ -154,14 +133,14 @@ func (b *BitbucketCloudService) List(_ context.Context) ([]*PullRequest, error)
return nil, fmt.Errorf("error unmarshalling json to type '[]BitbucketCloudPullRequest': %w", err) return nil, fmt.Errorf("error unmarshalling json to type '[]BitbucketCloudPullRequest': %w", err)
} }
pullRequests := []*PullRequest{}
for _, pull := range pulls { for _, pull := range pulls {
pullRequests = append(pullRequests, &PullRequest{ pullRequests = append(pullRequests, &PullRequest{
Number: int64(pull.ID), Number: pull.ID,
Title: pull.Title, Title: pull.Title,
Branch: pull.Source.Branch.Name, Branch: pull.Source.Branch.Name,
TargetBranch: pull.Destination.Branch.Name, HeadSHA: pull.Source.Commit.Hash,
HeadSHA: pull.Source.Commit.Hash, Author: pull.Author.Nickname,
Author: pull.Author.Nickname,
}) })
} }

View File

@@ -89,7 +89,7 @@ func TestListPullRequestBearerTokenCloud(t *testing.T) {
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 1) assert.Len(t, pullRequests, 1)
assert.Equal(t, int64(101), pullRequests[0].Number) assert.Equal(t, 101, pullRequests[0].Number)
assert.Equal(t, "feat(foo-bar)", pullRequests[0].Title) assert.Equal(t, "feat(foo-bar)", pullRequests[0].Title)
assert.Equal(t, "feature/foo-bar", pullRequests[0].Branch) assert.Equal(t, "feature/foo-bar", pullRequests[0].Branch)
assert.Equal(t, "1a8dd249c04a", pullRequests[0].HeadSHA) assert.Equal(t, "1a8dd249c04a", pullRequests[0].HeadSHA)
@@ -107,7 +107,7 @@ func TestListPullRequestNoAuthCloud(t *testing.T) {
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 1) assert.Len(t, pullRequests, 1)
assert.Equal(t, int64(101), pullRequests[0].Number) assert.Equal(t, 101, pullRequests[0].Number)
assert.Equal(t, "feat(foo-bar)", pullRequests[0].Title) assert.Equal(t, "feat(foo-bar)", pullRequests[0].Title)
assert.Equal(t, "feature/foo-bar", pullRequests[0].Branch) assert.Equal(t, "feature/foo-bar", pullRequests[0].Branch)
assert.Equal(t, "1a8dd249c04a", pullRequests[0].HeadSHA) assert.Equal(t, "1a8dd249c04a", pullRequests[0].HeadSHA)
@@ -125,7 +125,7 @@ func TestListPullRequestBasicAuthCloud(t *testing.T) {
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 1) assert.Len(t, pullRequests, 1)
assert.Equal(t, int64(101), pullRequests[0].Number) assert.Equal(t, 101, pullRequests[0].Number)
assert.Equal(t, "feat(foo-bar)", pullRequests[0].Title) assert.Equal(t, "feat(foo-bar)", pullRequests[0].Title)
assert.Equal(t, "feature/foo-bar", pullRequests[0].Branch) assert.Equal(t, "feature/foo-bar", pullRequests[0].Branch)
assert.Equal(t, "1a8dd249c04a", pullRequests[0].HeadSHA) assert.Equal(t, "1a8dd249c04a", pullRequests[0].HeadSHA)
@@ -138,7 +138,7 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
var err error var err error
switch r.RequestURI { switch r.RequestURI {
case "/repositories/OWNER/REPO/pullrequests/": case "/repositories/OWNER/REPO/pullrequests/":
_, err = fmt.Fprintf(w, `{ _, err = io.WriteString(w, fmt.Sprintf(`{
"size": 2, "size": 2,
"pagelen": 1, "pagelen": 1,
"page": 1, "page": 1,
@@ -177,9 +177,9 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
} }
} }
] ]
}`, r.Host) }`, r.Host))
case "/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=2": case "/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=2":
_, err = fmt.Fprintf(w, `{ _, err = io.WriteString(w, fmt.Sprintf(`{
"size": 2, "size": 2,
"pagelen": 1, "pagelen": 1,
"page": 2, "page": 2,
@@ -202,7 +202,7 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
} }
} }
] ]
}`, r.Host) }`, r.Host))
default: default:
t.Fail() t.Fail()
} }
@@ -329,7 +329,7 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
var err error var err error
switch r.RequestURI { switch r.RequestURI {
case "/repositories/OWNER/REPO/pullrequests/": case "/repositories/OWNER/REPO/pullrequests/":
_, err = fmt.Fprintf(w, `{ _, err = io.WriteString(w, fmt.Sprintf(`{
"size": 2, "size": 2,
"pagelen": 1, "pagelen": 1,
"page": 1, "page": 1,
@@ -349,11 +349,6 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
}, },
"author": { "author": {
"nickname": "testName" "nickname": "testName"
},
"destination": {
"branch": {
"name": "master"
}
} }
}, },
{ {
@@ -370,17 +365,12 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
}, },
"author": { "author": {
"nickname": "testName" "nickname": "testName"
},
"destination": {
"branch": {
"name": "branch-200"
}
} }
} }
] ]
}`, r.Host) }`, r.Host))
case "/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=2": case "/repositories/OWNER/REPO/pullrequests/?pagelen=1&page=2":
_, err = fmt.Fprintf(w, `{ _, err = io.WriteString(w, fmt.Sprintf(`{
"size": 2, "size": 2,
"pagelen": 1, "pagelen": 1,
"page": 2, "page": 2,
@@ -400,15 +390,10 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
}, },
"author": { "author": {
"nickname": "testName" "nickname": "testName"
},
"destination": {
"branch": {
"name": "master"
}
} }
} }
] ]
}`, r.Host) }`, r.Host))
default: default:
t.Fail() t.Fail()
} }
@@ -428,20 +413,18 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 2) assert.Len(t, pullRequests, 2)
assert.Equal(t, PullRequest{ assert.Equal(t, PullRequest{
Number: 101, Number: 101,
Title: "feat(101)", Title: "feat(101)",
Branch: "feature-101", Branch: "feature-101",
HeadSHA: "1a8dd249c04a", HeadSHA: "1a8dd249c04a",
Author: "testName", Author: "testName",
TargetBranch: "master",
}, *pullRequests[0]) }, *pullRequests[0])
assert.Equal(t, PullRequest{ assert.Equal(t, PullRequest{
Number: 102, Number: 102,
Title: "feat(102)", Title: "feat(102)",
Branch: "feature-102", Branch: "feature-102",
HeadSHA: "6344d9623e3b", HeadSHA: "6344d9623e3b",
Author: "testName", Author: "testName",
TargetBranch: "master",
}, *pullRequests[1]) }, *pullRequests[1])
regexp = `.*2$` regexp = `.*2$`
@@ -455,12 +438,11 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 1) assert.Len(t, pullRequests, 1)
assert.Equal(t, PullRequest{ assert.Equal(t, PullRequest{
Number: 102, Number: 102,
Title: "feat(102)", Title: "feat(102)",
Branch: "feature-102", Branch: "feature-102",
HeadSHA: "6344d9623e3b", HeadSHA: "6344d9623e3b",
Author: "testName", Author: "testName",
TargetBranch: "master",
}, *pullRequests[0]) }, *pullRequests[0])
regexp = `[\d{2}` regexp = `[\d{2}`
@@ -472,49 +454,4 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
}, },
}) })
require.Error(t, err) require.Error(t, err)
regexp = `feature-2[\d]{2}`
targetRegexp := `branch.*`
pullRequests, err = ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{
{
BranchMatch: &regexp,
TargetBranchMatch: &targetRegexp,
},
})
require.NoError(t, err)
assert.Len(t, pullRequests, 1)
assert.Equal(t, PullRequest{
Number: 200,
Title: "feat(200)",
Branch: "feature-200",
HeadSHA: "4cf807e67a6d",
Author: "testName",
TargetBranch: "branch-200",
}, *pullRequests[0])
}
func TestBitbucketCloudListReturnsRepositoryNotFoundError(t *testing.T) {
mux := http.NewServeMux()
server := httptest.NewServer(mux)
defer server.Close()
path := "/repositories/nonexistent/nonexistent/pullrequests/"
mux.HandleFunc(path, func(w http.ResponseWriter, _ *http.Request) {
// Return 404 status to simulate repository not found
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"message": "404 Project Not Found"}`))
})
svc, err := NewBitbucketCloudServiceNoAuth(server.URL, "nonexistent", "nonexistent")
require.NoError(t, err)
prs, err := svc.List(t.Context())
// Should return empty pull requests list
assert.Empty(t, prs)
// Should return RepositoryNotFoundError
require.Error(t, err)
assert.True(t, IsRepositoryNotFoundError(err), "Expected RepositoryNotFoundError but got: %v", err)
} }

View File

@@ -8,7 +8,7 @@ import (
bitbucketv1 "github.com/gfleury/go-bitbucket-v1" bitbucketv1 "github.com/gfleury/go-bitbucket-v1"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v3/applicationset/services" "github.com/argoproj/argo-cd/v3/applicationset/utils"
) )
type BitbucketService struct { type BitbucketService struct {
@@ -49,10 +49,15 @@ func NewBitbucketServiceNoAuth(ctx context.Context, url, projectKey, repositoryS
} }
func newBitbucketService(ctx context.Context, bitbucketConfig *bitbucketv1.Configuration, projectKey, repositorySlug string, scmRootCAPath string, insecure bool, caCerts []byte) (PullRequestService, error) { func newBitbucketService(ctx context.Context, bitbucketConfig *bitbucketv1.Configuration, projectKey, repositorySlug string, scmRootCAPath string, insecure bool, caCerts []byte) (PullRequestService, error) {
bbClient := services.SetupBitbucketClient(ctx, bitbucketConfig, scmRootCAPath, insecure, caCerts) bitbucketConfig.BasePath = utils.NormalizeBitbucketBasePath(bitbucketConfig.BasePath)
tlsConfig := utils.GetTlsConfig(scmRootCAPath, insecure, caCerts)
bitbucketConfig.HTTPClient = &http.Client{Transport: &http.Transport{
TLSClientConfig: tlsConfig,
}}
bitbucketClient := bitbucketv1.NewAPIClient(ctx, bitbucketConfig)
return &BitbucketService{ return &BitbucketService{
client: bbClient, client: bitbucketClient,
projectKey: projectKey, projectKey: projectKey,
repositorySlug: repositorySlug, repositorySlug: repositorySlug,
}, nil }, nil
@@ -67,11 +72,6 @@ func (b *BitbucketService) List(_ context.Context) ([]*PullRequest, error) {
for { for {
response, err := b.client.DefaultApi.GetPullRequestsPage(b.projectKey, b.repositorySlug, paged) response, err := b.client.DefaultApi.GetPullRequestsPage(b.projectKey, b.repositorySlug, paged)
if err != nil { if err != nil {
if response != nil && response.Response != nil && response.StatusCode == http.StatusNotFound {
// 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("error listing pull requests for %s/%s: %w", b.projectKey, b.repositorySlug, err) return nil, fmt.Errorf("error listing pull requests for %s/%s: %w", b.projectKey, b.repositorySlug, err)
} }
pulls, err := bitbucketv1.GetPullRequestsResponse(response) pulls, err := bitbucketv1.GetPullRequestsResponse(response)
@@ -82,7 +82,7 @@ func (b *BitbucketService) List(_ context.Context) ([]*PullRequest, error) {
for _, pull := range pulls { for _, pull := range pulls {
pullRequests = append(pullRequests, &PullRequest{ pullRequests = append(pullRequests, &PullRequest{
Number: int64(pull.ID), Number: pull.ID,
Title: pull.Title, Title: pull.Title,
Branch: pull.FromRef.DisplayID, // ID: refs/heads/main DisplayID: main Branch: pull.FromRef.DisplayID, // ID: refs/heads/main DisplayID: main
TargetBranch: pull.ToRef.DisplayID, TargetBranch: pull.ToRef.DisplayID,

View File

@@ -68,7 +68,7 @@ func TestListPullRequestNoAuth(t *testing.T) {
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 1) assert.Len(t, pullRequests, 1)
assert.Equal(t, int64(101), pullRequests[0].Number) assert.Equal(t, 101, pullRequests[0].Number)
assert.Equal(t, "feat(ABC) : 123", pullRequests[0].Title) assert.Equal(t, "feat(ABC) : 123", pullRequests[0].Title)
assert.Equal(t, "feature-ABC-123", pullRequests[0].Branch) assert.Equal(t, "feature-ABC-123", pullRequests[0].Branch)
assert.Equal(t, "master", pullRequests[0].TargetBranch) assert.Equal(t, "master", pullRequests[0].TargetBranch)
@@ -211,7 +211,7 @@ func TestListPullRequestBasicAuth(t *testing.T) {
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 1) assert.Len(t, pullRequests, 1)
assert.Equal(t, int64(101), pullRequests[0].Number) assert.Equal(t, 101, pullRequests[0].Number)
assert.Equal(t, "feature-ABC-123", pullRequests[0].Branch) assert.Equal(t, "feature-ABC-123", pullRequests[0].Branch)
assert.Equal(t, "cb3cf2e4d1517c83e720d2585b9402dbef71f992", pullRequests[0].HeadSHA) assert.Equal(t, "cb3cf2e4d1517c83e720d2585b9402dbef71f992", pullRequests[0].HeadSHA)
} }
@@ -228,7 +228,7 @@ func TestListPullRequestBearerAuth(t *testing.T) {
pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{}) pullRequests, err := ListPullRequests(t.Context(), svc, []v1alpha1.PullRequestGeneratorFilter{})
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pullRequests, 1) assert.Len(t, pullRequests, 1)
assert.Equal(t, int64(101), pullRequests[0].Number) assert.Equal(t, 101, pullRequests[0].Number)
assert.Equal(t, "feat(ABC) : 123", pullRequests[0].Title) assert.Equal(t, "feat(ABC) : 123", pullRequests[0].Title)
assert.Equal(t, "feature-ABC-123", pullRequests[0].Branch) assert.Equal(t, "feature-ABC-123", pullRequests[0].Branch)
assert.Equal(t, "cb3cf2e4d1517c83e720d2585b9402dbef71f992", pullRequests[0].HeadSHA) assert.Equal(t, "cb3cf2e4d1517c83e720d2585b9402dbef71f992", pullRequests[0].HeadSHA)
@@ -510,29 +510,3 @@ func TestListPullRequestBranchMatch(t *testing.T) {
}) })
require.Error(t, err) require.Error(t, err)
} }
func TestBitbucketServerListReturnsRepositoryNotFoundError(t *testing.T) {
mux := http.NewServeMux()
server := httptest.NewServer(mux)
defer server.Close()
path := "/rest/api/1.0/projects/nonexistent/repos/nonexistent/pull-requests?limit=100"
mux.HandleFunc(path, func(w http.ResponseWriter, _ *http.Request) {
// Return 404 status to simulate repository not found
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"message": "404 Project Not Found"}`))
})
svc, err := NewBitbucketServiceNoAuth(t.Context(), server.URL, "nonexistent", "nonexistent", "", false, nil)
require.NoError(t, err)
prs, err := svc.List(t.Context())
// Should return empty pull requests list
assert.Empty(t, prs)
// Should return RepositoryNotFoundError
require.Error(t, err)
assert.True(t, IsRepositoryNotFoundError(err), "Expected RepositoryNotFoundError but got: %v", err)
}

View File

@@ -1,23 +0,0 @@
package pull_request
import "errors"
// RepositoryNotFoundError represents an error when a repository is not found by a pull request provider
type RepositoryNotFoundError struct {
causingError error
}
func (e *RepositoryNotFoundError) Error() string {
return e.causingError.Error()
}
// NewRepositoryNotFoundError creates a new repository not found error
func NewRepositoryNotFoundError(err error) error {
return &RepositoryNotFoundError{causingError: err}
}
// IsRepositoryNotFoundError checks if the given error is a repository not found error
func IsRepositoryNotFoundError(err error) bool {
var repoErr *RepositoryNotFoundError
return errors.As(err, &repoErr)
}

View File

@@ -1,48 +0,0 @@
package pull_request
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRepositoryNotFoundError(t *testing.T) {
t.Run("NewRepositoryNotFoundError creates correct error type", func(t *testing.T) {
originalErr := errors.New("repository does not exist")
repoNotFoundErr := NewRepositoryNotFoundError(originalErr)
require.Error(t, repoNotFoundErr)
assert.Equal(t, "repository does not exist", repoNotFoundErr.Error())
})
t.Run("IsRepositoryNotFoundError identifies RepositoryNotFoundError", func(t *testing.T) {
originalErr := errors.New("repository does not exist")
repoNotFoundErr := NewRepositoryNotFoundError(originalErr)
assert.True(t, IsRepositoryNotFoundError(repoNotFoundErr))
})
t.Run("IsRepositoryNotFoundError returns false for regular errors", func(t *testing.T) {
regularErr := errors.New("some other error")
assert.False(t, IsRepositoryNotFoundError(regularErr))
})
t.Run("IsRepositoryNotFoundError returns false for nil error", func(t *testing.T) {
assert.False(t, IsRepositoryNotFoundError(nil))
})
t.Run("IsRepositoryNotFoundError works with wrapped errors", func(t *testing.T) {
originalErr := errors.New("repository does not exist")
repoNotFoundErr := NewRepositoryNotFoundError(originalErr)
wrappedErr := errors.New("wrapped: " + repoNotFoundErr.Error())
// Direct RepositoryNotFoundError should be identified
assert.True(t, IsRepositoryNotFoundError(repoNotFoundErr))
// Wrapped string error should not be identified (this is expected behavior)
assert.False(t, IsRepositoryNotFoundError(wrappedErr))
})
}

View File

@@ -14,12 +14,11 @@ type GiteaService struct {
client *gitea.Client client *gitea.Client
owner string owner string
repo string repo string
labels []string
} }
var _ PullRequestService = (*GiteaService)(nil) var _ PullRequestService = (*GiteaService)(nil)
func NewGiteaService(token, url, owner, repo string, labels []string, insecure bool) (PullRequestService, error) { func NewGiteaService(token, url, owner, repo string, insecure bool) (PullRequestService, error) {
if token == "" { if token == "" {
token = os.Getenv("GITEA_TOKEN") token = os.Getenv("GITEA_TOKEN")
} }
@@ -43,7 +42,6 @@ func NewGiteaService(token, url, owner, repo string, labels []string, insecure b
client: client, client: client,
owner: owner, owner: owner,
repo: repo, repo: repo,
labels: labels,
}, nil }, nil
} }
@@ -52,23 +50,14 @@ func (g *GiteaService) List(ctx context.Context) ([]*PullRequest, error) {
State: gitea.StateOpen, State: gitea.StateOpen,
} }
g.client.SetContext(ctx) g.client.SetContext(ctx)
list := []*PullRequest{} prs, _, err := g.client.ListRepoPullRequests(g.owner, g.repo, opts)
prs, resp, err := g.client.ListRepoPullRequests(g.owner, g.repo, opts)
if err != nil { if err != nil {
if resp != nil && resp.StatusCode == http.StatusNotFound {
// return a custom error indicating that the repository is not found,
// but also returning the empty result since the decision to continue or not in this case is made by the caller
return list, NewRepositoryNotFoundError(err)
}
return nil, err return nil, err
} }
list := []*PullRequest{}
for _, pr := range prs { for _, pr := range prs {
if !giteaContainLabels(g.labels, pr.Labels) {
continue
}
list = append(list, &PullRequest{ list = append(list, &PullRequest{
Number: int64(pr.Index), Number: int(pr.Index),
Title: pr.Title, Title: pr.Title,
Branch: pr.Head.Ref, Branch: pr.Head.Ref,
TargetBranch: pr.Base.Ref, TargetBranch: pr.Base.Ref,
@@ -80,21 +69,6 @@ func (g *GiteaService) List(ctx context.Context) ([]*PullRequest, error) {
return list, nil return list, nil
} }
// containLabels returns true if gotLabels contains expectedLabels
func giteaContainLabels(expectedLabels []string, gotLabels []*gitea.Label) bool {
gotLabelNamesMap := make(map[string]bool)
for i := 0; i < len(gotLabels); i++ {
gotLabelNamesMap[gotLabels[i].Name] = true
}
for _, expected := range expectedLabels {
v, ok := gotLabelNamesMap[expected]
if !v || !ok {
return false
}
}
return true
}
// Get the Gitea pull request label names. // Get the Gitea pull request label names.
func getGiteaPRLabelNames(giteaLabels []*gitea.Label) []string { func getGiteaPRLabelNames(giteaLabels []*gitea.Label) []string {
var labelNames []string var labelNames []string

View File

@@ -52,7 +52,7 @@ func giteaMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
}, },
"title": "add an empty file", "title": "add an empty file",
"body": "", "body": "",
"labels": [{"id": 1, "name": "label1", "color": "00aabb", "description": "foo", "url": ""}], "labels": [],
"milestone": null, "milestone": null,
"assignee": null, "assignee": null,
"assignees": null, "assignees": null,
@@ -246,64 +246,16 @@ func giteaMockHandler(t *testing.T) func(http.ResponseWriter, *http.Request) {
} }
} }
func TestGiteaContainLabels(t *testing.T) {
cases := []struct {
Name string
Labels []string
PullLabels []*gitea.Label
Expect bool
}{
{
Name: "Match labels",
Labels: []string{"label1", "label2"},
PullLabels: []*gitea.Label{
{Name: "label1"},
{Name: "label2"},
{Name: "label3"},
},
Expect: true,
},
{
Name: "Not match labels",
Labels: []string{"label1", "label4"},
PullLabels: []*gitea.Label{
{Name: "label1"},
{Name: "label2"},
{Name: "label3"},
},
Expect: false,
},
{
Name: "No specify",
Labels: []string{},
PullLabels: []*gitea.Label{
{Name: "label1"},
{Name: "label2"},
{Name: "label3"},
},
Expect: true,
},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
if got := giteaContainLabels(c.Labels, c.PullLabels); got != c.Expect {
t.Errorf("expect: %v, got: %v", c.Expect, got)
}
})
}
}
func TestGiteaList(t *testing.T) { func TestGiteaList(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
giteaMockHandler(t)(w, r) giteaMockHandler(t)(w, r)
})) }))
host, err := NewGiteaService("", ts.URL, "test-argocd", "pr-test", []string{"label1"}, false) host, err := NewGiteaService("", ts.URL, "test-argocd", "pr-test", false)
require.NoError(t, err) require.NoError(t, err)
prs, err := host.List(t.Context()) prs, err := host.List(t.Context())
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, prs, 1) assert.Len(t, prs, 1)
assert.Equal(t, int64(1), prs[0].Number) assert.Equal(t, 1, prs[0].Number)
assert.Equal(t, "add an empty file", prs[0].Title) assert.Equal(t, "add an empty file", prs[0].Title)
assert.Equal(t, "test", prs[0].Branch) assert.Equal(t, "test", prs[0].Branch)
assert.Equal(t, "main", prs[0].TargetBranch) assert.Equal(t, "main", prs[0].TargetBranch)
@@ -339,35 +291,3 @@ func TestGetGiteaPRLabelNames(t *testing.T) {
}) })
} }
} }
func TestGiteaListReturnsRepositoryNotFoundError(t *testing.T) {
mux := http.NewServeMux()
server := httptest.NewServer(mux)
defer server.Close()
// Handle version endpoint that Gitea client calls first
mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"version":"1.17.0+dev-452-g1f0541780"}`))
})
path := "/api/v1/repos/nonexistent/nonexistent/pulls?limit=0&page=1&state=open"
mux.HandleFunc(path, func(w http.ResponseWriter, _ *http.Request) {
// Return 404 status to simulate repository not found
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"message": "404 Project Not Found"}`))
})
svc, err := NewGiteaService("", server.URL, "nonexistent", "nonexistent", []string{}, false)
require.NoError(t, err)
prs, err := svc.List(t.Context())
// Should return empty pull requests list
assert.Empty(t, prs)
// Should return RepositoryNotFoundError
require.Error(t, err)
assert.True(t, IsRepositoryNotFoundError(err), "Expected RepositoryNotFoundError but got: %v", err)
}

View File

@@ -6,9 +6,7 @@ import (
"net/http" "net/http"
"os" "os"
"github.com/google/go-github/v69/github" "github.com/google/go-github/v66/github"
appsetutils "github.com/argoproj/argo-cd/v3/applicationset/utils"
) )
type GithubService struct { type GithubService struct {
@@ -20,28 +18,18 @@ type GithubService struct {
var _ PullRequestService = (*GithubService)(nil) var _ PullRequestService = (*GithubService)(nil)
func NewGithubService(token, url, owner, repo string, labels []string, optionalHTTPClient ...*http.Client) (PullRequestService, error) { func NewGithubService(token, url, owner, repo string, labels []string) (PullRequestService, error) {
// Undocumented environment variable to set a default token, to be used in testing to dodge anonymous rate limits. // Undocumented environment variable to set a default token, to be used in testing to dodge anonymous rate limits.
if token == "" { if token == "" {
token = os.Getenv("GITHUB_TOKEN") token = os.Getenv("GITHUB_TOKEN")
} }
httpClient := &http.Client{}
var client *github.Client var client *github.Client
httpClient := appsetutils.GetOptionalHTTPClient(optionalHTTPClient...)
if url == "" { if url == "" {
if token == "" { client = github.NewClient(httpClient).WithAuthToken(token)
client = github.NewClient(httpClient)
} else {
client = github.NewClient(httpClient).WithAuthToken(token)
}
} else { } else {
var err error var err error
if token == "" { client, err = github.NewClient(httpClient).WithEnterpriseURLs(url, url)
client, err = github.NewClient(httpClient).WithEnterpriseURLs(url, url)
} else {
client, err = github.NewClient(httpClient).WithAuthToken(token).WithEnterpriseURLs(url, url)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -64,11 +52,6 @@ func (g *GithubService) List(ctx context.Context) ([]*PullRequest, error) {
for { for {
pulls, resp, err := g.client.PullRequests.List(ctx, g.owner, g.repo, opts) pulls, resp, err := g.client.PullRequests.List(ctx, g.owner, g.repo, opts)
if err != nil { if err != nil {
if resp != nil && resp.StatusCode == http.StatusNotFound {
// return a custom error indicating that the repository is not found,
// but also returning 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("error listing pull requests for %s/%s: %w", g.owner, g.repo, err) return nil, fmt.Errorf("error listing pull requests for %s/%s: %w", g.owner, g.repo, err)
} }
for _, pull := range pulls { for _, pull := range pulls {
@@ -76,7 +59,7 @@ func (g *GithubService) List(ctx context.Context) ([]*PullRequest, error) {
continue continue
} }
pullRequests = append(pullRequests, &PullRequest{ pullRequests = append(pullRequests, &PullRequest{
Number: int64(*pull.Number), Number: *pull.Number,
Title: *pull.Title, Title: *pull.Title,
Branch: *pull.Head.Ref, Branch: *pull.Head.Ref,
TargetBranch: *pull.Base.Ref, TargetBranch: *pull.Base.Ref,

View File

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

View File

@@ -1,12 +1,9 @@
package pull_request package pull_request
import ( import (
"net/http"
"net/http/httptest"
"testing" "testing"
"github.com/google/go-github/v69/github" "github.com/google/go-github/v66/github"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -89,29 +86,3 @@ func TestGetGitHubPRLabelNames(t *testing.T) {
}) })
} }
} }
func TestGitHubListReturnsRepositoryNotFoundError(t *testing.T) {
mux := http.NewServeMux()
server := httptest.NewServer(mux)
defer server.Close()
path := "/repos/nonexistent/nonexistent/pulls"
mux.HandleFunc(path, func(w http.ResponseWriter, _ *http.Request) {
// Return 404 status to simulate repository not found
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"message": "404 Project Not Found"}`))
})
svc, err := NewGithubService("", server.URL, "nonexistent", "nonexistent", []string{}, nil)
require.NoError(t, err)
prs, err := svc.List(t.Context())
// Should return empty pull requests list
assert.Empty(t, prs)
// Should return RepositoryNotFoundError
require.Error(t, err)
assert.True(t, IsRepositoryNotFoundError(err), "Expected RepositoryNotFoundError but got: %v", err)
}

View File

@@ -61,15 +61,11 @@ func (g *GitLabService) List(ctx context.Context) ([]*PullRequest, error) {
var labelsList gitlab.LabelOptions = g.labels var labelsList gitlab.LabelOptions = g.labels
labels = &labelsList labels = &labelsList
} }
opts := &gitlab.ListProjectMergeRequestsOptions{
snippetsListOptions := gitlab.ExploreSnippetsOptions{
ListOptions: gitlab.ListOptions{ ListOptions: gitlab.ListOptions{
PerPage: 100, PerPage: 100,
}, },
} Labels: labels,
opts := &gitlab.ListProjectMergeRequestsOptions{
ListOptions: snippetsListOptions.ListOptions,
Labels: labels,
} }
if g.pullRequestState != "" { if g.pullRequestState != "" {
@@ -80,11 +76,6 @@ func (g *GitLabService) List(ctx context.Context) ([]*PullRequest, error) {
for { for {
mrs, resp, err := g.client.MergeRequests.ListProjectMergeRequests(g.project, opts, gitlab.WithContext(ctx)) mrs, resp, err := g.client.MergeRequests.ListProjectMergeRequests(g.project, opts, gitlab.WithContext(ctx))
if err != nil { if err != nil {
if resp != nil && resp.StatusCode == http.StatusNotFound {
// return a custom error indicating that the repository is not found,
// but also returning 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("error listing merge requests for project '%s': %w", g.project, err) return nil, fmt.Errorf("error listing merge requests for project '%s': %w", g.project, err)
} }
for _, mr := range mrs { for _, mr := range mrs {

View File

@@ -78,7 +78,7 @@ func TestList(t *testing.T) {
prs, err := svc.List(t.Context()) prs, err := svc.List(t.Context())
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, prs, 1) assert.Len(t, prs, 1)
assert.Equal(t, int64(15442), prs[0].Number) assert.Equal(t, 15442, prs[0].Number)
assert.Equal(t, "Draft: Use structured logging for DB load balancer", prs[0].Title) assert.Equal(t, "Draft: Use structured logging for DB load balancer", prs[0].Title)
assert.Equal(t, "use-structured-logging-for-db-load-balancer", prs[0].Branch) assert.Equal(t, "use-structured-logging-for-db-load-balancer", prs[0].Branch)
assert.Equal(t, "master", prs[0].TargetBranch) assert.Equal(t, "master", prs[0].TargetBranch)
@@ -191,29 +191,3 @@ func TestListWithStateTLS(t *testing.T) {
}) })
} }
} }
func TestGitLabListReturnsRepositoryNotFoundError(t *testing.T) {
mux := http.NewServeMux()
server := httptest.NewServer(mux)
defer server.Close()
path := "/api/v4/projects/nonexistent/merge_requests"
mux.HandleFunc(path, func(w http.ResponseWriter, _ *http.Request) {
// Return 404 status to simulate repository not found
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"message": "404 Project Not Found"}`))
})
svc, err := NewGitLabService("", server.URL, "nonexistent", []string{}, "", "", false, nil)
require.NoError(t, err)
prs, err := svc.List(t.Context())
// Should return empty pull requests list
assert.Empty(t, prs)
// Should return RepositoryNotFoundError
require.Error(t, err)
assert.True(t, IsRepositoryNotFoundError(err), "Expected RepositoryNotFoundError but got: %v", err)
}

View File

@@ -7,8 +7,7 @@ import (
type PullRequest struct { type PullRequest struct {
// Number is a number that will be the ID of the pull request. // Number is a number that will be the ID of the pull request.
// Gitlab uses int64 for the pull request number. Number int
Number int64
// Title of the pull request. // Title of the pull request.
Title string Title string
// Branch is the name of the branch from which the pull request originated. // Branch is the name of the branch from which the pull request originated.
@@ -31,5 +30,4 @@ type PullRequestService interface {
type Filter struct { type Filter struct {
BranchMatch *regexp.Regexp BranchMatch *regexp.Regexp
TargetBranchMatch *regexp.Regexp TargetBranchMatch *regexp.Regexp
TitleMatch *regexp.Regexp
} }

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